connectrpc.com/validate provides Connect interceptors that
take the tedium out of data validation. Rather than hand-writing repetitive
documentation and code — verifying 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.
go get connectrpc.com/validateCurious 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 argument:
package main
import (
"log"
"net/http"
"connectrpc.com/connect/v2"
"connectrpc.com/connect/v2/connecthttp"
"connectrpc.com/validate"
"connectrpc.com/validate/internal/gen/example/user/v1/userv1connect"
)
func main() {
server := connect.NewServer(validate.NewServerInterceptor())
userv1connect.RegisterUserServiceHandler(server, &userv1connect.UnimplementedUserServiceHandler{})
mux := http.NewServeMux()
connecthttp.Mount(mux, server)
if err := http.ListenAndServe("localhost:8080", mux); err != nil {
log.Fatalf("listen failed: %v", err)
}
}With the interceptor applied, our UserService implementation can assume that
all requests (and optionally responses) have already been validated — no
need for hand-written boilerplate!
Yes: NewClientInterceptor builds an interceptor that validates request
messages before sending them to the server, and optionally responses when they
are received. But unless you're sure that your clients always have an
up-to-date schema, it's better to let the server handle validation.
If the 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.
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.
By default, on both clients and servers, the interceptors only validate requests.
If you'd additionally like to validate responses, use the WithValidateResponses
option when constructing the interceptor.
- connect-go: the Connect runtime
- protovalidate-go: the underlying Protobuf validation library
- protovalidate: schemas and documentation for the constraint language
- CEL: the Common Expression Language
This module is unstable. Expect breaking changes as we iterate toward a stable release.
It supports:
- The two most recent major releases of Go. Keep in mind that only the last two releases receive security patches.
- APIv2 of Protocol Buffers in Go (
google.golang.org/protobuf).
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.
Offered under the Apache 2 license.