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 999p.Id = 1000err = p.Validate() // err: Email must be a valid email addressp.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 requiredp.Home = &Location{37.7, 999}err = p.Validate() // err: Home.Lng must be within [-180, 180]p.Home.Lng = -122.4err = 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 exactlyfloat x = 1 [(validate.rules).float.const = 1.23];// x must be less than 10int32 x = 1 [(validate.rules).int32.lt = 10];// x must be greater than or equal to 20uint64 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 3uint32 x = 1 [(validate.rules).uint32 = {in: [1,2,3]}];// x cannot be 0 nor 0.99float x = 1 [(validate.rules).float = {not_in: [0, 0.99]}];
Bools
// x must be set to truebool x = 1 [(validate.rules).bool.const = true];// x cannot be set to truebool 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 longstring x = 1 [(validate.rules).string.len = 5];// x must be at least 3 characters longstring x = 1 [(validate.rules).string.min_len = 3];// x must be between 5 and 10 characters, inclusivestring x = 1 [(validate.rules).string = {min_len: 5, max_len: 10}];// x must be at most 15 bytes longstring x = 1 [(validate.rules).string.max_bytes = 15];// x must be between 128 and 1024 bytes longstring x = 1 [(validate.rules).string = {min_bytes: 128, max_bytes: 1024}];// x must be a non-empty, case-insensitive hexadecimal stringstring 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 itstring x = 1 [(validate.rules).string.contains = "baz"];// x cannot contain "baz" anywhere inside itstring 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 charactersstring 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 bytesbytes x = 1 [(validate.rules).bytes.len = 3];// x must be at least 3 bytes longbytes x = 1 [(validate.rules).bytes.min_len = 3];// x must be between 5 and 10 bytes, inclusivebytes x = 1 [(validate.rules).bytes = {min_len: 5, max_len: 10}];// x must be a non-empty, ASCII byte sequencebytes 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 itbytes 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 formatbytes 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 ACTIVEState 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 unsetPerson x = 1 [(validate.rules).message.required = true];// x cannot be unset, but the validations on x will not be performedPerson x = 1 [(validate.rules).message = {required: true, skip: true}];
Repeated
// x must contain at least 3 elementsrepeated int32 x = 1 [(validate.rules).repeated.min_items = 3];// x must contain between 5 and 10 Persons, inclusiverepeated Person x = 1 [(validate.rules).repeated = {min_items: 5, max_items: 10}];// x must contain exactly 7 elementsrepeated double x = 1 [(validate.rules).repeated = {min_items: 7, max_items: 7}];// x must contain unique int64 valuesrepeated int64 x = 1 [(validate.rules).repeated.unique = true];items: 该规则指定了应该应用于字段中的每个元素的约束。除非在此约束上指定了skip,否则重复的消息字段也会应用其验证规则// x must contain positive float valuesrepeated float x = 1 [(validate.rules).repeated.items.float.gt = 0];// x must contain Persons but don't validate themrepeated Person x = 1 [(validate.rules).repeated.items.message.skip = true];
Maps
// x must contain at most 3 KV pairsmap<string, uint64> x = 1 [(validate.rules).map.min_pairs = 3];// x must contain between 5 and 10 KV pairsmap<string, string> x = 1 [(validate.rules)].map = {min_pairs: 5, max_pairs: 10}];// x must contain exactly 7 KV pairsmap<string, Person> x = 1 [(validate.rules)].map = {min_pairs: 7, max_pairs: 7}];// all values in x must be setmap<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 charactersmap<string, string> x = 1 [(validate.rules).map.values.string.min_len = 3];// x must contain Persons but doesn't validate themmap<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 123uint64 x = 1 [(validate.rules).uint64.gt = 123];// y's fields will not be validatedPerson y = 2;}ignored:不要为此消息生成验证方法或任何相关的验证代码message Person {option (validate.ignored) = true;// x will not be required to be greater than 123uint64 x = 1 [(validate.rules).uint64.gt = 123];// y's fields will not be validatedPerson 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 unsetgoogle.protobuf.Any x = 1 [(validate.rules).any.required = true];// x must not be the Duration or Timestamp WKTgoogle.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 unsetgoogle.protobuf.Duration x = 1 [(validate.rules).duration.required = true];// x must equal 1.5s exactlygoogle.protobuf.Duration x = 1 [(validate.rules).duration.const = {seconds: 1,nanos: 500000000}];// x must be less than 10sgoogle.protobuf.Duration x = 1 [(validate.rules).duration.lt.seconds = 10];// x must be greater than or equal to 20nsgoogle.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 1sgoogle.protobuf.Duration x = 1 [(validate.rules).duration = {in: [{},{seconds: 1}]}];// x cannot be 20s nor 500nsgoogle.protobuf.Duration x = 1 [(validate.rules).duration = {not_in: [{seconds: 20},{nanos: 500}]}];
Timestamps
// x cannot be unsetgoogle.protobuf.Timestamp x = 1 [(validate.rules).timestamp.required = true];// x must equal 2009/11/10T23:00:00.500Z exactlygoogle.protobuf.Timestamp x = 1 [(validate.rules).timestamp.const = {seconds: 63393490800,nanos: 500000000}];// x must be less than the Unix Epochgoogle.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt.seconds = 0];// x must be greater than or equal to 2009/11/10T23:00:00Zgoogle.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 timestampgoogle.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt_now = true];// x must be less than the current timestampgoogle.protobuf.Timestamp x = 1 [(validate.rules).timestamp.gt_now = true];// within:指定该字段的值应该在当前时间的持续时间内。该规则可以与lt_now和gt_now一起使用来控制这些范围// x must be within ±1s of the current timegoogle.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}}];
