Implementing Request Validation
1. Using protoc-gen-validate Plugin
| Tool | Status |
|---|---|
| protoc-gen-validate (PGV) LEGACY | Original; per-language code |
| protovalidate MODERN | CEL-based, runtime, polyglot |
| Install | go install github.com/bufbuild/protovalidate-go@latest |
2. Defining Validation Rules
Example: protovalidate rules
import "buf/validate/validate.proto";
message CreateUserRequest {
string email = 1 [(buf.validate.field).string.email = true];
int32 age = 2 [(buf.validate.field).int32 = {gte: 0, lte: 150}];
string name = 3 [(buf.validate.field).string = {min_len: 1, max_len: 100}];
repeated string tags = 4 [(buf.validate.field).repeated.max_items = 10];
}
| Rule | Example |
|---|---|
| String | min_len, max_len, pattern, email, uri |
| Numeric | gt, gte, lt, lte, in, not_in |
| Repeated | min_items, max_items, unique |
| Message | required, skip |
| CEL | cel: [{id, expression, message}] |
3. Generating Validation Code
| Approach | Detail |
|---|---|
| PGV | Generates Validate() method per message |
| protovalidate | No codegen — runtime evaluator over descriptors |
4. Calling Validate Method
Example: Runtime validation (Go)
v, _ := protovalidate.New()
if err := v.Validate(req); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
5. Handling Validation Errors
| Output | Use |
|---|---|
ValidationError | Contains Violations with field path + constraint |
| Map to errdetails | BadRequest_FieldViolation |
6. Implementing Custom Validators
Example: CEL expression
message Range {
int32 start = 1;
int32 end = 2;
option (buf.validate.message).cel = {
id: "range.order",
expression: "this.start <= this.end",
message: "start must be ≤ end",
};
}
7. Validating Nested Messages
| Option | Effect |
|---|---|
(buf.validate.field).required = true | Nested message must be set |
| Default | Nested validated recursively if present |
(buf.validate.field).skip = true | Skip validation for this field |
8. Validating Repeated Fields
| Rule | Detail |
|---|---|
repeated.min_items / max_items | Bound list size |
repeated.unique | No duplicates (scalars) |
repeated.items | Per-element rules |
9. Using Validation Interceptor
Example: Auto-validate interceptor
v, _ := protovalidate.New()
intc := func(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
if msg, ok := req.(proto.Message); ok {
if err := v.Validate(msg); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
}
return handler(ctx, req)
}
srv := grpc.NewServer(grpc.UnaryInterceptor(intc))
10. Setting Custom Error Messages
| Option | Detail |
|---|---|
| Per-rule message | cel: { message: "..." } |
| i18n | Use LocalizedMessage in error details |