validate

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2023 License: Apache-2.0 Imports: 6 Imported by: 7

README

Validate

Build Report Card GoDoc

connectrpc.com/validate provides a Connect interceptor that takes the tedium out of data validation. Rather than hand-writing repetitive documentation and code — verifing that User.email is valid, or that User.age falls within reasonable bounds — you can instead encode those constraints into your Protobuf schemas and automatically enforce them at runtime.

Under the hood, this package is powered by protovalidate and the Common Expression Language. Together, they make validation flexible, efficient, and consistent across languages without additional code generation.

Installation

go get connectrpc.com/validate

A small example

Curious what all this looks like in practice? First, let's define a schema for our user service:

syntax = "proto3";

package example.user.v1;

import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";

message User {
  // Simple constraints, like checking that an email address is valid, are
  // predefined.
  string email = 1 [(buf.validate.field).string.email = true];

  // For more complex use cases, like comparing fields against each other, we
  // can write a CEL expression.
  google.protobuf.Timestamp birth_date = 2;
  google.protobuf.Timestamp signup_date = 3;

  option (buf.validate.message).cel = {
    id: "user.signup_date",
    message: "signup date must be on or after birth date",
    expression: "this.signup_date >= this.birth_date"
  };
}

message CreateUserRequest {
  User user = 1;
}

message CreateUserResponse {
  User user = 1;
}

service UserService {
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {}
}

Notice that simple constraints, like checking email addresses, are short and declarative. When we need a more elaborate constraint, we can write a custom CEL expression, customize the error message, and much more. (See the main protovalidate repository for more examples.)

After implementing UserService, we can add a validating interceptor with just one option:

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"

	"connectrpc.com/connect"
	"connectrpc.com/validate"
	userv1 "connectrpc.com/validate/internal/gen/example/user/v1"
	"connectrpc.com/validate/internal/gen/validate/example/v1/userv1connect"
)

func main() {
	interceptor, err := validate.NewInterceptor()
	if err != nil {
		log.Fatal(err)
	}
	
	mux := http.NewServeMux()
	mux.Handle(userv1connect.NewUserServiceHandler(
		&userv1connect.UnimplementedUserServiceHandler{},
		connect.WithInterceptors(interceptor),
	))

	http.ListenAndServe("localhost:8080", mux)
}

With the validate.Interceptor applied, our UserService implementation can assume that all requests have already been validated — no need for hand-written boilerplate!

FAQ

Does this interceptor work with Connect clients?

Yes: it validates request messages before sending them to the server. But unless you're sure that your clients always have an up-to-date schema, it's better to let the server handle validation.

How do clients know which fields are invalid?

If the request message fails validation, the interceptor returns an error coded with connect.CodeInvalidArgument. It also adds a detailed representation of the validation error(s) as an error detail.

How should schemas import protovalidate's options?

Because this interceptor uses protovalidate, it doesn't need any generated code for validation. However, any Protobuf schemas with constraints must import buf/validate/validate.proto. It's easiest to import this file directly from the Buf Schema Registry: this repository contains an example schema with constraints, buf.yaml and buf.gen.yaml configuration files, and make generate recipe.

Ecosystem

Status: Unstable

This module is unstable. Expect breaking changes as we iterate toward a stable release.

It supports:

Within those parameters, this project follows semantic versioning. Once we tag a stable release, we will not make breaking changes without incrementing the major version.

License

Offered under the Apache 2 license.

Documentation

Overview

Package validate provides a connect.Interceptor that validates messages against constraints specified in their Protobuf schemas. Because the interceptor is powered by protovalidate, validation is flexible, efficient, and consistent across languages - without additional code generation.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Interceptor

type Interceptor struct {
	// contains filtered or unexported fields
}

Interceptor is a connect.Interceptor that ensures that RPC request messages match the constraints expressed in their Protobuf schemas. It does not validate response messages.

By default, Interceptors use a validator that lazily compiles constraints and works with any Protobuf message. This is a simple, widely-applicable configuration: after compiling and caching the constraints for a Protobuf message type once, validation is very efficient. To customize the validator, use WithValidator and protovalidate.ValidatorOption.

RPCs with invalid request messages short-circuit with an error. The error always uses connect.CodeInvalidArgument and has a detailed representation of the error attached as a connect.ErrorDetail.

This interceptor is primarily intended for use on handlers. Client-side use is possible, but discouraged unless the client always has an up-to-date schema.

func NewInterceptor

func NewInterceptor(opts ...Option) (*Interceptor, error)

NewInterceptor builds an Interceptor. The default configuration is appropriate for most use cases.

func (*Interceptor) WrapStreamingClient

func (i *Interceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc

WrapStreamingClient implements connect.Interceptor.

func (*Interceptor) WrapStreamingHandler

WrapStreamingHandler implements connect.Interceptor.

func (*Interceptor) WrapUnary

func (i *Interceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc

WrapUnary implements connect.Interceptor.

type Option

type Option interface {
	// contains filtered or unexported methods
}

An Option configures an Interceptor.

func WithValidator

func WithValidator(validator *protovalidate.Validator) Option

WithValidator configures the Interceptor to use a customized protovalidate.Validator. See protovalidate.ValidatorOption for the range of available customizations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL