Protovalidate Skills for AI Agents
This skill provides comprehensive knowledge about protovalidate, the next-generation Protocol Buffer validation library from Buf. It replaces the deprecated protoc-gen-validate.
Target When
Invoke this skill when:
- Defining proto validation rules: Adding constraints to
.proto files
- Custom validation: Writing CEL expressions for complex validation logic
- Error handling: Implementing custom error messages
- Framework integration: Using protovalidate with Kratos, go-zero, or other frameworks
- Migration: Upgrading from protoc-gen-validate to protovalidate
- Debugging: Troubleshooting validation errors
Quick Reference
Basic Syntax
syntax = "proto3";
package my.package;
import "buf/validate/validate.proto";
message User {
// String validation
string username = 1 [(buf.validate.field).string = {
min_len: 3,
max_len: 32,
pattern: "^[a-zA-Z0-9_]+$"
}];
// Integer validation
int64 age = 2 [(buf.validate.field).int64 = {gte: 0, lte: 150}];
// Email validation
string email = 3 [(buf.validate.field).string.email = true];
// Custom CEL validation with Chinese error message
string mobile = 4 [(buf.validate.field).cel = {
id: "mobile_format",
message: "手机号格式错误",
expression: "this.matches('^1[3-9][0-9]{9}$')"
}];
}
Import Path
// Buf CLI managed mode
import "buf/validate/validate.proto";
// Option extension
option (buf.validate.field) = { ... };
option (buf.validate.message) = { ... };
option (buf.validate.oneof) = { ... };
Knowledge Structure
Load specific guides as needed:
Field Type Rules
| File |
When to Load |
| references/string-rules.md |
String validation (length, pattern, email, IP, UUID, etc.) |
| references/number-rules.md |
Numeric validation (int32, int64, float, double, etc.) |
| references/complex-rules.md |
Complex types (repeated, map, enum, bytes, etc.) |
| references/message-rules.md |
Message-level validation, oneof rules |
CEL & Custom Rules
| File |
When to Load |
| references/cel-expressions.md |
CEL syntax, functions, cross-field validation |
| references/custom-errors.md |
Custom error messages, localization |
Examples
| File |
When to Load |
| examples/common-patterns.md |
Common validation patterns |
| examples/chinese-validation.md |
Chinese-specific validation (mobile, ID card, etc.) |
Framework Integrations
| File |
When to Load |
| integrations/kratos.md |
Go-Kratos framework integration |
Other framework integrations (go-zero, Spring Boot, etc.) can be added to integrations/ directory.
Field Types Overview
Scalar Types
| Proto Type |
Rules Type |
Example |
string |
StringRules |
[(buf.validate.field).string.min_len = 1] |
int32 |
Int32Rules |
[(buf.validate.field).int32.gt = 0] |
int64 |
Int64Rules |
[(buf.validate.field).int64.gte = 1] |
uint32 |
UInt32Rules |
[(buf.validate.field).uint32.lte = 100] |
uint64 |
UInt64Rules |
[(buf.validate.field).uint64.gt = 0] |
float |
FloatRules |
[(buf.validate.field).float.gte = 0.0] |
double |
DoubleRules |
[(buf.validate.field).double.lt = 100.0] |
bool |
BoolRules |
[(buf.validate.field).bool.const = true] |
bytes |
BytesRules |
[(buf.validate.field).bytes.min_len = 1] |
Complex Types
| Proto Type |
Rules Type |
Example |
enum |
EnumRules |
[(buf.validate.field).enum.defined_only = true] |
repeated |
RepeatedRules |
[(buf.validate.field).repeated.min_items = 1] |
map |
MapRules |
[(buf.validate.field).map.min_pairs = 1] |
message |
MessageRules |
[(buf.validate.field).required = true] |
Common Validation Rules
String Rules
| Rule |
Description |
Example |
min_len |
Minimum character count (Unicode) |
min_len: 3 |
max_len |
Maximum character count (Unicode) |
max_len: 32 |
len |
Exact character count |
len: 11 |
min_bytes |
Minimum byte count |
min_bytes: 1 |
max_bytes |
Maximum byte count |
max_bytes: 255 |
pattern |
RE2 regex pattern |
pattern: "^[a-z]+$" |
prefix |
Must start with |
prefix: "https://" |
suffix |
Must end with |
suffix: ".com" |
contains |
Must contain substring |
contains: "@" |
not_contains |
Must not contain |
not_contains: "spam" |
in |
Must be one of values |
in: ["a", "b", "c"] |
not_in |
Must not be one of |
not_in: ["admin", "root"] |
email |
Valid email format |
email: true |
hostname |
Valid hostname |
hostname: true |
ip |
Valid IP (v4/v6) |
ip: true |
ipv4 |
Valid IPv4 only |
ipv4: true |
ipv6 |
Valid IPv6 only |
ipv6: true |
uri |
Valid URI |
uri: true |
uuid |
Valid UUID |
uuid: true |
const |
Must equal exactly |
const: "fixed" |
Numeric Rules
| Rule |
Description |
Example |
const |
Must equal exactly |
const: 42 |
lt |
Less than (exclusive) |
lt: 100 |
lte |
Less than or equal |
lte: 100 |
gt |
Greater than (exclusive) |
gt: 0 |
gte |
Greater than or equal |
gte: 1 |
in |
Must be one of values |
in: [1, 2, 3] |
not_in |
Must not be one of |
not_in: [0, -1] |
Float/Double Only
| Rule |
Description |
Example |
finite |
Must be finite (not NaN/Inf) |
finite: true |
Ignore Options
// Ignore if field is unset or zero value
string optional_field = 1 [
(buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE,
(buf.validate.field).string.email = true
];
// Always ignore validation (useful for development)
string dev_field = 2 [
(buf.validate.field).ignore = IGNORE_ALWAYS
];
Required Fields
message Request {
// For fields with presence tracking (optional, message types)
optional string name = 1 [(buf.validate.field).required = true];
// Message type - required means must be set
User user = 2 [(buf.validate.field).required = true];
}
CEL Expressions
Basic CEL Syntax
message Example {
// Field-level CEL
int32 value = 1 [(buf.validate.field).cel = {
id: "value_positive",
message: "value must be positive",
expression: "this > 0"
}];
// Simplified CEL (id and message derived from expression)
int32 value2 = 2 [(buf.validate.field).cel_expression = "this > 0"];
// Message-level CEL for cross-field validation
option (buf.validate.message).cel = {
id: "password_match",
message: "passwords must match",
expression: "this.password == this.confirm_password"
};
string password = 1;
string confirm_password = 2;
}
CEL Functions
| Function |
Description |
Example |
size() |
String/repeated/map size |
size(this) <= 32 |
matches() |
Regex match (RE2) |
this.matches('^[a-z]+$') |
startsWith() |
Prefix check |
this.startsWith('http') |
endsWith() |
Suffix check |
this.endsWith('.com') |
contains() |
Substring check |
this.contains('@') |
isEmail() |
Email validation |
this.isEmail() |
isHostname() |
Hostname validation |
this.isHostname() |
isIp() |
IP validation |
this.isIp() |
isIpv4() |
IPv4 validation |
this.isIpv4() |
isIpv6() |
IPv6 validation |
this.isIpv6() |
isUri() |
URI validation |
this.isUri() |
isUuid() |
UUID validation |
this.isUuid() |
has() |
Field presence check |
has(this.field) |
getType() |
Get field type |
getType(this) |
CEL for Chinese Validation
// Chinese mobile phone
string mobile = 1 [(buf.validate.field).cel = {
id: "mobile_format",
message: "手机号格式错误,必须为11位有效手机号",
expression: "this.matches('^1[3-9][0-9]{9}$')"
}];
// Chinese ID card (18 digits)
string id_card = 2 [(buf.validate.field).cel = {
id: "id_card_format",
message: "身份证号格式错误",
expression: "this.matches('^[1-9][0-9]{5}(19|20)[0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])[0-9]{3}[0-9Xx]$')"
}];
Best Practices
Always Follow
- Use
buf.validate.field for field-level rules
- Use
buf.validate.message for cross-field validation
- Provide meaningful
id and message for CEL rules
- Use
IGNORE_IF_ZERO_VALUE for optional fields
- Test validation rules with edge cases
Never Do
- Use
protoc-gen-validate syntax (deprecated)
- Mix
buf.validate with validate imports
- Use
\d in regex patterns (use [0-9] instead)
- Reference other fields in field-level CEL (use message-level instead)
- Modify generated
.pb.go files
Framework Integration
Kratos Middleware
// pkg/middleware/validate.go
package middleware
import (
"context"
stderrors "errors"
"buf.build/go/protovalidate"
"github.com/go-kratos/kratos/v2/middleware"
"google.golang.org/protobuf/proto"
v1 "your-project/api/v1"
)
var validator protovalidate.Validator
func init() {
var err error
validator, err = protovalidate.New()
if err != nil {
panic(err)
}
}
func Validator() middleware.Middleware {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
if msg, ok := req.(proto.Message); ok {
if err := validator.Validate(msg); err != nil {
var valErr *protovalidate.ValidationError
if stderrors.As(err, &valErr) {
violations := valErr.Violations
if len(violations) > 0 && violations[0].Proto.GetMessage() != "" {
return nil, v1.ErrorBadRequest(violations[0].Proto.GetMessage())
}
}
return nil, v1.ErrorBadRequest(err.Error())
}
}
return handler(ctx, req)
}
}
}
buf.gen.yaml Configuration
# v2 format
version: v2
managed:
enabled: true
disable:
- file_option: go_package_prefix
module: buf.build/bufbuild/protovalidate
plugins:
- local: protoc-gen-go
out: .
opt:
- paths=source_relative
- local: protoc-gen-go-grpc
out: .
opt:
- paths=source_relative
- local: protoc-gen-go-http
out: .
opt:
- paths=source_relative
Resources
Quick invocation: Use /protovalidate-skills or ask "How do I validate [field type] with protovalidate?"