Implementing Request Validation

1. Using protoc-gen-validate Plugin

ToolStatus
protoc-gen-validate (PGV) LEGACYOriginal; per-language code
protovalidate MODERNCEL-based, runtime, polyglot
Installgo 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];
}
RuleExample
Stringmin_len, max_len, pattern, email, uri
Numericgt, gte, lt, lte, in, not_in
Repeatedmin_items, max_items, unique
Messagerequired, skip
CELcel: [{id, expression, message}]

3. Generating Validation Code

ApproachDetail
PGVGenerates Validate() method per message
protovalidateNo 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

OutputUse
ValidationErrorContains Violations with field path + constraint
Map to errdetailsBadRequest_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

OptionEffect
(buf.validate.field).required = trueNested message must be set
DefaultNested validated recursively if present
(buf.validate.field).skip = trueSkip validation for this field

8. Validating Repeated Fields

RuleDetail
repeated.min_items / max_itemsBound list size
repeated.uniqueNo duplicates (scalars)
repeated.itemsPer-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

OptionDetail
Per-rule messagecel: { message: "..." }
i18nUse LocalizedMessage in error details