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
Examples
Framework Integrations
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?"