https://github.com/envoyproxy/protoc-gen-validate提供了字段检验的功能
还有其它方案:https://www.yuque.com/jw-go/mqm3eb/gy8npv
安装
- 下载该项目下的validate/validate.proto,与常用的proto文件放一起
- go get github.com/envoyproxy/protoc-gen-validate
使用
syntax = "proto3";
package examplepb;
import "validate/validate.proto";
message Person {
uint64 id = 1 [(validate.rules).uint64.gt = 999];
string email = 2 [(validate.rules).string.email = true];
string name = 3 [(validate.rules).string = {
pattern: "^[^[0-9]A-Za-z]+( [^[0-9]A-Za-z]+)*$",
max_bytes: 256,
}];
Location home = 4 [(validate.rules).message.required = true];
message Location {
double lat = 1 [(validate.rules).double = { gte: -90, lte: 90 }];
double lng = 2 [(validate.rules).double = { gte: -180, lte: 180 }];
}
}
生成pb.go 与validate.go
- protoc —proto_path=/Users/lx/gopath/src —proto_path=. \
—go_out=plugins=grpc,paths=source_relative:. \
—validate_out=”lang=go,paths=source_relative:.” \
test.proto
p := new(Person)
err := p.Validate() // err: Id must be greater than 999
p.Id = 1000
err = p.Validate() // err: Email must be a valid email address
p.Email = "example@lyft.com"
err = p.Validate() // err: Name must match pattern '^[^\d\s]+( [^\d\s]+)*$'
p.Name = "Protocol Buffer"
err = p.Validate() // err: Home is required
p.Home = &Location{37.7, 999}
err = p.Validate() // err: Home.Lng must be within [-180, 180]
p.Home.Lng = -122.4
err = p.Validate() // err: nil
Numerics
All numeric types (float
, double
, int32
, int64
, uint32
, uint64
, sint32
, sint64
, fixed32
, fixed64
, sfixed32
, sfixed64
) share the same rules
const =
lt <
lte <=
gt >
gte >=
// x must equal 1.23 exactly
float x = 1 [(validate.rules).float.const = 1.23];
// x must be less than 10
int32 x = 1 [(validate.rules).int32.lt = 10];
// x must be greater than or equal to 20
uint64 x = 1 [(validate.rules).uint64.gte = 20];
// x must be in the range [30, 40)
fixed32 x = 1 [(validate.rules).fixed32 = {gte:30, lt: 40}];
// in/not_in
// x must be either 1, 2, or 3
uint32 x = 1 [(validate.rules).uint32 = {in: [1,2,3]}];
// x cannot be 0 nor 0.99
float x = 1 [(validate.rules).float = {not_in: [0, 0.99]}];
Bools
// x must be set to true
bool x = 1 [(validate.rules).bool.const = true];
// x cannot be set to true
bool x = 1 [(validate.rules).bool.const = false];
Strings
// x must be set to "foo"
string x = 1 [(validate.rules).string.const = "foo"];
// x must be exactly 5 characters long
string x = 1 [(validate.rules).string.len = 5];
// x must be at least 3 characters long
string x = 1 [(validate.rules).string.min_len = 3];
// x must be between 5 and 10 characters, inclusive
string x = 1 [(validate.rules).string = {min_len: 5, max_len: 10}];
// x must be at most 15 bytes long
string x = 1 [(validate.rules).string.max_bytes = 15];
// x must be between 128 and 1024 bytes long
string x = 1 [(validate.rules).string = {min_bytes: 128, max_bytes: 1024}];
// x must be a non-empty, case-insensitive hexadecimal string
string x = 1 [(validate.rules).string.pattern = "(?i)^[0-9a-f]+$"];
// x must begin with "foo"
string x = 1 [(validate.rules).string.prefix = "foo"];
// x must end with "bar"
string x = 1 [(validate.rules).string.suffix = "bar"];
// x must contain "baz" anywhere inside it
string x = 1 [(validate.rules).string.contains = "baz"];
// x cannot contain "baz" anywhere inside it
string x = 1 [(validate.rules).string.not_contains = "baz"];
// x must begin with "fizz" and end with "buzz"
string x = 1 [(validate.rules).string = {prefix: "fizz", suffix: "buzz"}];
// x must end with ".proto" and be less than 64 characters
string x = 1 [(validate.rules).string = {suffix: ".proto", max_len:64}];
// x must be either "foo", "bar", or "baz"
string x = 1 [(validate.rules).string = {in: ["foo", "bar", "baz"]}];
// x cannot be "fizz" nor "buzz"
string x = 1 [(validate.rules).string = {not_in: ["fizz", "buzz"]}];
// x must be a valid email address (via RFC 1034)
string x = 1 [(validate.rules).string.email = true];
// x must be a valid address (IP or Hostname).
string x = 1 [(validate.rules).string.address = true];
// x must be a valid hostname (via RFC 1034)
string x = 1 [(validate.rules).string.hostname = true];
// x must be a valid IP address (either v4 or v6)
string x = 1 [(validate.rules).string.ip = true];
// x must be a valid IPv4 address
// eg: "192.168.0.1"
string x = 1 [(validate.rules).string.ipv4 = true];
// x must be a valid IPv6 address
// eg: "fe80::3"
string x = 1 [(validate.rules).string.ipv6 = true];
// x must be a valid absolute URI (via RFC 3986)
string x = 1 [(validate.rules).string.uri = true];
// x must be a valid URI reference (either absolute or relative)
string x = 1 [(validate.rules).string.uri_ref = true];
// x must be a valid UUID (via RFC 4122)
string x = 1 [(validate.rules).string.uuid = true];
// x must conform to a well known regex for HTTP header names (via RFC 7230)
string x = 1 [(validate.rules).string.well_known_regex = HTTP_HEADER_NAME]
// x must conform to a well known regex for HTTP header values (via RFC 7230)
string x = 1 [(validate.rules).string.well_known_regex = HTTP_HEADER_VALUE];
// x must conform to a well known regex for headers, disallowing \r\n\0 characters.
string x = 1 [(validate.rules).string {well_known_regex: HTTP_HEADER_VALUE, strict: false}];
Bytes
// x must be set to "foo" ("\x66\x6f\x6f")
bytes x = 1 [(validate.rules).bytes.const = "foo"];
// x must be set to "\xf0\x90\x28\xbc"
bytes x = 1 [(validate.rules).bytes.const = "\xf0\x90\x28\xbc"];
// x must be exactly 3 bytes
bytes x = 1 [(validate.rules).bytes.len = 3];
// x must be at least 3 bytes long
bytes x = 1 [(validate.rules).bytes.min_len = 3];
// x must be between 5 and 10 bytes, inclusive
bytes x = 1 [(validate.rules).bytes = {min_len: 5, max_len: 10}];
// x must be a non-empty, ASCII byte sequence
bytes x = 1 [(validate.rules).bytes.pattern = "^[\x00-\x7F]+$"];
// x must begin with "\x99"
bytes x = 1 [(validate.rules).bytes.prefix = "\x99"];
// x must end with "buz\x7a"
bytes x = 1 [(validate.rules).bytes.suffix = "buz\x7a"];
// x must contain "baz" anywhere inside it
bytes x = 1 [(validate.rules).bytes.contains = "baz"];
// x must be either "foo", "bar", or "baz"
bytes x = 1 [(validate.rules).bytes = {in: ["foo", "bar", "baz"]}];
// x cannot be "fizz" nor "buzz"
bytes x = 1 [(validate.rules).bytes = {not_in: ["fizz", "buzz"]}];
// x must be a valid IP address (either v4 or v6) in byte format
bytes x = 1 [(validate.rules).bytes.ip = true];
// x must be a valid IPv4 address in byte format
// eg: "\xC0\xA8\x00\x01"
bytes x = 1 [(validate.rules).bytes.ipv4 = true];
// x must be a valid IPv6 address in byte format
// eg: "\x20\x01\x0D\xB8\x85\xA3\x00\x00\x00\x00\x8A\x2E\x03\x70\x73\x34"
bytes x = 1 [(validate.rules).bytes.ipv6 = true];
Enums
All literal values should use the numeric (int32) value as defined in the enum descriptor.
enum State {
INACTIVE = 0;
PENDING = 1;
ACTIVE = 2;
}
// x must be set to ACTIVE (2)
State x = 1 [(validate.rules).enum.const = 2];
// x can only be INACTIVE, PENDING, or ACTIVE
State x = 1 [(validate.rules).enum.defined_only = true];
// x must be either INACTIVE (0) or ACTIVE (2)
State x = 1 [(validate.rules).enum = {in: [0,2]}];
// x cannot be PENDING (1)
State x = 1 [(validate.rules).enum = {not_in: [1]}];
Messages
If a field contains a message and the message has been generated with PGV, validation will be performed recursively. Message’s not generated with PGV are skipped
一般来讲,pb.go这个存根文件不会由PGV生成
// if Person was generated with PGV and x is set,
// x's fields will be validated.
Person x = 1;
// The fields on Person x will not be validated.
Person x = 1 [(validate.rules).message.skip = true];
// x cannot be unset
Person x = 1 [(validate.rules).message.required = true];
// x cannot be unset, but the validations on x will not be performed
Person x = 1 [(validate.rules).message = {required: true, skip: true}];
Repeated
// x must contain at least 3 elements
repeated int32 x = 1 [(validate.rules).repeated.min_items = 3];
// x must contain between 5 and 10 Persons, inclusive
repeated Person x = 1 [(validate.rules).repeated = {min_items: 5, max_items: 10}];
// x must contain exactly 7 elements
repeated double x = 1 [(validate.rules).repeated = {min_items: 7, max_items: 7}];
// x must contain unique int64 values
repeated int64 x = 1 [(validate.rules).repeated.unique = true];
items: 该规则指定了应该应用于字段中的每个元素的约束。除非在此约束上指定了skip,否则重复的消息字段也会
应用其验证规则
// x must contain positive float values
repeated float x = 1 [(validate.rules).repeated.items.float.gt = 0];
// x must contain Persons but don't validate them
repeated Person x = 1 [(validate.rules).repeated.items.message.skip = true];
Maps
// x must contain at most 3 KV pairs
map<string, uint64> x = 1 [(validate.rules).map.min_pairs = 3];
// x must contain between 5 and 10 KV pairs
map<string, string> x = 1 [(validate.rules)].map = {min_pairs: 5, max_pairs: 10}];
// x must contain exactly 7 KV pairs
map<string, Person> x = 1 [(validate.rules)].map = {min_pairs: 7, max_pairs: 7}];
// all values in x must be set
map<uint64, Person> x = 1 [(validate.rules).map.no_sparse = true];
// keys : 该规则指定了应用于字段中的键的约束
<sint32, string> x = [(validate.rules).map.keys.sint32.lt = 0];
// x must contain strings of at least 3 characters
map<string, string> x = 1 [(validate.rules).map.values.string.min_len = 3];
// x must contain Persons but doesn't validate them
map<string, Person> x = 1 [(validate.rules).map.values.message.skip = true];
Message-Global
disabled:消息上字段的所有验证规则都可以作废,包括支持验证本身的任何消息字段
message Person {
option (validate.disabled) = true;
// x will not be required to be greater than 123
uint64 x = 1 [(validate.rules).uint64.gt = 123];
// y's fields will not be validated
Person y = 2;
}
ignored:不要为此消息生成验证方法或任何相关的验证代码
message Person {
option (validate.ignored) = true;
// x will not be required to be greater than 123
uint64 x = 1 [(validate.rules).uint64.gt = 123];
// y's fields will not be validated
Person y = 2;
}
OneOfs
oneof id {
// either x, y, or z must be set.
option (validate.required) = true;
string x = 1;
int32 y = 2;
Person z = 3;
}
第三方类型
Anys
// x cannot be unset
google.protobuf.Any x = 1 [(validate.rules).any.required = true];
// x must not be the Duration or Timestamp WKT
google.protobuf.Any x = 1 [(validate.rules).any = {not_in: [
"type.googleapis.com/google.protobuf.Duration",
"type.googleapis.com/google.protobuf.Timestamp"
]}];
Durations
// x cannot be unset
google.protobuf.Duration x = 1 [(validate.rules).duration.required = true];
// x must equal 1.5s exactly
google.protobuf.Duration x = 1 [(validate.rules).duration.const = {
seconds: 1,
nanos: 500000000
}];
// x must be less than 10s
google.protobuf.Duration x = 1 [(validate.rules).duration.lt.seconds = 10];
// x must be greater than or equal to 20ns
google.protobuf.Duration x = 1 [(validate.rules).duration.gte.nanos = 20];
// x must be in the range [0s, 1s)
google.protobuf.Duration x = 1 [(validate.rules).duration = {
gte: {},
lt: {seconds: 1}
}];
// 将lt(e)和gt(e)的值反向是有效的,并创建一个排他范围
// x must be outside the range [0s, 1s)
google.protobuf.Duration x = 1 [(validate.rules).duration = {
lt: {},
gte: {seconds: 1}
}];
// x must be either 0s or 1s
google.protobuf.Duration x = 1 [(validate.rules).duration = {in: [
{},
{seconds: 1}
]}];
// x cannot be 20s nor 500ns
google.protobuf.Duration x = 1 [(validate.rules).duration = {not_in: [
{seconds: 20},
{nanos: 500}
]}];
Timestamps
// x cannot be unset
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.required = true];
// x must equal 2009/11/10T23:00:00.500Z exactly
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.const = {
seconds: 63393490800,
nanos: 500000000
}];
// x must be less than the Unix Epoch
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt.seconds = 0];
// x must be greater than or equal to 2009/11/10T23:00:00Z
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.gte.seconds = 63393490800];
// x must be in the range [epoch, 2009/11/10T23:00:00Z)
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
gte: {},
lt: {seconds: 63393490800}
}];
//将lt(e)和gt(e)的值反向是有效的,并创建一个排他范围
// x must be outside the range [epoch, 2009/11/10T23:00:00Z)
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
lt: {},
gte: {seconds: 63393490800}
}];
//lt_now/gt_now: these inequalities allow for ranges relative to the current time
// x must be less than the current timestamp
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt_now = true];
// x must be less than the current timestamp
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.gt_now = true];
// within:指定该字段的值应该在当前时间的持续时间内。该规则可以与lt_now和gt_now一起使用来控制这些范围
// x must be within ±1s of the current time
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.within.seconds = 1];
// x must be within the range (now, now+1h)
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
gt_now: true,
within: {seconds: 3600}
}];