From 4754c5654ace1c1c1afc3cc4a9c4009d46ed6043 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Fri, 7 Dec 2018 03:57:11 +0100 Subject: [PATCH 01/17] all: Remove references to mwitkow --- Makefile | 5 +- examples/nested.pb.go | 148 ++++++++++++++++++++---------- examples/nested.proto | 4 +- examples/nested.validator.pb.go | 31 ++----- plugin/plugin.go | 4 +- protoc-gen-govalidators/main.go | 2 +- test/validator_proto2.proto | 2 +- test/validator_proto3.proto | 2 +- test/validator_proto3_map.proto | 2 +- test/validator_proto3_oneof.proto | 4 +- validator.pb.go | 55 ++++++----- 11 files changed, 159 insertions(+), 100 deletions(-) diff --git a/Makefile b/Makefile index ff1bc0d..f437b81 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,12 @@ export PATH := ${GOPATH}/bin:${PATH} +init: + go get -u "github.com/gogo/protobuf/protoc-gen-gogo" + install: @echo "--- Installing govalidators to GOPATH" - go install github.com/mwitkow/go-proto-validators/protoc-gen-govalidators + go install github.com/TheTHingsIndustries/go-proto-validators/protoc-gen-govalidators regenerate_test_gogo: @echo "Regenerating test .proto files with gogo imports" diff --git a/examples/nested.pb.go b/examples/nested.pb.go index 96bc418..8a6fe35 100644 --- a/examples/nested.pb.go +++ b/examples/nested.pb.go @@ -1,23 +1,14 @@ -// Code generated by protoc-gen-go. +// Code generated by protoc-gen-go. DO NOT EDIT. // source: examples/nested.proto -// DO NOT EDIT! -/* -Package validator_examples is a generated protocol buffer package. - -It is generated from these files: - examples/nested.proto - -It has these top-level messages: - InnerMessage - OuterMessage -*/ package validator_examples -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" -import _ "github.com/mwitkow/go-proto-validators" +import ( + fmt "fmt" + _ "github.com/TheThingsIndustries/go-proto-validators" + proto "github.com/golang/protobuf/proto" + math "math" +) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal @@ -26,29 +17,91 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type InnerMessage struct { // some_integer can only be in range (1, 100). - SomeInteger int32 `protobuf:"varint,1,opt,name=some_integer,json=someInteger" json:"some_integer,omitempty"` + SomeInteger int32 `protobuf:"varint,1,opt,name=some_integer,json=someInteger,proto3" json:"some_integer,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InnerMessage) Reset() { *m = InnerMessage{} } +func (m *InnerMessage) String() string { return proto.CompactTextString(m) } +func (*InnerMessage) ProtoMessage() {} +func (*InnerMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_48ff2d59662eadb1, []int{0} +} + +func (m *InnerMessage) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InnerMessage.Unmarshal(m, b) +} +func (m *InnerMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InnerMessage.Marshal(b, m, deterministic) } +func (m *InnerMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_InnerMessage.Merge(m, src) +} +func (m *InnerMessage) XXX_Size() int { + return xxx_messageInfo_InnerMessage.Size(m) +} +func (m *InnerMessage) XXX_DiscardUnknown() { + xxx_messageInfo_InnerMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_InnerMessage proto.InternalMessageInfo -func (m *InnerMessage) Reset() { *m = InnerMessage{} } -func (m *InnerMessage) String() string { return proto.CompactTextString(m) } -func (*InnerMessage) ProtoMessage() {} -func (*InnerMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (m *InnerMessage) GetSomeInteger() int32 { + if m != nil { + return m.SomeInteger + } + return 0 +} type OuterMessage struct { // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax). - ImportantString string `protobuf:"bytes,1,opt,name=important_string,json=importantString" json:"important_string,omitempty"` + ImportantString string `protobuf:"bytes,1,opt,name=important_string,json=importantString,proto3" json:"important_string,omitempty"` // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage. - Inner *InnerMessage `protobuf:"bytes,2,opt,name=inner" json:"inner,omitempty"` + Inner *InnerMessage `protobuf:"bytes,2,opt,name=inner,proto3" json:"inner,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *OuterMessage) Reset() { *m = OuterMessage{} } -func (m *OuterMessage) String() string { return proto.CompactTextString(m) } -func (*OuterMessage) ProtoMessage() {} -func (*OuterMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +func (m *OuterMessage) Reset() { *m = OuterMessage{} } +func (m *OuterMessage) String() string { return proto.CompactTextString(m) } +func (*OuterMessage) ProtoMessage() {} +func (*OuterMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_48ff2d59662eadb1, []int{1} +} + +func (m *OuterMessage) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OuterMessage.Unmarshal(m, b) +} +func (m *OuterMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OuterMessage.Marshal(b, m, deterministic) +} +func (m *OuterMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_OuterMessage.Merge(m, src) +} +func (m *OuterMessage) XXX_Size() int { + return xxx_messageInfo_OuterMessage.Size(m) +} +func (m *OuterMessage) XXX_DiscardUnknown() { + xxx_messageInfo_OuterMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_OuterMessage proto.InternalMessageInfo + +func (m *OuterMessage) GetImportantString() string { + if m != nil { + return m.ImportantString + } + return "" +} func (m *OuterMessage) GetInner() *InnerMessage { if m != nil { @@ -62,22 +115,25 @@ func init() { proto.RegisterType((*OuterMessage)(nil), "validator.examples.OuterMessage") } -var fileDescriptor0 = []byte{ - // 245 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4d, 0xad, 0x48, 0xcc, - 0x2d, 0xc8, 0x49, 0x2d, 0xd6, 0xcf, 0x4b, 0x2d, 0x2e, 0x49, 0x4d, 0xd1, 0x2b, 0x28, 0xca, 0x2f, - 0xc9, 0x17, 0x12, 0x2a, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, 0x2c, 0xc9, 0x2f, 0xd2, 0x83, 0x29, 0x90, - 0x32, 0x4b, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xcf, 0x2d, 0xcf, 0x2c, - 0xc9, 0xce, 0x2f, 0xd7, 0x4f, 0xcf, 0xd7, 0x05, 0x6b, 0xd0, 0x85, 0xab, 0x2f, 0xd6, 0x47, 0x68, - 0x05, 0x4b, 0x29, 0x59, 0x73, 0xf1, 0x78, 0xe6, 0xe5, 0xa5, 0x16, 0xf9, 0xa6, 0x16, 0x17, 0x27, - 0xa6, 0xa7, 0x0a, 0x69, 0x73, 0xf1, 0x14, 0xe7, 0xe7, 0xa6, 0xc6, 0x67, 0xe6, 0x95, 0xa4, 0xa6, - 0xa7, 0x16, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x3a, 0x71, 0x3c, 0xba, 0x2f, 0xcf, 0x22, 0xc0, - 0x20, 0x91, 0x12, 0xc4, 0x0d, 0x92, 0xf5, 0x84, 0x48, 0x2a, 0xf5, 0x32, 0x72, 0xf1, 0xf8, 0x97, - 0x96, 0x20, 0x74, 0xdb, 0x72, 0x09, 0x64, 0xe6, 0x16, 0xe4, 0x17, 0x95, 0x24, 0xe6, 0x95, 0xc4, - 0x17, 0x97, 0x14, 0x65, 0xe6, 0xa5, 0x83, 0x4d, 0xe0, 0x74, 0x12, 0x02, 0x9a, 0xc0, 0xc7, 0xc5, - 0x13, 0x17, 0x9d, 0xa8, 0x5b, 0x15, 0x5b, 0x6d, 0xa4, 0x63, 0x5a, 0xab, 0x12, 0xc4, 0x0f, 0x57, - 0x1b, 0x0c, 0x56, 0x2a, 0x64, 0xc7, 0xc5, 0x9a, 0x09, 0x72, 0x8c, 0x04, 0x13, 0x50, 0x0f, 0xb7, - 0x91, 0x82, 0x1e, 0xa6, 0x47, 0xf5, 0x90, 0x5d, 0xeb, 0xc4, 0x06, 0x34, 0x15, 0xa8, 0x36, 0x08, - 0xa2, 0x2d, 0x89, 0x0d, 0xec, 0x27, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x08, 0x68, 0xd9, - 0xd5, 0x38, 0x01, 0x00, 0x00, +func init() { proto.RegisterFile("examples/nested.proto", fileDescriptor_48ff2d59662eadb1) } + +var fileDescriptor_48ff2d59662eadb1 = []byte{ + // 259 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x8f, 0x41, 0x4b, 0xc3, 0x30, + 0x1c, 0xc5, 0xed, 0x70, 0x43, 0xb3, 0xa2, 0x23, 0x20, 0x14, 0x2f, 0x2d, 0xc3, 0xc3, 0x40, 0x9b, + 0xc2, 0xc4, 0x93, 0xe8, 0xa1, 0xb7, 0x1e, 0x44, 0xa8, 0xbb, 0x89, 0x8e, 0xcc, 0xfe, 0x49, 0x03, + 0x6b, 0x52, 0x92, 0x7f, 0x45, 0x14, 0x3f, 0x82, 0x9f, 0xb1, 0xd0, 0x4f, 0x22, 0xcd, 0x70, 0x13, + 0x76, 0x7e, 0xef, 0xf7, 0xf8, 0x3d, 0x72, 0x06, 0x1f, 0xbc, 0xaa, 0xd7, 0x60, 0x13, 0x05, 0x16, + 0xa1, 0x60, 0xb5, 0xd1, 0xa8, 0x29, 0x7d, 0xe7, 0x6b, 0x59, 0x70, 0xd4, 0x86, 0xfd, 0x15, 0xce, + 0x53, 0x21, 0xb1, 0x6c, 0x56, 0xec, 0x4d, 0x57, 0xc9, 0xa2, 0x84, 0x45, 0x29, 0x95, 0xb0, 0x99, + 0x2a, 0x1a, 0x8b, 0x46, 0x82, 0x4d, 0x84, 0x8e, 0x1d, 0x1c, 0x6f, 0x59, 0x9b, 0xec, 0x66, 0x5c, + 0x34, 0xbd, 0x25, 0x7e, 0xa6, 0x14, 0x98, 0x07, 0xb0, 0x96, 0x0b, 0xa0, 0x97, 0xc4, 0xb7, 0xba, + 0x82, 0xa5, 0x54, 0x08, 0x02, 0x4c, 0xe0, 0x45, 0xde, 0x6c, 0x98, 0x1e, 0x75, 0x6d, 0x78, 0x38, + 0x39, 0x08, 0x8a, 0x7c, 0xdc, 0xa7, 0xd9, 0x26, 0x9c, 0xfe, 0x78, 0xc4, 0x7f, 0x6c, 0x70, 0x47, + 0xdf, 0x91, 0x89, 0xac, 0x6a, 0x6d, 0x90, 0x2b, 0x5c, 0xf6, 0x16, 0x4a, 0xb8, 0x85, 0xe3, 0x94, + 0x76, 0x6d, 0x78, 0x42, 0xfc, 0xd7, 0x67, 0x1e, 0x7f, 0xbe, 0x7c, 0xcd, 0xaf, 0x6e, 0xbe, 0x2f, + 0xf2, 0xd3, 0x6d, 0xf7, 0xc9, 0x55, 0xe9, 0x3d, 0x19, 0xca, 0x5e, 0x26, 0x18, 0x44, 0xde, 0x6c, + 0x3c, 0x8f, 0xd8, 0xfe, 0x69, 0xf6, 0xdf, 0x36, 0x1d, 0x75, 0x6d, 0x38, 0x88, 0xbc, 0x7c, 0x83, + 0xad, 0x46, 0xee, 0xd3, 0xf5, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe4, 0x2f, 0x84, 0x14, 0x44, + 0x01, 0x00, 0x00, } diff --git a/examples/nested.proto b/examples/nested.proto index 5689c03..e8a1233 100644 --- a/examples/nested.proto +++ b/examples/nested.proto @@ -1,6 +1,6 @@ syntax = "proto3"; package validator.examples; -import "github.com/mwitkow/go-proto-validators/validator.proto"; +import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; message InnerMessage { // some_integer can only be in range (1, 100). @@ -12,4 +12,4 @@ message OuterMessage { string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}]; // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage. InnerMessage inner = 2 [(validator.field) = {msg_exists : true}]; -} \ No newline at end of file +} diff --git a/examples/nested.validator.pb.go b/examples/nested.validator.pb.go index 3ee7b1b..eac3ac4 100644 --- a/examples/nested.validator.pb.go +++ b/examples/nested.validator.pb.go @@ -1,25 +1,14 @@ -// Code generated by protoc-gen-gogo. +// Code generated by protoc-gen-gogo. DO NOT EDIT. // source: examples/nested.proto -// DO NOT EDIT! -/* -Package validator_examples is a generated protocol buffer package. - -It is generated from these files: - examples/nested.proto - -It has these top-level messages: - InnerMessage - OuterMessage -*/ package validator_examples import regexp "regexp" import fmt "fmt" -import github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" +import github_com_TheThingsIndustries_go_proto_validators "github.com/TheThingsIndustries/go-proto-validators" import proto "github.com/golang/protobuf/proto" import math "math" -import _ "github.com/mwitkow/go-proto-validators" +import _ "github.com/TheThingsIndustries/go-proto-validators" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal @@ -28,26 +17,26 @@ var _ = math.Inf func (this *InnerMessage) Validate() error { if !(this.SomeInteger > 0) { - return fmt.Errorf("validation error: InnerMessage.SomeInteger must be greater than '0'") + return github_com_TheThingsIndustries_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be greater than '0'`, this.SomeInteger)) } if !(this.SomeInteger < 100) { - return fmt.Errorf("validation error: InnerMessage.SomeInteger must be less than '100'") + return github_com_TheThingsIndustries_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be less than '100'`, this.SomeInteger)) } return nil } -var _regex_OuterMessage_ImportantString = regexp.MustCompile("^[a-z]{2,5}$") +var _regex_OuterMessage_ImportantString = regexp.MustCompile(`^[a-z]{2,5}$`) func (this *OuterMessage) Validate() error { if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) { - return fmt.Errorf("validation error: OuterMessage.ImportantString must conform to regex " + "^[a-z]{2,5}$") + return github_com_TheThingsIndustries_go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, this.ImportantString)) } if nil == this.Inner { - return fmt.Errorf("validation error: OuterMessage.Inner message must exist") + return github_com_TheThingsIndustries_go_proto_validators.FieldError("Inner", fmt.Errorf("message must exist")) } if this.Inner != nil { - if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Inner); err != nil { - return err + if err := github_com_TheThingsIndustries_go_proto_validators.CallValidatorIfExists(this.Inner); err != nil { + return github_com_TheThingsIndustries_go_proto_validators.FieldError("Inner", err) } } return nil diff --git a/plugin/plugin.go b/plugin/plugin.go index 221c0a5..503a2f9 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -55,12 +55,12 @@ import ( "strconv" "strings" + "github.com/TheThingsIndustries/go-proto-validators" "github.com/gogo/protobuf/gogoproto" "github.com/gogo/protobuf/proto" descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" "github.com/gogo/protobuf/protoc-gen-gogo/generator" "github.com/gogo/protobuf/vanity" - "github.com/mwitkow/go-proto-validators" ) type plugin struct { @@ -92,7 +92,7 @@ func (p *plugin) Generate(file *generator.FileDescriptor) { p.PluginImports = generator.NewPluginImports(p.Generator) p.regexPkg = p.NewImport("regexp") p.fmtPkg = p.NewImport("fmt") - p.validatorPkg = p.NewImport("github.com/mwitkow/go-proto-validators") + p.validatorPkg = p.NewImport("github.com/TheThingsIndustries/go-proto-validators") for _, msg := range file.Messages() { if msg.DescriptorProto.GetOptions().GetMapEntry() { diff --git a/protoc-gen-govalidators/main.go b/protoc-gen-govalidators/main.go index 2aacf29..49797bc 100644 --- a/protoc-gen-govalidators/main.go +++ b/protoc-gen-govalidators/main.go @@ -9,9 +9,9 @@ import ( "strconv" "strings" + validator_plugin "github.com/TheThingsIndustries/go-proto-validators/plugin" "github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/protoc-gen-gogo/generator" - validator_plugin "github.com/mwitkow/go-proto-validators/plugin" ) func main() { diff --git a/test/validator_proto2.proto b/test/validator_proto2.proto index e0e95ae..344b994 100644 --- a/test/validator_proto2.proto +++ b/test/validator_proto2.proto @@ -5,7 +5,7 @@ syntax = "proto2"; package validatortest; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/mwitkow/go-proto-validators/validator.proto"; +import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; message ValidatorMessage { // Embedded message test structure. diff --git a/test/validator_proto3.proto b/test/validator_proto3.proto index dd0b9dd..96500c7 100644 --- a/test/validator_proto3.proto +++ b/test/validator_proto3.proto @@ -5,7 +5,7 @@ syntax = "proto3"; package validatortest; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/mwitkow/go-proto-validators/validator.proto"; +import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; message ValidatorMessage3 { // Embedded message test structure. diff --git a/test/validator_proto3_map.proto b/test/validator_proto3_map.proto index f8799b1..1047b8a 100644 --- a/test/validator_proto3_map.proto +++ b/test/validator_proto3_map.proto @@ -5,7 +5,7 @@ syntax = "proto3"; package validatortest; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/mwitkow/go-proto-validators/validator.proto"; +import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; message ValueType { string something = 1 ; diff --git a/test/validator_proto3_oneof.proto b/test/validator_proto3_oneof.proto index 2170ed5..096ad6b 100644 --- a/test/validator_proto3_oneof.proto +++ b/test/validator_proto3_oneof.proto @@ -5,7 +5,7 @@ syntax = "proto3"; package validatortest; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/mwitkow/go-proto-validators/validator.proto"; +import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; message ExternalMsg { string Identifier = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}]; @@ -25,4 +25,4 @@ message OneOfMessage3 { uint32 three_int = 5 [(validator.field) = {int_gt: 20}]; uint32 four_int = 6 [(validator.field) = {int_gt: 100}]; } -} \ No newline at end of file +} diff --git a/validator.pb.go b/validator.pb.go index 755e6ed..1aae3a6 100644 --- a/validator.pb.go +++ b/validator.pb.go @@ -1,22 +1,12 @@ -// Code generated by protoc-gen-gogo. +// Code generated by protoc-gen-gogo. DO NOT EDIT. // source: validator.proto -// DO NOT EDIT! -/* -Package validator is a generated protocol buffer package. - -It is generated from these files: - validator.proto - -It has these top-level messages: - FieldValidator -*/ package validator import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" -import google_protobuf "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" +import descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal @@ -71,14 +61,35 @@ type FieldValidator struct { // Field value of length smaller than this value. LengthLt *int64 `protobuf:"varint,15,opt,name=length_lt,json=lengthLt" json:"length_lt,omitempty"` // Field value of integer strictly equal this value. - LengthEq *int64 `protobuf:"varint,16,opt,name=length_eq,json=lengthEq" json:"length_eq,omitempty"` - XXX_unrecognized []byte `json:"-"` + LengthEq *int64 `protobuf:"varint,16,opt,name=length_eq,json=lengthEq" json:"length_eq,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FieldValidator) Reset() { *m = FieldValidator{} } +func (m *FieldValidator) String() string { return proto.CompactTextString(m) } +func (*FieldValidator) ProtoMessage() {} +func (*FieldValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_validator_46337c4efef37b9b, []int{0} +} +func (m *FieldValidator) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FieldValidator.Unmarshal(m, b) +} +func (m *FieldValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FieldValidator.Marshal(b, m, deterministic) +} +func (dst *FieldValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_FieldValidator.Merge(dst, src) +} +func (m *FieldValidator) XXX_Size() int { + return xxx_messageInfo_FieldValidator.Size(m) +} +func (m *FieldValidator) XXX_DiscardUnknown() { + xxx_messageInfo_FieldValidator.DiscardUnknown(m) } -func (m *FieldValidator) Reset() { *m = FieldValidator{} } -func (m *FieldValidator) String() string { return proto.CompactTextString(m) } -func (*FieldValidator) ProtoMessage() {} -func (*FieldValidator) Descriptor() ([]byte, []int) { return fileDescriptorValidator, []int{0} } +var xxx_messageInfo_FieldValidator proto.InternalMessageInfo func (m *FieldValidator) GetRegex() string { if m != nil && m.Regex != nil { @@ -193,7 +204,7 @@ func (m *FieldValidator) GetLengthEq() int64 { } var E_Field = &proto.ExtensionDesc{ - ExtendedType: (*google_protobuf.FieldOptions)(nil), + ExtendedType: (*descriptor.FieldOptions)(nil), ExtensionType: (*FieldValidator)(nil), Field: 65020, Name: "validator.field", @@ -206,11 +217,11 @@ func init() { proto.RegisterExtension(E_Field) } -func init() { proto.RegisterFile("validator.proto", fileDescriptorValidator) } +func init() { proto.RegisterFile("validator.proto", fileDescriptor_validator_46337c4efef37b9b) } -var fileDescriptorValidator = []byte{ +var fileDescriptor_validator_46337c4efef37b9b = []byte{ // 392 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x92, 0xcf, 0x8f, 0x93, 0x40, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xcf, 0x8f, 0x93, 0x40, 0x14, 0xc7, 0x83, 0xbb, 0xec, 0xc2, 0xb0, 0xdb, 0x6d, 0x26, 0x9a, 0x4c, 0x35, 0x8d, 0x44, 0x2f, 0x1c, 0x0c, 0x4d, 0x3c, 0x7a, 0xd4, 0x60, 0x2f, 0xf8, 0x23, 0x1c, 0x3c, 0x78, 0x21, 0x58, 0x5e, 0xa7, 0x93, 0x0c, 0x33, 0x74, 0xe6, 0xd5, 0xe0, 0x3f, 0xe0, 0x3f, 0xad, 0x07, 0xc3, 0x20, 0x85, From d8747be6c28971006e30660d9ff9758cbcb5913b Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sun, 6 Jan 2019 15:20:51 +0100 Subject: [PATCH 02/17] Update makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f437b81..d933304 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ init: install: @echo "--- Installing govalidators to GOPATH" - go install github.com/TheTHingsIndustries/go-proto-validators/protoc-gen-govalidators + go install github.com/TheThingsIndustries/go-proto-validators/protoc-gen-govalidators regenerate_test_gogo: @echo "Regenerating test .proto files with gogo imports" From 9b4ff731be1a33beb2198f4deb9433f1ba49d9cc Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sun, 6 Jan 2019 15:25:54 +0100 Subject: [PATCH 03/17] Refactor helper package --- helper.go | 39 --------- util/util.go | 147 +++++++++++++++++++++++++++++++ util/util_test.go | 219 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+), 39 deletions(-) delete mode 100644 helper.go create mode 100644 util/util.go create mode 100644 util/util_test.go diff --git a/helper.go b/helper.go deleted file mode 100644 index 9a3ee9b..0000000 --- a/helper.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2016 Michal Witkowski. All Rights Reserved. -// See LICENSE for licensing terms. - -package validator - -import "strings" - -// Validator is a general interface that allows a message to be validated. -type Validator interface { - Validate() error -} - -func CallValidatorIfExists(candidate interface{}) error { - if validator, ok := candidate.(Validator); ok { - return validator.Validate() - } - return nil -} - -type fieldError struct { - fieldStack []string - nestedErr error -} - -func (f *fieldError) Error() string { - return "invalid field " + strings.Join(f.fieldStack, ".") + ": " + f.nestedErr.Error() -} - -// FieldError wraps a given Validator error providing a message call stack. -func FieldError(fieldName string, err error) error { - if fErr, ok := err.(*fieldError); ok { - fErr.fieldStack = append([]string{fieldName}, fErr.fieldStack...) - return err - } - return &fieldError{ - fieldStack: []string{fieldName}, - nestedErr: err, - } -} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..4d7e83c --- /dev/null +++ b/util/util.go @@ -0,0 +1,147 @@ +package util + +import ( + "errors" + "reflect" + "strings" +) + +const ( + fieldMaskDelimiter string = "." + jsonTagDelimiter string = "," +) + +var ( + errInvalidMessage error = errors.New("Invalid Message") + errEmptyMessage error = errors.New("Message is empty") +) + +// Validator is a general interface that allows a message to be validated. +type Validator interface { + Validate([]string) error +} + +// CallValidatorIfExists is used to call the validator for the embedded message if it exists. +// It generates the fieldmask for the sub-field before calling it. +// The conditional field shouldBeCalled is used to prevent this function from calling the sub validator based on the parent fieldmask. +func CallValidatorIfExists(candidate interface{}, topLevelPath string, fullPaths []string) error { + if validator, ok := candidate.(Validator); ok { + return validator.Validate(getFieldMaskForEmbeddedFields(topLevelPath, fullPaths)) + } + return nil +} + +type fieldError struct { + fieldStack []string + nestedErr error +} + +// Error returns the error as a string +func (f *fieldError) Error() string { + return "invalid field " + strings.Join(f.fieldStack, ".") + ": " + f.nestedErr.Error() +} + +// FieldError wraps a given Validator error providing a message call stack. +func FieldError(fieldName string, err error) error { + if fErr, ok := err.(*fieldError); ok { + fErr.fieldStack = append([]string{fieldName}, fErr.fieldStack...) + return err + } + return &fieldError{ + fieldStack: []string{fieldName}, + nestedErr: err, + } +} + +// GetFieldsToValidate extracts the names of fields for the corresponding fieldmasks. +// If the fieldmask is empty, all the fields are returned. +// This works only on protobuf generated structs or structs that have JSON tags in the format "fieldname,omitempty". +func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { + val := reflect.ValueOf(i).Elem() + if !val.IsValid() || val.Type().NumField() == 0 { + return []string{}, errInvalidMessage + } + fields := []string{} + for i := 0; i < val.Type().NumField(); i++ { + jsonTag := val.Type().Field(i).Tag.Get("json") + if jsonTag == "" || jsonTag == "-" { + continue + } + // Split the tag since protobuf json tags are in the format "fieldname,omitempty". + s := strings.Split(jsonTag, jsonTagDelimiter) + if len(s) != 2 { + return []string{}, errInvalidMessage + } + // Add all fields to the list to be validated if no fieldmask paths are specified. + if len(paths) == 0 { + fields = append(fields, val.Type().Field(i).Name) + continue + } + // Add a field if it a part of the supplied list. + for _, st := range paths { + if s[0] == st { + fields = append(fields, val.Type().Field(i).Name) + break + } + } + } + return fields, nil +} + +// ShouldBeValidated checks if the given field is a part of the list of fields to be validated. +// This list is created using "GetFieldsToValidate". +func ShouldBeValidated(name string, fieldNames []string) bool { + // The name as passed by the generator would be in the format this.FieldName + s := strings.Split(name, fieldMaskDelimiter) + if len(s) != 2 { + // When it's malformed, validate anyway since it's difficult validate errors here. + return true + } + for _, fieldName := range fieldNames { + if s[1] == fieldName { + return true + } + } + return false +} + +// getFieldMaskForEmbeddedFields returns a new FieldMask path for fields inside an embedded message. +func getFieldMaskForEmbeddedFields(topLevelMask string, paths []string) []string { + var subFields strings.Builder + embeddedFields := []string{} + for _, path := range paths { + subFields.Reset() + if path == "" { + continue //Sanity check for empty paths + } + s := strings.Split(path, fieldMaskDelimiter) + if len(s) < 2 || s[0] != topLevelMask { + continue + } + // Join the rest of the sub-fields back into a single string. + for i := 1; i < len(s); i++ { + if s[i] == "" { + // If empty strings are encountered, exit loop and invalidate the entire string + subFields.Reset() + break + } + if subFields.String() != "" { + // Add the dot for all subsequent characters + subFields.WriteString(fieldMaskDelimiter) + } + subFields.WriteString(s[i]) + } + if subFields.String() != "" { + embeddedFields = append(embeddedFields, subFields.String()) + } + } + return embeddedFields +} + +func GetFieldMaskForRepeatedFields(i interface{}, paths []string) ([]string, error) { + return nil, nil +} + +func GetFieldMaskForOneOfFields(i interface{}, paths []string) ([]string, error) { + return nil, nil +} diff --git a/util/util_test.go b/util/util_test.go new file mode 100644 index 0000000..217462e --- /dev/null +++ b/util/util_test.go @@ -0,0 +1,219 @@ +package util + +import ( + "fmt" + "reflect" + "testing" +) + +var allFields = []string{"SomeString", "SomeInt", "SomeDouble", "SomeRepeated", "SomeEmbedded"} +var completeFieldMask = []string{"some_string", "some_int", "some_double", "some_repeated", "some_embedded"} +var completeEmbeddedFieldMask = []string{"some_string", "some_int", "some_double", "some_repeated", "some_embedded.ids", "some_embedded.ids.version", "some_embedded.ids.version.timestamp", "some_embedded.value", "some_embedded.name"} +var embeddedFields = []string{"ids", "ids.version", "ids.version.timestamp", "value", "name"} + +// Embedded is an embedded message test structure. +type Embedded struct { + Identifier string `protobuf:"bytes,1,opt,name=Identifier,proto3" json:"Identifier,omitempty"` + SomeValue int64 `protobuf:"varint,2,opt,name=SomeValue,proto3" json:"SomeValue,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +type TestMessage struct { + SomeString string `protobuf:"bytes,1,opt,name=SomeString,proto3" json:"some_string,omitempty"` + SomeInt uint32 `protobuf:"varint,6,opt,name=SomeInt,proto3" json:"some_int,omitempty"` + SomeDouble float64 `protobuf:"fixed64,24,opt,name=SomeDouble,proto3" json:"some_double,omitempty"` + SomeRepeated []int32 `protobuf:"varint,33,rep,packed,name=SomeRepeated" json:"some_repeated,omitempty"` + SomeEmbedded *Embedded `protobuf:"bytes,10,opt,name=SomeEmbedded" json:"some_embedded,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +type BadStruct struct { + ID string `json:"id"` +} + +func (m *TestMessage) Validate(fieldMask []string) error { + return nil +} +func (m *Embedded) Validate(fieldMask []string) error { + fmt.Println(fieldMask) + return nil +} + +func TestGetFieldsToValidate(t *testing.T) { + + for _, tc := range []struct { + Name string + InputMessage interface{} + InputFieldMaskPaths []string + ExpectedFields []string + ExpectedError interface{} + }{ + { + Name: "NilFieldMask", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: nil, + ExpectedFields: allFields, + ExpectedError: nil, + }, + { + Name: "OneField", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: []string{"some_int"}, + ExpectedFields: []string{"SomeInt"}, + ExpectedError: nil, + }, + { + Name: "InvalidFieldMask", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: []string{"somesome_int"}, + ExpectedFields: []string{}, + ExpectedError: nil, + }, + { + Name: "FullFieldMask", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: completeFieldMask, + ExpectedFields: allFields, + ExpectedError: nil, + }, + { + Name: "InvalidStructTag", + InputMessage: &BadStruct{}, + InputFieldMaskPaths: completeFieldMask, + ExpectedFields: []string{}, + ExpectedError: errInvalidMessage, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + res, err := GetFieldsToValidate(tc.InputMessage, tc.InputFieldMaskPaths) + if err != tc.ExpectedError { + t.Fatal(err) + } + if !reflect.DeepEqual(res, tc.ExpectedFields) { + t.Fatalf("Invalid field array received: %s", res) + } + }) + } + +} + +func TestShouldBeValidated(t *testing.T) { + for _, tc := range []struct { + Name string + InputField string + ValidFields []string + ExpectedResult bool + }{ + { + Name: "ShouldBeValidated", + InputField: "this.SomeInt", + ValidFields: allFields, + ExpectedResult: true, + }, + { + Name: "ShouldNotValidated", + InputField: "this.SomeInt", + ValidFields: []string{"SomeString", "SomeDouble", "SomeRepeated", "SomeEmbedded"}, + ExpectedResult: false, + }, + { + Name: "MalFormedInput", + InputField: "SomeInt", + ValidFields: []string{"SomeString", "SomeDouble", "SomeRepeated", "SomeEmbedded"}, + ExpectedResult: true, + }, + { + Name: "NoFieldsToValidate", + InputField: "this.SomeInt", + ValidFields: []string{}, + ExpectedResult: false, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + res := ShouldBeValidated(tc.InputField, tc.ValidFields) + if tc.ExpectedResult != res { + t.Fatal(fmt.Sprintf("Expected %v, received %v", tc.ExpectedResult, res)) + } + }) + } + +} + +func TestGetFieldMaskForEmbeddedFields(t *testing.T) { + for _, tc := range []struct { + Name string + TopLevelField string + InputFieldMask []string + ExpectedFieldMask []string + }{ + { + Name: "EmptyTopField", + TopLevelField: "", + InputFieldMask: completeEmbeddedFieldMask, + ExpectedFieldMask: []string{}, + }, + { + Name: "EmptyFieldMask", + TopLevelField: "ids", + InputFieldMask: nil, + ExpectedFieldMask: []string{}, + }, + { + Name: "WithSomeEmptyPaths", + TopLevelField: "ids", + InputFieldMask: []string{"ids.name", "ids.version", "", "up_counter"}, + ExpectedFieldMask: []string{"name", "version"}, + }, + { + Name: "WithSomeMalformedFields", + TopLevelField: "ids", + InputFieldMask: []string{"ids.name", "ids.version", "", "up_counter", "ids.", ".ids", "ids.version.name.", "ids..version.name", "firmware,version"}, + ExpectedFieldMask: []string{"name", "version"}, + }, + { + Name: "NoMatch", + TopLevelField: "counter", + InputFieldMask: completeEmbeddedFieldMask, + ExpectedFieldMask: []string{}, + }, + { + Name: "FullMatch", + TopLevelField: "some_embedded", + InputFieldMask: completeEmbeddedFieldMask, + ExpectedFieldMask: embeddedFields, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + res := getFieldMaskForEmbeddedFields(tc.TopLevelField, tc.InputFieldMask) + if !reflect.DeepEqual(res, tc.ExpectedFieldMask) { + t.Fatalf("Invalid FieldMasks received: %s", res) + } + + }) + } +} + +func TestCallIfValidatorExists(t *testing.T) { + for _, tc := range []struct { + Name string + Message interface{} + TopLevelPath string + FullPaths []string + }{ + { + Name: "ValidFields", + Message: &Embedded{}, + TopLevelPath: "some_embedded", + FullPaths: completeEmbeddedFieldMask, + }, + } { + err := CallValidatorIfExists(tc.Message, tc.TopLevelPath, tc.FullPaths) + if err != nil { + t.Fatal("Validator not called") + } + } +} From 5e7eae8e37f4d9ff3c99fb637ffd317697caf179 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sun, 6 Jan 2019 17:13:13 +0100 Subject: [PATCH 04/17] Adapt plugin for generic case --- plugin/plugin.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index 503a2f9..eda0c6e 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -92,7 +92,7 @@ func (p *plugin) Generate(file *generator.FileDescriptor) { p.PluginImports = generator.NewPluginImports(p.Generator) p.regexPkg = p.NewImport("regexp") p.fmtPkg = p.NewImport("fmt") - p.validatorPkg = p.NewImport("github.com/TheThingsIndustries/go-proto-validators") + p.validatorPkg = p.NewImport("github.com/TheThingsIndustries/go-proto-validators/util") for _, msg := range file.Messages() { if msg.DescriptorProto.GetOptions().GetMapEntry() { @@ -236,8 +236,16 @@ func (p *plugin) generateProto2Message(file *generator.FileDescriptor, message * func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message *generator.Descriptor) { ccTypeName := generator.CamelCaseSlice(message.TypeName()) - p.P(`func (this *`, ccTypeName, `) Validate() error {`) + p.P(`func (this *`, ccTypeName, `) Validate(paths []string) error {`) + p.In() + p.P(`toBeValidated, err := `, p.validatorPkg.Use(), `.GetFieldsToValidate(this, paths)`) + p.P(`if err != nil {`) p.In() + p.P(`return err`) + p.Out() + p.P(`}`) + p.P("\n") + for _, field := range message.Field { fieldValidator := getFieldValidatorIfAny(field) if fieldValidator == nil && !field.IsMessage() { @@ -299,13 +307,13 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } } if nullable { - p.P(`if `, variableName, ` != nil {`) + p.P(`if `, variableName, ` != nil && `, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", paths){`) p.In() } else { // non-nullable fields in proto3 store actual structs, we need pointers to operate on interfaces variableName = "&(" + variableName + ")" } - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `); err != nil {`) + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,"`, variableName, `", paths ); err != nil {`) p.In() p.P(`return `, p.validatorPkg.Use(), `.FieldError("`, fieldName, `", err)`) p.Out() @@ -370,7 +378,7 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, } if fv.LengthEq != nil { - p.P(`if !( len(`, variableName, `) == `, fv.LengthEq, `) {`) + p.P(`if !( len(`, variableName, `) == `, fv.LengthEq, ` && shouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`length be not equal '%d'`, fv.GetLengthEq()) p.generateErrorString(variableName, fieldName, errorStr, fv) From 15340e76c0273556fc902f54ba13cbf39c449cb0 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Mon, 7 Jan 2019 10:11:12 +0100 Subject: [PATCH 05/17] Support basic types with new function definition --- plugin/plugin.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index eda0c6e..f241cc2 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -307,7 +307,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } } if nullable { - p.P(`if `, variableName, ` != nil && `, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", paths){`) + p.P(`if (`, variableName, ` != nil) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", paths)){`) p.In() } else { // non-nullable fields in proto3 store actual structs, we need pointers to operate on interfaces @@ -341,7 +341,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv.IntGt != nil { - p.P(`if !(`, variableName, ` > `, fv.IntGt, `) {`) + p.P(`if !(`, variableName, ` > `, fv.IntGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`be greater than '%d'`, fv.GetIntGt()) p.generateErrorString(variableName, fieldName, errorStr, fv) @@ -349,7 +349,7 @@ func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fi p.P(`}`) } if fv.IntLt != nil { - p.P(`if !(`, variableName, ` < `, fv.IntLt, `) {`) + p.P(`if !(`, variableName, ` < `, fv.IntLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`be less than '%d'`, fv.GetIntLt()) p.generateErrorString(variableName, fieldName, errorStr, fv) @@ -360,7 +360,7 @@ func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fi func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv.LengthGt != nil { - p.P(`if !( len(`, variableName, `) > `, fv.LengthGt, `) {`) + p.P(`if !( len(`, variableName, `) == `, fv.LengthGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`length be greater than '%d'`, fv.GetLengthGt()) p.generateErrorString(variableName, fieldName, errorStr, fv) @@ -369,7 +369,7 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, } if fv.LengthLt != nil { - p.P(`if !( len(`, variableName, `) < `, fv.LengthLt, `) {`) + p.P(`if !( len(`, variableName, `) == `, fv.LengthLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`length be less than '%d'`, fv.GetLengthLt()) p.generateErrorString(variableName, fieldName, errorStr, fv) @@ -378,7 +378,7 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, } if fv.LengthEq != nil { - p.P(`if !( len(`, variableName, `) == `, fv.LengthEq, ` && shouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !( len(`, variableName, `) == `, fv.LengthEq, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`length be not equal '%d'`, fv.GetLengthEq()) p.generateErrorString(variableName, fieldName, errorStr, fv) @@ -470,7 +470,7 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, func (p *plugin) generateStringValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv.Regex != nil { - p.P(`if !`, p.regexName(ccTypeName, fieldName), `.MatchString(`, variableName, `) {`) + p.P(`if !`, p.regexName(ccTypeName, fieldName), `.MatchString(`, variableName, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := "be a string conforming to regex " + strconv.Quote(fv.GetRegex()) p.generateErrorString(variableName, fieldName, errorStr, fv) @@ -478,7 +478,7 @@ func (p *plugin) generateStringValidator(variableName string, ccTypeName string, p.P(`}`) } if fv.StringNotEmpty != nil && fv.GetStringNotEmpty() { - p.P(`if `, variableName, ` == "" {`) + p.P(`if (`, variableName, ` == "" ) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := "not be an empty string" p.generateErrorString(variableName, fieldName, errorStr, fv) @@ -585,9 +585,9 @@ func (p *plugin) validatorWithNonRepeatedConstraint(fv *validator.FieldValidator } // Need to use reflection in order to be future-proof for new types of constraints. - v := reflect.ValueOf(*fv) + v := reflect.ValueOf(fv).Elem() for i := 0; i < v.NumField(); i++ { - if v.Type().Field(i).Name != "RepeatedCountMin" && v.Type().Field(i).Name != "RepeatedCountMax" && v.Field(i).Pointer() != 0 { + if v.Type().Field(i).Name != "RepeatedCountMin" && v.Type().Field(i).Name != "RepeatedCountMax" { return true } } From 5ec3d1e3a56f4b7ea653ac1f7f2200737c5cc1fc Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Mon, 7 Jan 2019 10:49:21 +0100 Subject: [PATCH 06/17] Support custom json tags --- util/util.go | 4 +--- util/util_test.go | 13 ++++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/util/util.go b/util/util.go index 4d7e83c..632ccf8 100644 --- a/util/util.go +++ b/util/util.go @@ -55,7 +55,6 @@ func FieldError(fieldName string, err error) error { // GetFieldsToValidate extracts the names of fields for the corresponding fieldmasks. // If the fieldmask is empty, all the fields are returned. -// This works only on protobuf generated structs or structs that have JSON tags in the format "fieldname,omitempty". func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { val := reflect.ValueOf(i).Elem() if !val.IsValid() || val.Type().NumField() == 0 { @@ -67,9 +66,8 @@ func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { if jsonTag == "" || jsonTag == "-" { continue } - // Split the tag since protobuf json tags are in the format "fieldname,omitempty". s := strings.Split(jsonTag, jsonTagDelimiter) - if len(s) != 2 { + if len(s) > 2 { return []string{}, errInvalidMessage } // Add all fields to the list to be validated if no fieldmask paths are specified. diff --git a/util/util_test.go b/util/util_test.go index 217462e..fd67c63 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -32,7 +32,11 @@ type TestMessage struct { } type BadStruct struct { - ID string `json:"id"` + ID string `json:"id,omitempty,repeatable"` +} + +type CustomTagStruct struct { + SomeString string `json:"some_string"` } func (m *TestMessage) Validate(fieldMask []string) error { @@ -87,6 +91,13 @@ func TestGetFieldsToValidate(t *testing.T) { ExpectedFields: []string{}, ExpectedError: errInvalidMessage, }, + { + Name: "CustomStructTag", + InputMessage: &CustomTagStruct{}, + InputFieldMaskPaths: completeFieldMask, + ExpectedFields: []string{"SomeString"}, + ExpectedError: nil, + }, } { t.Run(tc.Name, func(t *testing.T) { res, err := GetFieldsToValidate(tc.InputMessage, tc.InputFieldMaskPaths) From e13c031fc4a7ca0fb113e9b252f01f9a4e3e95fc Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Mon, 7 Jan 2019 13:32:08 +0100 Subject: [PATCH 07/17] Remove proto2 support --- plugin/plugin.go | 95 +++++------------------------------------------- 1 file changed, 9 insertions(+), 86 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index f241cc2..92cc218 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -50,6 +50,7 @@ package plugin import ( "fmt" + "log" "os" "reflect" "strconv" @@ -70,9 +71,11 @@ type plugin struct { fmtPkg generator.Single protoPkg generator.Single validatorPkg generator.Single + errorPkg generator.Single useGogoImport bool } +// NewPlugin ... func NewPlugin(useGogoImport bool) generator.Plugin { return &plugin{useGogoImport: useGogoImport} } @@ -93,6 +96,7 @@ func (p *plugin) Generate(file *generator.FileDescriptor) { p.regexPkg = p.NewImport("regexp") p.fmtPkg = p.NewImport("fmt") p.validatorPkg = p.NewImport("github.com/TheThingsIndustries/go-proto-validators/util") + p.errorPkg = p.NewImport("github.com/TheThingsIndustries/go-proto-validators/errors") for _, msg := range file.Messages() { if msg.DescriptorProto.GetOptions().GetMapEntry() { @@ -102,7 +106,7 @@ func (p *plugin) Generate(file *generator.FileDescriptor) { if gogoproto.IsProto3(file.FileDescriptorProto) { p.generateProto3Message(file, msg) } else { - p.generateProto2Message(file, msg) + log.Fatal("Only proto3 messages are supported") } } @@ -153,87 +157,6 @@ func (p *plugin) generateRegexVars(file *generator.FileDescriptor, message *gene } } -func (p *plugin) generateProto2Message(file *generator.FileDescriptor, message *generator.Descriptor) { - ccTypeName := generator.CamelCaseSlice(message.TypeName()) - - p.P(`func (this *`, ccTypeName, `) Validate() error {`) - p.In() - for _, field := range message.Field { - fieldName := p.GetFieldName(message, field) - fieldValidator := getFieldValidatorIfAny(field) - if fieldValidator == nil && !field.IsMessage() { - continue - } - if p.validatorWithMessageExists(fieldValidator) { - fmt.Fprintf(os.Stderr, "WARNING: field %v.%v is a proto2 message, validator.msg_exists has no effect\n", ccTypeName, fieldName) - } - variableName := "this." + fieldName - repeated := field.IsRepeated() - nullable := gogoproto.IsNullable(field) - // For proto2 syntax, only Gogo generates non-pointer fields - nonpointer := gogoproto.ImportsGoGoProto(file.FileDescriptorProto) && !gogoproto.IsNullable(field) - if repeated { - p.generateRepeatedCountValidator(variableName, ccTypeName, fieldName, fieldValidator) - if field.IsMessage() || p.validatorWithNonRepeatedConstraint(fieldValidator) { - p.P(`for _, item := range `, variableName, `{`) - p.In() - variableName = "item" - } - } else if nullable { - p.P(`if `, variableName, ` != nil {`) - p.In() - if !field.IsBytes() { - variableName = "*(" + variableName + ")" - } - } else if nonpointer { - // can use the field directly - } else if !field.IsMessage() { - variableName = `this.Get` + fieldName + `()` - } - if !repeated && fieldValidator != nil { - if fieldValidator.RepeatedCountMin != nil { - fmt.Fprintf(os.Stderr, "WARNING: field %v.%v is not repeated, validator.min_elts has no effects\n", ccTypeName, fieldName) - } - if fieldValidator.RepeatedCountMax != nil { - fmt.Fprintf(os.Stderr, "WARNING: field %v.%v is not repeated, validator.max_elts has no effects\n", ccTypeName, fieldName) - } - } - if field.IsString() { - p.generateStringValidator(variableName, ccTypeName, fieldName, fieldValidator) - } else if p.isSupportedInt(field) { - p.generateIntValidator(variableName, ccTypeName, fieldName, fieldValidator) - } else if p.isSupportedFloat(field) { - p.generateFloatValidator(variableName, ccTypeName, fieldName, fieldValidator) - } else if field.IsBytes() { - p.generateLengthValidator(variableName, ccTypeName, fieldName, fieldValidator) - } else if field.IsMessage() { - if repeated && nullable { - variableName = "*(item)" - } - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(&(`, variableName, `)); err != nil {`) - p.In() - p.P(`return `, p.validatorPkg.Use(), `.FieldError("`, fieldName, `", err)`) - p.Out() - p.P(`}`) - } - if repeated { - // end the repeated loop - if field.IsMessage() || p.validatorWithNonRepeatedConstraint(fieldValidator) { - // This internal 'if' cannot be refactored as it would change semantics with respect to the corresponding prelude 'if's - p.Out() - p.P(`}`) - } - } else if nullable { - // end the if around nullable - p.Out() - p.P(`}`) - } - } - p.P(`return nil`) - p.Out() - p.P(`}`) -} - func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message *generator.Descriptor) { ccTypeName := generator.CamelCaseSlice(message.TypeName()) p.P(`func (this *`, ccTypeName, `) Validate(paths []string) error {`) @@ -297,7 +220,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * if nullable && !repeated { p.P(`if nil == `, variableName, `{`) p.In() - p.P(`return `, p.validatorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), `.Errorf("message must exist"))`) + p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), `.Errorf("message must exist"))`) p.Out() p.P(`}`) } else if repeated { @@ -315,7 +238,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,"`, variableName, `", paths ); err != nil {`) p.In() - p.P(`return `, p.validatorPkg.Use(), `.FieldError("`, fieldName, `", err)`) + p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `", err)`) p.Out() p.P(`}`) if nullable { @@ -515,9 +438,9 @@ func (p *plugin) generateRepeatedCountValidator(variableName string, ccTypeName func (p *plugin) generateErrorString(variableName string, fieldName string, specificError string, fv *validator.FieldValidator) { if fv.GetHumanError() == "" { - p.P(`return `, p.validatorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`value '%v' must ", specificError, "`", `, `, variableName, `))`) + p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`value '%v' must ", specificError, "`", `, `, variableName, `))`) } else { - p.P(`return `, p.validatorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`", fv.GetHumanError(), "`))") + p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`", fv.GetHumanError(), "`))") } } From 9d730aecccfe33c1ae8ba409f4fc8fef4751422a Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Mon, 7 Jan 2019 15:15:26 +0100 Subject: [PATCH 08/17] Enhance error handling --- Makefile | 16 +++++ errors/errors.go | 62 ++++++++++++++++++++ errors/errors_test.go | 22 +++++++ errors/types.pb.go | 132 ++++++++++++++++++++++++++++++++++++++++++ errors/types.proto | 40 +++++++++++++ plugin/plugin.go | 53 +++++++++-------- util/util.go | 22 ------- 7 files changed, 300 insertions(+), 47 deletions(-) create mode 100644 errors/errors.go create mode 100644 errors/errors_test.go create mode 100644 errors/types.pb.go create mode 100644 errors/types.proto diff --git a/Makefile b/Makefile index d933304..25565ca 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,15 @@ install: @echo "--- Installing govalidators to GOPATH" go install github.com/TheThingsIndustries/go-proto-validators/protoc-gen-govalidators + +regenerate_test_generic: + @echo "Regenerating the generic test .proto files (with gogo)" + (protoc \ + --proto_path=${GOPATH}/src \ + --proto_path=test \ + --gogo_out=test \ + --govalidators_out=gogoimport=true:test test/generic/*.proto) + regenerate_test_gogo: @echo "Regenerating test .proto files with gogo imports" (protoc \ @@ -34,6 +43,13 @@ regenerate_example: install --go_out=. \ --govalidators_out=. examples/*.proto) +errors: install + @echo "--- Regenerating error definitions" + (protoc \ + --proto_path=${GOPATH}/src \ + --proto_path=errors \ + --go_out=errors errors/*.proto) + test: install regenerate_test_gogo regenerate_test_golang @echo "Running tests" go test -v ./... diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..92461f4 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,62 @@ +package errors + +import ( + "fmt" + "strings" +) + +// ValidatorFieldError is a generic struct that can be used for better error usage in tests and in code. +type ValidatorFieldError struct { + fieldStack []string + nestedErr error + fieldName string + errType Types +} + +// Error returns the error as a string +func (f *ValidatorFieldError) Error() string { + return fmt.Sprintf("FIELD_ERROR_TYPE_%s: %s: %s", f.errType.String(), strings.Join(f.fieldStack, "."), f.nestedErr.Error()) +} + +// GetFieldName extracts the field name from the error message. +func GetFieldName(err string) string { + s := strings.Split(err, ": ") + if len(s) != 3 { + return "" + } + return s[1] +} + +// GetErrorDescripton extracts the error stack from the error message. +func GetErrorDescripton(err string) string { + s := strings.Split(err, ": ") + if len(s) != 3 { + return "" + } + return s[2] +} + +// GetType extracts the errors.Types name from the error message. +func GetType(err string) string { + s := strings.Split(err, ": ") + if len(s) != 3 { + return "" + } + return strings.Replace(s[0], "FIELD_ERROR_TYPE", "Types", 1) +} + +// FieldError wraps a given Validator error providing a message call stack. +func FieldError(fieldName string, Type Types, err error) error { + if fErr, ok := err.(*ValidatorFieldError); ok { + fErr.fieldStack = append([]string{fieldName}, fErr.fieldStack...) + fErr.fieldName = fieldName + fErr.errType = Type + return err + } + return &ValidatorFieldError{ + fieldStack: []string{fieldName}, + nestedErr: err, + fieldName: fieldName, + errType: Type, + } +} diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 0000000..50f366f --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,22 @@ +package errors + +import "testing" + +func TestErrorMethods(t *testing.T) { + errString := "FIELD_ERROR_TYPE_INT_GT: SomeInt: field must be greater than '500'" + + errType := GetType(errString) + if errType != "Types_INT_GT" { + t.Fatal(t) + } + + errFieldName := GetFieldName(errString) + if errFieldName != "SomeInt" { + t.Fatal(t) + } + + errDesc := GetErrorDescripton(errString) + if errDesc != "field must be greater than '500'" { + t.Fatal(t) + } +} diff --git a/errors/types.pb.go b/errors/types.pb.go new file mode 100644 index 0000000..4220f20 --- /dev/null +++ b/errors/types.pb.go @@ -0,0 +1,132 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: types.proto + +package errors + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Types defines the error types for the supported validation checks. +// If validation criteria are added/removed from "validator.proto", make sure that this is updated. +// Please keep this list sorted alphabetically. +type Types int32 + +const ( + // Floating value Field describing the epsilon within which any comparison should be considered to be true. + Types_FLOAT_ELIPSON Types = 0 + // Floating value Field must be strictly greater than the specified value. + Types_FLOAT_GT Types = 1 + // Floating value Field must be greater than or equal to the specified value. + Types_FLOAT_GTE Types = 2 + // Floating value Field must be strictly lesser than the specified value. + Types_FLOAT_LT Types = 3 + // Floating value Field must be lesser than or equal to the specified value. + Types_FLOAT_LTE Types = 4 + // Field has an accompanying user specified error. + Types_HUMAN_ERROR Types = 5 + // Integer Field must be strictly greater than the specified value. + Types_INT_GT Types = 6 + // Integer Field must be strictly lesser than the specified value. + Types_INT_LT Types = 7 + // Field (string/byte slice) must have a length equal to the specified value. + Types_LENGTH_EQ Types = 8 + // Field (string/byte slice) must have a length greater than the specified value. + Types_LENGTH_GT Types = 9 + // Field (string/byte slice) must have a length lesser than the specified value. + Types_LENGTH_LT Types = 10 + // Message Field must exist. + Types_MSG_EXISTS Types = 11 + // Repeated Field must at the most the specified number elements. + Types_REPEATED_COUNT_MAX Types = 12 + // Repeated Field must at least the specified number elements. + Types_REPEATED_COUNT_MIN Types = 13 + // String Field must not be empty. + Types_STRING_NOT_EMPTY Types = 14 + // String Field must pass the regex check. + Types_STRING_REGEX Types = 15 +) + +var Types_name = map[int32]string{ + 0: "FLOAT_ELIPSON", + 1: "FLOAT_GT", + 2: "FLOAT_GTE", + 3: "FLOAT_LT", + 4: "FLOAT_LTE", + 5: "HUMAN_ERROR", + 6: "INT_GT", + 7: "INT_LT", + 8: "LENGTH_EQ", + 9: "LENGTH_GT", + 10: "LENGTH_LT", + 11: "MSG_EXISTS", + 12: "REPEATED_COUNT_MAX", + 13: "REPEATED_COUNT_MIN", + 14: "STRING_NOT_EMPTY", + 15: "STRING_REGEX", +} + +var Types_value = map[string]int32{ + "FLOAT_ELIPSON": 0, + "FLOAT_GT": 1, + "FLOAT_GTE": 2, + "FLOAT_LT": 3, + "FLOAT_LTE": 4, + "HUMAN_ERROR": 5, + "INT_GT": 6, + "INT_LT": 7, + "LENGTH_EQ": 8, + "LENGTH_GT": 9, + "LENGTH_LT": 10, + "MSG_EXISTS": 11, + "REPEATED_COUNT_MAX": 12, + "REPEATED_COUNT_MIN": 13, + "STRING_NOT_EMPTY": 14, + "STRING_REGEX": 15, +} + +func (x Types) String() string { + return proto.EnumName(Types_name, int32(x)) +} + +func (Types) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d938547f84707355, []int{0} +} + +func init() { + proto.RegisterEnum("errors.Types", Types_name, Types_value) +} + +func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) } + +var fileDescriptor_d938547f84707355 = []byte{ + // 228 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x31, 0x4e, 0xf3, 0x40, + 0x10, 0x46, 0xff, 0x3f, 0x10, 0x93, 0x8c, 0xed, 0xe4, 0x63, 0x84, 0x38, 0x04, 0x05, 0x0d, 0x27, + 0xb0, 0x60, 0xd8, 0xac, 0xb4, 0xde, 0x35, 0xbb, 0x13, 0xc9, 0x54, 0x2b, 0x21, 0xa5, 0x76, 0xe4, + 0xa4, 0xe1, 0x1a, 0x9c, 0x18, 0x25, 0x10, 0x25, 0x05, 0xdd, 0xbc, 0xf7, 0x34, 0x53, 0x0c, 0x95, + 0xfb, 0xcf, 0xed, 0x66, 0xf7, 0xb8, 0x1d, 0x87, 0xfd, 0xc0, 0xc5, 0x66, 0x1c, 0x87, 0x71, 0xf7, + 0xf0, 0x35, 0xa1, 0xa9, 0x1e, 0x3c, 0xdf, 0x52, 0xfd, 0xea, 0x42, 0xa3, 0x59, 0x9c, 0xed, 0x52, + 0xf0, 0xf8, 0xc7, 0x15, 0xcd, 0x7e, 0x94, 0x51, 0xfc, 0xe7, 0x9a, 0xe6, 0x27, 0x12, 0x4c, 0xce, + 0xd1, 0x29, 0xae, 0xce, 0xd1, 0xa9, 0xe0, 0x9a, 0x97, 0x54, 0xae, 0xd6, 0x6d, 0xe3, 0xb3, 0xc4, + 0x18, 0x22, 0xa6, 0x4c, 0x54, 0x58, 0x7f, 0x3c, 0x54, 0x9c, 0x66, 0xa7, 0xb8, 0x39, 0xec, 0x39, + 0xf1, 0x46, 0x57, 0x59, 0xde, 0x30, 0xbb, 0x40, 0xa3, 0x98, 0x5f, 0xa0, 0x53, 0x10, 0x2f, 0x88, + 0xda, 0x64, 0xb2, 0xf4, 0x36, 0x69, 0x42, 0xc9, 0xf7, 0xc4, 0x51, 0x3a, 0x69, 0x54, 0x5e, 0xf2, + 0x73, 0x58, 0x7b, 0xcd, 0x6d, 0xd3, 0xa3, 0xfa, 0xcb, 0x5b, 0x8f, 0x9a, 0xef, 0x08, 0x49, 0xa3, + 0xf5, 0x26, 0xfb, 0xa0, 0x59, 0xda, 0x4e, 0xdf, 0xb1, 0x60, 0x50, 0xf5, 0x6b, 0xa3, 0x18, 0xe9, + 0xb1, 0xfc, 0x28, 0x8e, 0x3f, 0x7a, 0xfa, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x13, 0xcb, 0x25, 0x9a, + 0x32, 0x01, 0x00, 0x00, +} diff --git a/errors/types.proto b/errors/types.proto new file mode 100644 index 0000000..23fab52 --- /dev/null +++ b/errors/types.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; +package errors; + +// Types defines the error types for the supported validation checks. +// If validation criteria are added/removed from "validator.proto", make sure that this is updated. +// Please keep this list sorted alphabetically. +enum Types{ + // Floating value Field describing the epsilon within which any comparison should be considered to be true. + FLOAT_ELIPSON = 0; + // Floating value Field must be strictly greater than the specified value. + FLOAT_GT = 1; + // Floating value Field must be greater than or equal to the specified value. + FLOAT_GTE = 2; + // Floating value Field must be strictly lesser than the specified value. + FLOAT_LT = 3; + // Floating value Field must be lesser than or equal to the specified value. + FLOAT_LTE = 4; + // Field has an accompanying user specified error. + HUMAN_ERROR = 5; + // Integer Field must be strictly greater than the specified value. + INT_GT = 6; + // Integer Field must be strictly lesser than the specified value. + INT_LT = 7; + // Field (string/byte slice) must have a length equal to the specified value. + LENGTH_EQ = 8; + // Field (string/byte slice) must have a length greater than the specified value. + LENGTH_GT = 9; + // Field (string/byte slice) must have a length lesser than the specified value. + LENGTH_LT = 10; + // Message Field must exist. + MSG_EXISTS = 11; + // Repeated Field must at the most the specified number elements. + REPEATED_COUNT_MAX = 12; + // Repeated Field must at least the specified number elements. + REPEATED_COUNT_MIN = 13; + // String Field must not be empty. + STRING_NOT_EMPTY = 14; + // String Field must pass the regex check. + STRING_REGEX = 15; +} diff --git a/plugin/plugin.go b/plugin/plugin.go index 92cc218..300bd65 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -50,13 +50,13 @@ package plugin import ( "fmt" - "log" "os" "reflect" "strconv" "strings" "github.com/TheThingsIndustries/go-proto-validators" + "github.com/TheThingsIndustries/go-proto-validators/errors" "github.com/gogo/protobuf/gogoproto" "github.com/gogo/protobuf/proto" descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" @@ -105,10 +105,7 @@ func (p *plugin) Generate(file *generator.FileDescriptor) { p.generateRegexVars(file, msg) if gogoproto.IsProto3(file.FileDescriptorProto) { p.generateProto3Message(file, msg) - } else { - log.Fatal("Only proto3 messages are supported") } - } } @@ -220,7 +217,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * if nullable && !repeated { p.P(`if nil == `, variableName, `{`) p.In() - p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), `.Errorf("message must exist"))`) + p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.errorPkg.Use(), `.Types_`, errors.Types_MSG_EXISTS.String(), `, `, p.fmtPkg.Use(), `.Errorf("message must exist"))`) p.Out() p.P(`}`) } else if repeated { @@ -238,7 +235,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,"`, variableName, `", paths ); err != nil {`) p.In() - p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `", err)`) + p.P(`return err`) p.Out() p.P(`}`) if nullable { @@ -267,15 +264,15 @@ func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fi p.P(`if !(`, variableName, ` > `, fv.IntGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`be greater than '%d'`, fv.GetIntGt()) - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errors.Types_INT_GT, fieldName, errorStr, fv) p.Out() p.P(`}`) } if fv.IntLt != nil { p.P(`if !(`, variableName, ` < `, fv.IntLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() - errorStr := fmt.Sprintf(`be less than '%d'`, fv.GetIntLt()) - p.generateErrorString(variableName, fieldName, errorStr, fv) + errorStr := fmt.Sprintf(`be lesser than '%d'`, fv.GetIntLt()) + p.generateErrorString(variableName, errors.Types_INT_LT, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -285,8 +282,8 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, if fv.LengthGt != nil { p.P(`if !( len(`, variableName, `) == `, fv.LengthGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() - errorStr := fmt.Sprintf(`length be greater than '%d'`, fv.GetLengthGt()) - p.generateErrorString(variableName, fieldName, errorStr, fv) + errorStr := fmt.Sprintf(` be longer than '%d' elements`, fv.GetLengthGt()) + p.generateErrorString(variableName, errors.Types_LENGTH_GT, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -294,8 +291,8 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, if fv.LengthLt != nil { p.P(`if !( len(`, variableName, `) == `, fv.LengthLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() - errorStr := fmt.Sprintf(`length be less than '%d'`, fv.GetLengthLt()) - p.generateErrorString(variableName, fieldName, errorStr, fv) + errorStr := fmt.Sprintf(` be shorter than '%d' elements`, fv.GetLengthLt()) + p.generateErrorString(variableName, errors.Types_LENGTH_LT, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -303,8 +300,8 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, if fv.LengthEq != nil { p.P(`if !( len(`, variableName, `) == `, fv.LengthEq, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() - errorStr := fmt.Sprintf(`length be not equal '%d'`, fv.GetLengthEq()) - p.generateErrorString(variableName, fieldName, errorStr, fv) + errorStr := fmt.Sprintf(` have length equal to '%d' elements`, fv.GetLengthEq()) + p.generateErrorString(variableName, errors.Types_LENGTH_EQ, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -350,22 +347,26 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, // Generate the constraint checking code. errorStr := "" compareStr := "" + var errType errors.Types if fv.FloatGt != nil || fv.FloatGte != nil { compareStr = fmt.Sprint(`if !(`, variableName) if lowerIsStrict { + errType = errors.Types_FLOAT_GT errorStr = fmt.Sprintf(`be strictly greater than '%g'`, fv.GetFloatGt()) if fv.FloatEpsilon != nil { + errType = errors.Types_FLOAT_ELIPSON errorStr += fmt.Sprintf(` with a tolerance of '%g'`, fv.GetFloatEpsilon()) compareStr += fmt.Sprint(` + `, fv.GetFloatEpsilon()) } compareStr += fmt.Sprint(` > `, fv.GetFloatGt(), `) {`) } else { + errType = errors.Types_FLOAT_GTE errorStr = fmt.Sprintf(`be greater than or equal to '%g'`, fv.GetFloatGte()) compareStr += fmt.Sprint(` >= `, fv.GetFloatGte(), `) {`) } p.P(compareStr) p.In() - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errType, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -373,19 +374,22 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, if fv.FloatLt != nil || fv.FloatLte != nil { compareStr = fmt.Sprint(`if !(`, variableName) if upperIsStrict { + errType = errors.Types_FLOAT_LT errorStr = fmt.Sprintf(`be strictly lower than '%g'`, fv.GetFloatLt()) if fv.FloatEpsilon != nil { + errType = errors.Types_FLOAT_ELIPSON errorStr += fmt.Sprintf(` with a tolerance of '%g'`, fv.GetFloatEpsilon()) compareStr += fmt.Sprint(` - `, fv.GetFloatEpsilon()) } compareStr += fmt.Sprint(` < `, fv.GetFloatLt(), `) {`) } else { + errType = errors.Types_FLOAT_LTE errorStr = fmt.Sprintf(`be lower than or equal to '%g'`, fv.GetFloatLte()) compareStr += fmt.Sprint(` <= `, fv.GetFloatLte(), `) {`) } p.P(compareStr) p.In() - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errType, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -395,8 +399,8 @@ func (p *plugin) generateStringValidator(variableName string, ccTypeName string, if fv.Regex != nil { p.P(`if !`, p.regexName(ccTypeName, fieldName), `.MatchString(`, variableName, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() - errorStr := "be a string conforming to regex " + strconv.Quote(fv.GetRegex()) - p.generateErrorString(variableName, fieldName, errorStr, fv) + errorStr := "be a string conforming to the regex " + strconv.Quote(fv.GetRegex()) + p.generateErrorString(variableName, errors.Types_STRING_REGEX, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -404,7 +408,7 @@ func (p *plugin) generateStringValidator(variableName string, ccTypeName string, p.P(`if (`, variableName, ` == "" ) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := "not be an empty string" - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errors.Types_STRING_NOT_EMPTY, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -421,7 +425,7 @@ func (p *plugin) generateRepeatedCountValidator(variableName string, ccTypeName p.P(compareStr) p.In() errorStr := fmt.Sprint(`contain at least `, fv.GetRepeatedCountMin(), ` elements`) - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errors.Types_REPEATED_COUNT_MIN, fieldName, errorStr, fv) p.Out() p.P(`}`) } @@ -430,19 +434,18 @@ func (p *plugin) generateRepeatedCountValidator(variableName string, ccTypeName p.P(compareStr) p.In() errorStr := fmt.Sprint(`contain at most `, fv.GetRepeatedCountMax(), ` elements`) - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errors.Types_REPEATED_COUNT_MAX, fieldName, errorStr, fv) p.Out() p.P(`}`) } } -func (p *plugin) generateErrorString(variableName string, fieldName string, specificError string, fv *validator.FieldValidator) { +func (p *plugin) generateErrorString(variableName string, errType errors.Types, fieldName string, specificError string, fv *validator.FieldValidator) { if fv.GetHumanError() == "" { - p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`value '%v' must ", specificError, "`", `, `, variableName, `))`) + p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.errorPkg.Use(), `.Types_`, errType.String(), `, `, p.fmtPkg.Use(), ".Errorf( `field must ", specificError, "`", `))`) } else { p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`", fv.GetHumanError(), "`))") } - } func (p *plugin) fieldIsProto3Map(file *generator.FileDescriptor, message *generator.Descriptor, field *descriptor.FieldDescriptorProto) bool { diff --git a/util/util.go b/util/util.go index 632ccf8..1304197 100644 --- a/util/util.go +++ b/util/util.go @@ -31,28 +31,6 @@ func CallValidatorIfExists(candidate interface{}, topLevelPath string, fullPaths return nil } -type fieldError struct { - fieldStack []string - nestedErr error -} - -// Error returns the error as a string -func (f *fieldError) Error() string { - return "invalid field " + strings.Join(f.fieldStack, ".") + ": " + f.nestedErr.Error() -} - -// FieldError wraps a given Validator error providing a message call stack. -func FieldError(fieldName string, err error) error { - if fErr, ok := err.(*fieldError); ok { - fErr.fieldStack = append([]string{fieldName}, fErr.fieldStack...) - return err - } - return &fieldError{ - fieldStack: []string{fieldName}, - nestedErr: err, - } -} - // GetFieldsToValidate extracts the names of fields for the corresponding fieldmasks. // If the fieldmask is empty, all the fields are returned. func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { From a7444086a562a593f7402bdebb7bd94b7c801506 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Mon, 7 Jan 2019 18:08:31 +0100 Subject: [PATCH 09/17] Support passing fields to sub-messages --- errors/errors.go | 2 +- errors/errors_test.go | 8 ++++++- plugin/plugin.go | 4 ++-- util/util.go | 34 +++++++++++++++++++++++++++++ util/util_test.go | 51 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 93 insertions(+), 6 deletions(-) diff --git a/errors/errors.go b/errors/errors.go index 92461f4..b066b1c 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -42,7 +42,7 @@ func GetType(err string) string { if len(s) != 3 { return "" } - return strings.Replace(s[0], "FIELD_ERROR_TYPE", "Types", 1) + return strings.Replace(s[0], "FIELD_ERROR_TYPE_", "", 1) } // FieldError wraps a given Validator error providing a message call stack. diff --git a/errors/errors_test.go b/errors/errors_test.go index 50f366f..296c0ce 100644 --- a/errors/errors_test.go +++ b/errors/errors_test.go @@ -6,7 +6,7 @@ func TestErrorMethods(t *testing.T) { errString := "FIELD_ERROR_TYPE_INT_GT: SomeInt: field must be greater than '500'" errType := GetType(errString) - if errType != "Types_INT_GT" { + if errType != "INT_GT" { t.Fatal(t) } @@ -19,4 +19,10 @@ func TestErrorMethods(t *testing.T) { if errDesc != "field must be greater than '500'" { t.Fatal(t) } + + nilErrType := GetType("") + if nilErrType != "" { + t.Fatal(t) + } + } diff --git a/plugin/plugin.go b/plugin/plugin.go index 300bd65..9c31bc9 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -227,13 +227,13 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } } if nullable { - p.P(`if (`, variableName, ` != nil) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", paths)){`) + p.P(`if (`, variableName, ` != nil) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)){`) p.In() } else { // non-nullable fields in proto3 store actual structs, we need pointers to operate on interfaces variableName = "&(" + variableName + ")" } - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,"`, variableName, `", paths ); err != nil {`) + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetTopNameForField("`, variableName, `", this), paths ); err != nil {`) p.In() p.P(`return err`) p.Out() diff --git a/util/util.go b/util/util.go index 1304197..28392b5 100644 --- a/util/util.go +++ b/util/util.go @@ -66,7 +66,11 @@ func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { // ShouldBeValidated checks if the given field is a part of the list of fields to be validated. // This list is created using "GetFieldsToValidate". +// If no fields are provided, it returns true. func ShouldBeValidated(name string, fieldNames []string) bool { + if len(fieldNames) == 0 { + return true + } // The name as passed by the generator would be in the format this.FieldName s := strings.Split(name, fieldMaskDelimiter) if len(s) != 2 { @@ -81,6 +85,36 @@ func ShouldBeValidated(name string, fieldNames []string) bool { return false } +// GetTopNameForField retrieves the top field name for the field. +// the name is passed as "this.Fieldname" +func GetTopNameForField(name string, i interface{}) string { + if name == "" || i == nil { + return "" + } + names := strings.Split(name, ".") + if len(names) != 2 { + return "" + } + val := reflect.ValueOf(i).Elem() + if !val.IsValid() || val.Type().NumField() == 0 { + return "" + } + for i := 0; i < val.Type().NumField(); i++ { + if names[1] == val.Type().Field(i).Name { + jsonTag := val.Type().Field(i).Tag.Get("json") + if jsonTag == "" || jsonTag == "-" { + return "" + } + s := strings.Split(jsonTag, jsonTagDelimiter) + if len(s) > 2 { + return "" + } + return s[0] + } + } + return "" +} + // getFieldMaskForEmbeddedFields returns a new FieldMask path for fields inside an embedded message. func getFieldMaskForEmbeddedFields(topLevelMask string, paths []string) []string { var subFields strings.Builder diff --git a/util/util_test.go b/util/util_test.go index fd67c63..2ff11dc 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -43,7 +43,6 @@ func (m *TestMessage) Validate(fieldMask []string) error { return nil } func (m *Embedded) Validate(fieldMask []string) error { - fmt.Println(fieldMask) return nil } @@ -141,7 +140,7 @@ func TestShouldBeValidated(t *testing.T) { Name: "NoFieldsToValidate", InputField: "this.SomeInt", ValidFields: []string{}, - ExpectedResult: false, + ExpectedResult: true, }, } { t.Run(tc.Name, func(t *testing.T) { @@ -154,6 +153,54 @@ func TestShouldBeValidated(t *testing.T) { } +func TestGetTopNameForField(t *testing.T) { + for _, tc := range []struct { + Name string + InputTopField string + InputMessage interface{} + ExpectedResult string + }{ + { + Name: "ValidInputs", + InputTopField: "this.SomeInt", + InputMessage: &TestMessage{}, + ExpectedResult: "some_int", + }, + { + Name: "NilMessage", + InputTopField: "this.SomeInt", + InputMessage: nil, + ExpectedResult: "", + }, + { + Name: "MalformedTopField", + InputTopField: "SomeInt", + InputMessage: &TestMessage{}, + ExpectedResult: "", + }, + { + Name: "CustomStruct", + InputTopField: "this.SomeString", + InputMessage: &CustomTagStruct{}, + ExpectedResult: "some_string", + }, + { + Name: "BadStruct", + InputTopField: "this.SomeInt", + InputMessage: &BadStruct{}, + ExpectedResult: "", + }, + } { + t.Run(tc.Name, func(t *testing.T) { + res := GetTopNameForField(tc.InputTopField, tc.InputMessage) + if tc.ExpectedResult != res { + t.Fatal(fmt.Sprintf("Expected %v, received %v", tc.ExpectedResult, res)) + } + }) + } + +} + func TestGetFieldMaskForEmbeddedFields(t *testing.T) { for _, tc := range []struct { Name string From aaa4cafb60ec8ff672fa9adf0971579faf8c4e71 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Tue, 8 Jan 2019 13:15:05 +0100 Subject: [PATCH 10/17] Fix Length validators --- plugin/plugin.go | 9 ++++++--- util/util_test.go | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index 9c31bc9..2c345ae 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -230,7 +230,10 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * p.P(`if (`, variableName, ` != nil) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)){`) p.In() } else { - // non-nullable fields in proto3 store actual structs, we need pointers to operate on interfaces + // Non-nullable fields are created using [(gogoproto.nullable) = false]. + // These fields in proto3 store actual structs, we need pointers to operate on interfaces. + // For these messages, the fieldmask will be ignored since they must exist. + // So the validator of the message will be called regardless of the fieldmask. variableName = "&(" + variableName + ")" } p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetTopNameForField("`, variableName, `", this), paths ); err != nil {`) @@ -280,7 +283,7 @@ func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fi func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv.LengthGt != nil { - p.P(`if !( len(`, variableName, `) == `, fv.LengthGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !( len(`, variableName, `) > `, fv.LengthGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(` be longer than '%d' elements`, fv.GetLengthGt()) p.generateErrorString(variableName, errors.Types_LENGTH_GT, fieldName, errorStr, fv) @@ -289,7 +292,7 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, } if fv.LengthLt != nil { - p.P(`if !( len(`, variableName, `) == `, fv.LengthLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !( len(`, variableName, `) < `, fv.LengthLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(` be shorter than '%d' elements`, fv.GetLengthLt()) p.generateErrorString(variableName, errors.Types_LENGTH_LT, fieldName, errorStr, fv) diff --git a/util/util_test.go b/util/util_test.go index 2ff11dc..4839aa1 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -269,9 +269,11 @@ func TestCallIfValidatorExists(t *testing.T) { FullPaths: completeEmbeddedFieldMask, }, } { - err := CallValidatorIfExists(tc.Message, tc.TopLevelPath, tc.FullPaths) - if err != nil { - t.Fatal("Validator not called") - } + t.Run(tc.Name, func(t *testing.T) { + err := CallValidatorIfExists(tc.Message, tc.TopLevelPath, tc.FullPaths) + if err != nil { + t.Fatal("Validator not called") + } + }) } } From 3014fcae8560483ad412dc28704a2850074dd226 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Tue, 8 Jan 2019 14:01:26 +0100 Subject: [PATCH 11/17] Test generic functionality with nesting --- test/generic/generic.proto | 32 +++ test/generic/generic_test.go | 437 +++++++++++++++++++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 test/generic/generic.proto create mode 100644 test/generic/generic_test.go diff --git a/test/generic/generic.proto b/test/generic/generic.proto new file mode 100644 index 0000000..e7fd5b3 --- /dev/null +++ b/test/generic/generic.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; +package validatortest; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; +import "google/protobuf/field_mask.proto"; +import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; + +// InnerEmbedded is the innermost embedded structure. +message InnerEmbedded{ + string id = 1 [(validator.field) = {string_not_empty: true}]; + int64 some_int = 2 [(validator.field) = {int_gt: 0, int_lt: 100}]; + google.protobuf.FieldMask field_mask = 6 [(gogoproto.nullable) = false]; +} + +// Embedded is the intermediate embedded structure. +message Embedded { + string identifier = 1 [(validator.field) = {regex: "^[a-z]{2,10}$"}]; + int64 some_int = 2 [(validator.field) = {int_gt: 0, int_lt: 100}]; + InnerEmbedded inner = 3; + google.protobuf.FieldMask field_mask = 4 [(gogoproto.nullable) = false]; +} + +// GenericTestMessage is the top level message that embeds other messages. +message GenericTestMessage { + string some_string = 1 [(validator.field) = {regex: "^.{2,5}$"}]; + bytes some_bytes = 2 [(validator.field) = {length_lt: 5}]; + int64 some_int = 3 [(validator.field) = {int_gt: 500}]; + Embedded embedded_mandatory = 4 [(validator.field) = {msg_exists: true}]; + Embedded embedded_not_mandatory = 5; + google.protobuf.FieldMask field_mask = 6 [(gogoproto.nullable) = false]; +} + diff --git a/test/generic/generic_test.go b/test/generic/generic_test.go new file mode 100644 index 0000000..f07cbdf --- /dev/null +++ b/test/generic/generic_test.go @@ -0,0 +1,437 @@ +package validatortest + +import ( + "fmt" + "testing" + + "github.com/TheThingsIndustries/go-proto-validators/errors" + "github.com/TheThingsIndustries/go-proto-validators/util" +) + +var fullFieldMask = []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.inner", "embedded_mandatory.field_mask", "embedded_mandatory.inner.id", "embedded_mandatory.inner.some_int", "embedded_mandatory.inner.field_mask"} +var outerOnlyFieldMask = []string{"some_string", "some_bytes", "some_int", "field_mask"} +var outerAndMiddleOnlyFieldMask = []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask"} + +func TestWithNilFieldMask(t *testing.T) { + for _, tc := range []struct { + Name string + Message interface{} + FieldMask []string + ErrorExpected bool + ExpectedErrorFieldName string + ExpectedErrorType errors.Types + }{ + { + Name: "OuterWithValid", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1, + }, + }, + }, + FieldMask: nil, + ErrorExpected: false, + }, + { + Name: "OuterWithInValid", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 499, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1, + }, + }, + }, + FieldMask: nil, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeInt", + ExpectedErrorType: errors.Types_INT_GT, + }, + { + Name: "MiddleWithInValid", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 101, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1, + }, + }, + }, + FieldMask: nil, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeInt", + ExpectedErrorType: errors.Types_INT_LT, + }, + { + Name: "InnerWithInValid", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "", + SomeInt: 1, + }, + }, + }, + FieldMask: nil, + ErrorExpected: true, + ExpectedErrorFieldName: "Id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + var err error + if msgVal, ok := tc.Message.(util.Validator); ok { + err = msgVal.Validate(tc.FieldMask) + } else { + t.Fatal("Validator not found for message") + } + if tc.ErrorExpected && err != nil { + if tc.ExpectedErrorFieldName != errors.GetFieldName(err.Error()) || tc.ExpectedErrorType.String() != errors.GetType(err.Error()) { + t.Fatal(err) + } + } else if tc.ErrorExpected && err == nil { + t.Fatal(fmt.Sprintf("Error %s Expected on field %s", tc.ExpectedErrorType, tc.ExpectedErrorFieldName)) + } else if !tc.ErrorExpected && err != nil { + t.Fatal(err) + } else { + // Test Passed. Added for completeness + } + }) + } +} + +func TestWithFullFieldMask(t *testing.T) { + for _, tc := range []struct { + Name string + Message interface{} + FieldMask []string + ErrorExpected bool + ExpectedErrorFieldName string + ExpectedErrorType errors.Types + }{ + { + Name: "ValidMessage", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 1, + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: false, + }, + { + Name: "InnerInvalid", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "", + SomeInt: 1, + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "Id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + }, + { + Name: "MiddleInvalid", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "&*^", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "", + SomeInt: 1, + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "Identifier", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "OuterInvalid", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "&HR", + SomeInt: 1000, + Inner: &InnerEmbedded{ + Id: "", + SomeInt: 1, + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeBytes", + ExpectedErrorType: errors.Types_LENGTH_LT, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + var err error + if msgVal, ok := tc.Message.(util.Validator); ok { + err = msgVal.Validate(tc.FieldMask) + } else { + t.Fatal("Validator not found for message") + } + if tc.ErrorExpected && err != nil { + if tc.ExpectedErrorFieldName != errors.GetFieldName(err.Error()) || tc.ExpectedErrorType.String() != errors.GetType(err.Error()) { + t.Fatal(err) + } + } else if tc.ErrorExpected && err == nil { + t.Fatal(fmt.Sprintf("Error %s Expected on field %s", tc.ExpectedErrorType, tc.ExpectedErrorFieldName)) + } else if !tc.ErrorExpected && err != nil { + t.Fatal(err) + } else { + // Test Passed. Added for completeness + } + }) + } +} + +func TestWithPartialFieldMask(t *testing.T) { + for _, tc := range []struct { + Name string + Message interface{} + FieldMask []string + ErrorExpected bool + ExpectedErrorFieldName string + ExpectedErrorType errors.Types + }{ + { + Name: "ValidOuterWithInvalidMiddleFMNotSet", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "^&^", + SomeInt: 500, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1000, + }, + }, + }, + FieldMask: outerOnlyFieldMask, + ErrorExpected: false, + }, + { + Name: "ValidOuterWithInvalidMiddleFMNotSet1", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "^&^", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 99, + }, + }, + }, + FieldMask: []string{"some_string", "some_bytes", "some_int", "field_mask", "embedded_mandatory", "embedded_mandatory.some_int"}, + ErrorExpected: false, + }, + { + Name: "ValidOuterWithInvalidMiddleFMSet", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "^&^", + SomeInt: 500, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1000, + }, + }, + }, + FieldMask: []string{"some_string", "some_bytes", "some_int", "field_mask", "embedded_mandatory", "embedded_mandatory.identifier"}, + ErrorExpected: true, + ExpectedErrorFieldName: "Identifier", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "ValidOuterWithOnlyOnefield", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{}, + }, + FieldMask: []string{"some_string"}, + ErrorExpected: false, + }, + { + Name: "InvalidOuterWithOnlyOnefield", + Message: &GenericTestMessage{ + SomeString: "&%*$sd", + EmbeddedMandatory: &Embedded{}, + }, + FieldMask: []string{"some_string"}, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeString", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "InvalidOuterWithInvalidMiddle", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "^&^", + SomeInt: 500, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1000, + }, + }, + }, + FieldMask: outerOnlyFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeBytes", + ExpectedErrorType: errors.Types_LENGTH_LT, + }, + { + Name: "ValidOuterAndMiddleWithInvalidInnerFMNotSet", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1000, + }, + }, + }, + FieldMask: outerAndMiddleOnlyFieldMask, + ErrorExpected: false, + }, + { + Name: "ValidOuterAndMiddleWithInvalidInnerFMNotSet1", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1000, + }, + }, + }, + FieldMask: []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask", "embedded_mandatory.inner", "embedded_mandatory.inner.id"}, + ErrorExpected: false, + }, + { + Name: "ValidOuterAndMiddleWithInvalidInnerFMSet", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "test-inner", + SomeInt: 1000, + }, + }, + }, + FieldMask: []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask", "embedded_mandatory.inner", "embedded_mandatory.inner.some_int"}, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeInt", + ExpectedErrorType: errors.Types_INT_LT, + }, + { + Name: "ValidOuterAndMiddleWithInvalidInnerFMSet2", + Message: &GenericTestMessage{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + SomeInt: 501, + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "", + SomeInt: 99, + }, + }, + }, + FieldMask: []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask", "embedded_mandatory.inner", "embedded_mandatory.inner.id"}, + ErrorExpected: true, + ExpectedErrorFieldName: "Id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + var err error + if msgVal, ok := tc.Message.(util.Validator); ok { + err = msgVal.Validate(tc.FieldMask) + } else { + t.Fatal("Validator not found for message") + } + if tc.ErrorExpected && err != nil { + if tc.ExpectedErrorFieldName != errors.GetFieldName(err.Error()) || tc.ExpectedErrorType.String() != errors.GetType(err.Error()) { + t.Fatal(err) + } + } else if tc.ErrorExpected && err == nil { + t.Fatal(fmt.Sprintf("Error %s Expected on field %s", tc.ExpectedErrorType, tc.ExpectedErrorFieldName)) + } else if !tc.ErrorExpected && err != nil { + t.Fatal(err) + } else { + // Test Passed. Added for completeness + } + }) + } +} From 9c268875758f7b301b955c222bddb71fe0dff482 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sun, 13 Jan 2019 13:08:41 +0100 Subject: [PATCH 12/17] Add support for repeated types --- plugin/plugin.go | 9 +- test/generic/generic.proto | 10 +- test/generic/generic_test.go | 254 ++++++++++++++++++++++++++++++++++- 3 files changed, 267 insertions(+), 6 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index 2c345ae..020d72e 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -194,7 +194,6 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * if field.IsMessage() || p.validatorWithNonRepeatedConstraint(fieldValidator) { p.P(`for _, item := range `, variableName, `{`) p.In() - variableName = "item" } } else if fieldValidator != nil { if fieldValidator.RepeatedCountMin != nil { @@ -236,7 +235,13 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * // So the validator of the message will be called regardless of the fieldmask. variableName = "&(" + variableName + ")" } - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetTopNameForField("`, variableName, `", this), paths ); err != nil {`) + + if repeated { + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(item,`, p.validatorPkg.Use(), `.GetTopNameForField("`, variableName, `", this), paths ); err != nil {`) + } else { + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetTopNameForField("`, variableName, `", this), paths ); err != nil {`) + } + p.In() p.P(`return err`) p.Out() diff --git a/test/generic/generic.proto b/test/generic/generic.proto index e7fd5b3..40a73c6 100644 --- a/test/generic/generic.proto +++ b/test/generic/generic.proto @@ -27,6 +27,14 @@ message GenericTestMessage { int64 some_int = 3 [(validator.field) = {int_gt: 500}]; Embedded embedded_mandatory = 4 [(validator.field) = {msg_exists: true}]; Embedded embedded_not_mandatory = 5; - google.protobuf.FieldMask field_mask = 6 [(gogoproto.nullable) = false]; + google.protobuf.FieldMask field_mask = 7 [(gogoproto.nullable) = false]; +} + +// GenericTestMessageWithRepeated is a test message with embedded fields. +message GenericTestMessageWithRepeated{ + string some_string = 1 [(validator.field) = {regex: "^.{2,5}$"}]; + bytes some_bytes = 2 [(validator.field) = {length_lt: 5}]; + repeated Embedded embedded_repeated = 3; + repeated Embedded embedded_repeated_with_check = 4 [(validator.field) = {repeated_count_max: 10, repeated_count_min: 1}]; } diff --git a/test/generic/generic_test.go b/test/generic/generic_test.go index f07cbdf..300e708 100644 --- a/test/generic/generic_test.go +++ b/test/generic/generic_test.go @@ -11,6 +11,9 @@ import ( var fullFieldMask = []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.inner", "embedded_mandatory.field_mask", "embedded_mandatory.inner.id", "embedded_mandatory.inner.some_int", "embedded_mandatory.inner.field_mask"} var outerOnlyFieldMask = []string{"some_string", "some_bytes", "some_int", "field_mask"} var outerAndMiddleOnlyFieldMask = []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask"} +var fullFieldMaskWithEmbedded = []string{"some_string", "some_bytes", "embedded_repeated", "embedded_repeated_with_check", "embedded_repeated.identifier", "embedded_repeated.some_int", "embedded_repeated.field_mask", "embedded_repeated.inner", "embedded_repeated.inner.id", "embedded_repeated.inner.some_int", "embedded_repeated.inner.field_mask", "embedded_repeated_with_check.identifier", "embedded_repeated_with_check.some_int", "embedded_repeated_with_check.field_mask", "embedded_repeated_with_check.inner", "embedded_repeated_with_check.inner.id", "embedded_repeated_with_check.inner.some_int", "embedded_repeated_with_check.inner.field_mask"} +var outerOnlyFieldMaskWithEmbedded = []string{"some_string", "some_bytes", "embedded_repeated"} +var outerAndMiddleOnlyFieldMaskWithEmbedded = []string{"some_string", "some_bytes", "embedded_repeated", "embedded_repeated_with_check", "embedded_repeated.identifier", "embedded_repeated.some_int", "embedded_repeated.field_mask", "embedded_repeated_with_check.identifier", "embedded_repeated_with_check.some_int", "embedded_repeated_with_check.field_mask"} func TestWithNilFieldMask(t *testing.T) { for _, tc := range []struct { @@ -40,7 +43,7 @@ func TestWithNilFieldMask(t *testing.T) { ErrorExpected: false, }, { - Name: "OuterWithInValid", + Name: "OuterWithInvalid", Message: &GenericTestMessage{ SomeString: "outer", SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, @@ -60,7 +63,7 @@ func TestWithNilFieldMask(t *testing.T) { ExpectedErrorType: errors.Types_INT_GT, }, { - Name: "MiddleWithInValid", + Name: "MiddleWithInvalid", Message: &GenericTestMessage{ SomeString: "outer", SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, @@ -80,7 +83,7 @@ func TestWithNilFieldMask(t *testing.T) { ExpectedErrorType: errors.Types_INT_LT, }, { - Name: "InnerWithInValid", + Name: "InnerWithInvalid", Message: &GenericTestMessage{ SomeString: "outer", SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, @@ -435,3 +438,248 @@ func TestWithPartialFieldMask(t *testing.T) { }) } } + +func TestWithRepeatedFields(t *testing.T) { + for _, tc := range []struct { + Name string + Message interface{} + FieldMask []string + ErrorExpected bool + ExpectedErrorFieldName string + ExpectedErrorType errors.Types + }{ + { + Name: "ValidWithNilFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + }, + FieldMask: []string{}, + ErrorExpected: false, + }, + { + Name: "InvalidWithNilFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "", + SomeInt: 99, + }, + }, + }, + }, + FieldMask: []string{}, + ErrorExpected: true, + ExpectedErrorFieldName: "Id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + }, + { + Name: "NoFieldWithNilFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: nil, + }, + FieldMask: []string{}, + ErrorExpected: true, + ExpectedErrorFieldName: "EmbeddedRepeatedWithCheck", + ExpectedErrorType: errors.Types_REPEATED_COUNT_MIN, + }, + { + Name: "ValidWithFullFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: false, + }, + { + Name: "InvalidWithFullFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 500, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeInt", + ExpectedErrorType: errors.Types_INT_LT, + }, + { + Name: "InvalidRepeatedWithFullFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 101, + }, + }, + }, + + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: true, + ExpectedErrorFieldName: "SomeInt", + ExpectedErrorType: errors.Types_INT_LT, + }, + { + Name: "InvalidWithPartialFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 500, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + }, + FieldMask: outerOnlyFieldMaskWithEmbedded, + ErrorExpected: false, + }, + { + Name: "InvalidWithPartialFieldMask2", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 99, + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + SomeInt: 99, + Inner: &InnerEmbedded{ + Id: "inner", + SomeInt: 500, + }, + }, + }, + }, + FieldMask: outerAndMiddleOnlyFieldMaskWithEmbedded, + ErrorExpected: false, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + var err error + if msgVal, ok := tc.Message.(util.Validator); ok { + err = msgVal.Validate(tc.FieldMask) + } else { + t.Fatal("Validator not found for message") + } + if tc.ErrorExpected && err != nil { + if tc.ExpectedErrorFieldName != errors.GetFieldName(err.Error()) || tc.ExpectedErrorType.String() != errors.GetType(err.Error()) { + t.Fatal(err) + } + } else if tc.ErrorExpected && err == nil { + t.Fatal(fmt.Sprintf("Error %s Expected on field %s", tc.ExpectedErrorType, tc.ExpectedErrorFieldName)) + } else if !tc.ErrorExpected && err != nil { + t.Fatal(err) + } else { + // Test Passed. Added for completeness + } + }) + } +} From a30d1a753e816a02b8b89465ad6ce1dad25e2d55 Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sat, 26 Jan 2019 20:11:22 +0100 Subject: [PATCH 13/17] Improve error handling --- plugin/plugin.go | 2 +- test/generic/generic.proto | 5 --- util/util.go | 60 ++++++++++++--------------- util/util_test.go | 83 +++++++++++++++++++++++++++----------- 4 files changed, 86 insertions(+), 64 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index 020d72e..8f91bae 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -55,7 +55,7 @@ import ( "strconv" "strings" - "github.com/TheThingsIndustries/go-proto-validators" + validator "github.com/TheThingsIndustries/go-proto-validators" "github.com/TheThingsIndustries/go-proto-validators/errors" "github.com/gogo/protobuf/gogoproto" "github.com/gogo/protobuf/proto" diff --git a/test/generic/generic.proto b/test/generic/generic.proto index 40a73c6..62338ed 100644 --- a/test/generic/generic.proto +++ b/test/generic/generic.proto @@ -8,14 +8,12 @@ import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; // InnerEmbedded is the innermost embedded structure. message InnerEmbedded{ string id = 1 [(validator.field) = {string_not_empty: true}]; - int64 some_int = 2 [(validator.field) = {int_gt: 0, int_lt: 100}]; google.protobuf.FieldMask field_mask = 6 [(gogoproto.nullable) = false]; } // Embedded is the intermediate embedded structure. message Embedded { string identifier = 1 [(validator.field) = {regex: "^[a-z]{2,10}$"}]; - int64 some_int = 2 [(validator.field) = {int_gt: 0, int_lt: 100}]; InnerEmbedded inner = 3; google.protobuf.FieldMask field_mask = 4 [(gogoproto.nullable) = false]; } @@ -23,8 +21,6 @@ message Embedded { // GenericTestMessage is the top level message that embeds other messages. message GenericTestMessage { string some_string = 1 [(validator.field) = {regex: "^.{2,5}$"}]; - bytes some_bytes = 2 [(validator.field) = {length_lt: 5}]; - int64 some_int = 3 [(validator.field) = {int_gt: 500}]; Embedded embedded_mandatory = 4 [(validator.field) = {msg_exists: true}]; Embedded embedded_not_mandatory = 5; google.protobuf.FieldMask field_mask = 7 [(gogoproto.nullable) = false]; @@ -33,7 +29,6 @@ message GenericTestMessage { // GenericTestMessageWithRepeated is a test message with embedded fields. message GenericTestMessageWithRepeated{ string some_string = 1 [(validator.field) = {regex: "^.{2,5}$"}]; - bytes some_bytes = 2 [(validator.field) = {length_lt: 5}]; repeated Embedded embedded_repeated = 3; repeated Embedded embedded_repeated_with_check = 4 [(validator.field) = {repeated_count_max: 10, repeated_count_min: 1}]; } diff --git a/util/util.go b/util/util.go index 28392b5..54523a1 100644 --- a/util/util.go +++ b/util/util.go @@ -12,8 +12,8 @@ const ( ) var ( - errInvalidMessage error = errors.New("Invalid Message") - errEmptyMessage error = errors.New("Message is empty") + errInvalidMessage = errors.New("Invalid Message") + errEmptyMessage = errors.New("Message is empty") ) // Validator is a general interface that allows a message to be validated. @@ -32,13 +32,23 @@ func CallValidatorIfExists(candidate interface{}, topLevelPath string, fullPaths } // GetFieldsToValidate extracts the names of fields for the corresponding fieldmasks. -// If the fieldmask is empty, all the fields are returned. -func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { +// If the fieldmask is empty, an empty map is returned which means that nothing will be validated. +func GetFieldsToValidate(i interface{}, paths []string) (map[string]string, error) { + if len(paths) == 0 { + return map[string]string{}, nil + } val := reflect.ValueOf(i).Elem() if !val.IsValid() || val.Type().NumField() == 0 { - return []string{}, errInvalidMessage + return map[string]string{}, errInvalidMessage } - fields := []string{} + topPaths := []string{} + for _, path := range paths { + s := strings.Split(path, fieldMaskDelimiter) + if len(s) != 0 { + topPaths = append(topPaths, s[0]) + } + } + fields := make(map[string]string) for i := 0; i < val.Type().NumField(); i++ { jsonTag := val.Type().Field(i).Tag.Get("json") if jsonTag == "" || jsonTag == "-" { @@ -46,17 +56,12 @@ func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { } s := strings.Split(jsonTag, jsonTagDelimiter) if len(s) > 2 { - return []string{}, errInvalidMessage - } - // Add all fields to the list to be validated if no fieldmask paths are specified. - if len(paths) == 0 { - fields = append(fields, val.Type().Field(i).Name) - continue + return map[string]string{}, errInvalidMessage } // Add a field if it a part of the supplied list. - for _, st := range paths { + for _, st := range topPaths { if s[0] == st { - fields = append(fields, val.Type().Field(i).Name) + fields[s[0]] = val.Type().Field(i).Name break } } @@ -66,19 +71,9 @@ func GetFieldsToValidate(i interface{}, paths []string) ([]string, error) { // ShouldBeValidated checks if the given field is a part of the list of fields to be validated. // This list is created using "GetFieldsToValidate". -// If no fields are provided, it returns true. -func ShouldBeValidated(name string, fieldNames []string) bool { - if len(fieldNames) == 0 { - return true - } - // The name as passed by the generator would be in the format this.FieldName - s := strings.Split(name, fieldMaskDelimiter) - if len(s) != 2 { - // When it's malformed, validate anyway since it's difficult validate errors here. - return true - } - for _, fieldName := range fieldNames { - if s[1] == fieldName { +func ShouldBeValidated(name string, fields map[string]string) bool { + for _, fieldName := range fields { + if name == fieldName { return true } } @@ -86,7 +81,6 @@ func ShouldBeValidated(name string, fieldNames []string) bool { } // GetTopNameForField retrieves the top field name for the field. -// the name is passed as "this.Fieldname" func GetTopNameForField(name string, i interface{}) string { if name == "" || i == nil { return "" @@ -100,7 +94,7 @@ func GetTopNameForField(name string, i interface{}) string { return "" } for i := 0; i < val.Type().NumField(); i++ { - if names[1] == val.Type().Field(i).Name { + if name == val.Type().Field(i).Name { jsonTag := val.Type().Field(i).Tag.Get("json") if jsonTag == "" || jsonTag == "-" { return "" @@ -122,12 +116,13 @@ func getFieldMaskForEmbeddedFields(topLevelMask string, paths []string) []string for _, path := range paths { subFields.Reset() if path == "" { - continue //Sanity check for empty paths + continue } s := strings.Split(path, fieldMaskDelimiter) if len(s) < 2 || s[0] != topLevelMask { continue } + // Join the rest of the sub-fields back into a single string. for i := 1; i < len(s); i++ { if s[i] == "" { @@ -148,10 +143,7 @@ func getFieldMaskForEmbeddedFields(topLevelMask string, paths []string) []string return embeddedFields } -func GetFieldMaskForRepeatedFields(i interface{}, paths []string) ([]string, error) { - return nil, nil -} - +// GetFieldMaskForOneOfFields ... func GetFieldMaskForOneOfFields(i interface{}, paths []string) ([]string, error) { return nil, nil } diff --git a/util/util_test.go b/util/util_test.go index 4839aa1..468d340 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -var allFields = []string{"SomeString", "SomeInt", "SomeDouble", "SomeRepeated", "SomeEmbedded"} +var allFields = map[string]string{"some_string": "SomeString", "some_int": "SomeInt", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"} var completeFieldMask = []string{"some_string", "some_int", "some_double", "some_repeated", "some_embedded"} var completeEmbeddedFieldMask = []string{"some_string", "some_int", "some_double", "some_repeated", "some_embedded.ids", "some_embedded.ids.version", "some_embedded.ids.version.timestamp", "some_embedded.value", "some_embedded.name"} var embeddedFields = []string{"ids", "ids.version", "ids.version.timestamp", "value", "name"} @@ -52,28 +52,28 @@ func TestGetFieldsToValidate(t *testing.T) { Name string InputMessage interface{} InputFieldMaskPaths []string - ExpectedFields []string + ExpectedFields map[string]string ExpectedError interface{} }{ { Name: "NilFieldMask", InputMessage: &TestMessage{}, InputFieldMaskPaths: nil, - ExpectedFields: allFields, + ExpectedFields: map[string]string{}, ExpectedError: nil, }, { Name: "OneField", InputMessage: &TestMessage{}, InputFieldMaskPaths: []string{"some_int"}, - ExpectedFields: []string{"SomeInt"}, + ExpectedFields: map[string]string{"some_int": "SomeInt"}, ExpectedError: nil, }, { Name: "InvalidFieldMask", InputMessage: &TestMessage{}, InputFieldMaskPaths: []string{"somesome_int"}, - ExpectedFields: []string{}, + ExpectedFields: map[string]string{}, ExpectedError: nil, }, { @@ -87,14 +87,14 @@ func TestGetFieldsToValidate(t *testing.T) { Name: "InvalidStructTag", InputMessage: &BadStruct{}, InputFieldMaskPaths: completeFieldMask, - ExpectedFields: []string{}, + ExpectedFields: map[string]string{}, ExpectedError: errInvalidMessage, }, { Name: "CustomStructTag", InputMessage: &CustomTagStruct{}, InputFieldMaskPaths: completeFieldMask, - ExpectedFields: []string{"SomeString"}, + ExpectedFields: map[string]string{"some_string": "SomeString"}, ExpectedError: nil, }, } { @@ -115,32 +115,32 @@ func TestShouldBeValidated(t *testing.T) { for _, tc := range []struct { Name string InputField string - ValidFields []string + ValidFields map[string]string ExpectedResult bool }{ { Name: "ShouldBeValidated", - InputField: "this.SomeInt", + InputField: "SomeInt", ValidFields: allFields, ExpectedResult: true, }, { - Name: "ShouldNotValidated", - InputField: "this.SomeInt", - ValidFields: []string{"SomeString", "SomeDouble", "SomeRepeated", "SomeEmbedded"}, + Name: "ShouldNotBeValidated", + InputField: "SomeInt", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, ExpectedResult: false, }, { Name: "MalFormedInput", - InputField: "SomeInt", - ValidFields: []string{"SomeString", "SomeDouble", "SomeRepeated", "SomeEmbedded"}, - ExpectedResult: true, + InputField: "this.SomeInt", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, + ExpectedResult: false, }, { Name: "NoFieldsToValidate", - InputField: "this.SomeInt", - ValidFields: []string{}, - ExpectedResult: true, + InputField: "SomeInt", + ValidFields: map[string]string{}, + ExpectedResult: false, }, } { t.Run(tc.Name, func(t *testing.T) { @@ -150,7 +150,6 @@ func TestShouldBeValidated(t *testing.T) { } }) } - } func TestGetTopNameForField(t *testing.T) { @@ -162,31 +161,31 @@ func TestGetTopNameForField(t *testing.T) { }{ { Name: "ValidInputs", - InputTopField: "this.SomeInt", + InputTopField: "SomeInt", InputMessage: &TestMessage{}, ExpectedResult: "some_int", }, { Name: "NilMessage", - InputTopField: "this.SomeInt", + InputTopField: "SomeInt", InputMessage: nil, ExpectedResult: "", }, { Name: "MalformedTopField", - InputTopField: "SomeInt", + InputTopField: "this.SomeInt", InputMessage: &TestMessage{}, ExpectedResult: "", }, { Name: "CustomStruct", - InputTopField: "this.SomeString", + InputTopField: "SomeString", InputMessage: &CustomTagStruct{}, ExpectedResult: "some_string", }, { Name: "BadStruct", - InputTopField: "this.SomeInt", + InputTopField: "SomeInt", InputMessage: &BadStruct{}, ExpectedResult: "", }, @@ -244,6 +243,30 @@ func TestGetFieldMaskForEmbeddedFields(t *testing.T) { InputFieldMask: completeEmbeddedFieldMask, ExpectedFieldMask: embeddedFields, }, + { + Name: "NoMatch", + TopLevelField: "some_embedded", + InputFieldMask: []string{"some_embedded"}, + ExpectedFieldMask: []string{}, + }, + { + Name: "PartialMatch", + TopLevelField: "some_embedded", + InputFieldMask: []string{"some_embedded.ids"}, + ExpectedFieldMask: []string{"ids"}, + }, + { + Name: "EmptyFM", + TopLevelField: "some_embedded", + InputFieldMask: []string{}, + ExpectedFieldMask: []string{}, + }, + { + Name: "OnlyTopField", + TopLevelField: "some_embedded", + InputFieldMask: completeEmbeddedFieldMask, + ExpectedFieldMask: embeddedFields, + }, } { t.Run(tc.Name, func(t *testing.T) { res := getFieldMaskForEmbeddedFields(tc.TopLevelField, tc.InputFieldMask) @@ -268,6 +291,18 @@ func TestCallIfValidatorExists(t *testing.T) { TopLevelPath: "some_embedded", FullPaths: completeEmbeddedFieldMask, }, + { + Name: "NoTopLevelPath", + Message: &Embedded{}, + TopLevelPath: "", + FullPaths: []string{}, + }, + { + Name: "NoFieldMask", + Message: &Embedded{}, + TopLevelPath: "some_embedded", + FullPaths: []string{}, + }, } { t.Run(tc.Name, func(t *testing.T) { err := CallValidatorIfExists(tc.Message, tc.TopLevelPath, tc.FullPaths) From 17f50c419208f960fcc5715eaffa688e371bdfcf Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sun, 27 Jan 2019 12:40:23 +0100 Subject: [PATCH 14/17] Return proto names in errors --- .gitignore | 3 ++ errors/errors.go | 36 ++++++++------- errors/errors_test.go | 2 +- plugin/plugin.go | 10 ++--- util/util.go | 39 ++++++---------- util/util_test.go | 101 +++++++++++++++++++++++++++++++----------- 6 files changed, 116 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index 5f452f6..8809716 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,6 @@ Session.vim .Trash-* # Created by .ignore support plugin (hsz.mobi) + +# Test Coverage files +cover.* diff --git a/errors/errors.go b/errors/errors.go index b066b1c..a0d9b35 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -7,15 +7,14 @@ import ( // ValidatorFieldError is a generic struct that can be used for better error usage in tests and in code. type ValidatorFieldError struct { - fieldStack []string - nestedErr error - fieldName string - errType Types + nestedErr error + fieldName string + errType Types } // Error returns the error as a string func (f *ValidatorFieldError) Error() string { - return fmt.Sprintf("FIELD_ERROR_TYPE_%s: %s: %s", f.errType.String(), strings.Join(f.fieldStack, "."), f.nestedErr.Error()) + return fmt.Sprintf("%s: %s: %s", f.fieldName, f.errType.String(), f.nestedErr.Error()) } // GetFieldName extracts the field name from the error message. @@ -24,39 +23,42 @@ func GetFieldName(err string) string { if len(s) != 3 { return "" } - return s[1] + return s[0] } -// GetErrorDescripton extracts the error stack from the error message. -func GetErrorDescripton(err string) string { +// GetType extracts the errors.Types name from the error message. +func GetType(err string) string { s := strings.Split(err, ": ") if len(s) != 3 { return "" } - return s[2] + return s[1] } -// GetType extracts the errors.Types name from the error message. -func GetType(err string) string { +// GetErrorDescripton extracts the error stack from the error message. +func GetErrorDescripton(err string) string { s := strings.Split(err, ": ") if len(s) != 3 { return "" } - return strings.Replace(s[0], "FIELD_ERROR_TYPE_", "", 1) + return s[2] +} + +// GetErrorWithTopField ... +func GetErrorWithTopField(name string, err error) error { + return fmt.Errorf(fmt.Sprintf("%s.%s", name, err.Error())) } // FieldError wraps a given Validator error providing a message call stack. func FieldError(fieldName string, Type Types, err error) error { if fErr, ok := err.(*ValidatorFieldError); ok { - fErr.fieldStack = append([]string{fieldName}, fErr.fieldStack...) fErr.fieldName = fieldName fErr.errType = Type return err } return &ValidatorFieldError{ - fieldStack: []string{fieldName}, - nestedErr: err, - fieldName: fieldName, - errType: Type, + nestedErr: err, + fieldName: fieldName, + errType: Type, } } diff --git a/errors/errors_test.go b/errors/errors_test.go index 296c0ce..5c8d6df 100644 --- a/errors/errors_test.go +++ b/errors/errors_test.go @@ -3,7 +3,7 @@ package errors import "testing" func TestErrorMethods(t *testing.T) { - errString := "FIELD_ERROR_TYPE_INT_GT: SomeInt: field must be greater than '500'" + errString := "SomeInt: INT_GT: field must be greater than '500'" errType := GetType(errString) if errType != "INT_GT" { diff --git a/plugin/plugin.go b/plugin/plugin.go index 8f91bae..3571f80 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -216,7 +216,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * if nullable && !repeated { p.P(`if nil == `, variableName, `{`) p.In() - p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.errorPkg.Use(), `.Types_`, errors.Types_MSG_EXISTS.String(), `, `, p.fmtPkg.Use(), `.Errorf("message must exist"))`) + p.P(`return `, p.errorPkg.Use(), `.FieldError(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, fieldName, `", toBeValidated),`, p.errorPkg.Use(), `.Types_`, errors.Types_MSG_EXISTS.String(), `, `, p.fmtPkg.Use(), `.Errorf("message must exist"))`) p.Out() p.P(`}`) } else if repeated { @@ -237,13 +237,13 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } if repeated { - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(item,`, p.validatorPkg.Use(), `.GetTopNameForField("`, variableName, `", this), paths ); err != nil {`) + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(item,`, p.validatorPkg.Use(), `.GetProtoNameForField("`, variableName, `", toBeValidated), paths ); err != nil {`) } else { - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetTopNameForField("`, variableName, `", this), paths ); err != nil {`) + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetProtoNameForField("`, variableName, `", toBeValidated), paths ); err != nil {`) } p.In() - p.P(`return err`) + p.P(`return `, p.errorPkg.Use(), `.GetErrorWithTopField(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, variableName, `",toBeValidated),err)`) p.Out() p.P(`}`) if nullable { @@ -450,7 +450,7 @@ func (p *plugin) generateRepeatedCountValidator(variableName string, ccTypeName func (p *plugin) generateErrorString(variableName string, errType errors.Types, fieldName string, specificError string, fv *validator.FieldValidator) { if fv.GetHumanError() == "" { - p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.errorPkg.Use(), `.Types_`, errType.String(), `, `, p.fmtPkg.Use(), ".Errorf( `field must ", specificError, "`", `))`) + p.P(`return `, p.errorPkg.Use(), `.FieldError(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, fieldName, `", toBeValidated),`, p.errorPkg.Use(), `.Types_`, errType.String(), `, `, p.fmtPkg.Use(), ".Errorf( `field must ", specificError, "`", `))`) } else { p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`", fv.GetHumanError(), "`))") } diff --git a/util/util.go b/util/util.go index 54523a1..ee0445c 100644 --- a/util/util.go +++ b/util/util.go @@ -23,7 +23,6 @@ type Validator interface { // CallValidatorIfExists is used to call the validator for the embedded message if it exists. // It generates the fieldmask for the sub-field before calling it. -// The conditional field shouldBeCalled is used to prevent this function from calling the sub validator based on the parent fieldmask. func CallValidatorIfExists(candidate interface{}, topLevelPath string, fullPaths []string) error { if validator, ok := candidate.(Validator); ok { return validator.Validate(getFieldMaskForEmbeddedFields(topLevelPath, fullPaths)) @@ -72,38 +71,28 @@ func GetFieldsToValidate(i interface{}, paths []string) (map[string]string, erro // ShouldBeValidated checks if the given field is a part of the list of fields to be validated. // This list is created using "GetFieldsToValidate". func ShouldBeValidated(name string, fields map[string]string) bool { + names := strings.Split(name, fieldMaskDelimiter) + if len(names) != 2 { + return true + } for _, fieldName := range fields { - if name == fieldName { + if names[1] == fieldName { return true } } return false } -// GetTopNameForField retrieves the top field name for the field. -func GetTopNameForField(name string, i interface{}) string { - if name == "" || i == nil { - return "" - } - names := strings.Split(name, ".") - if len(names) != 2 { - return "" - } - val := reflect.ValueOf(i).Elem() - if !val.IsValid() || val.Type().NumField() == 0 { - return "" +// GetProtoNameForField returns the proto name for a field so that it can be returned in an error. +func GetProtoNameForField(name string, fields map[string]string) string { + field := name + names := strings.Split(name, fieldMaskDelimiter) + if len(names) == 2 { + field = names[1] } - for i := 0; i < val.Type().NumField(); i++ { - if name == val.Type().Field(i).Name { - jsonTag := val.Type().Field(i).Tag.Get("json") - if jsonTag == "" || jsonTag == "-" { - return "" - } - s := strings.Split(jsonTag, jsonTagDelimiter) - if len(s) > 2 { - return "" - } - return s[0] + for protoName, fieldName := range fields { + if field == fieldName { + return protoName } } return "" diff --git a/util/util_test.go b/util/util_test.go index 468d340..f7125cc 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -120,25 +120,25 @@ func TestShouldBeValidated(t *testing.T) { }{ { Name: "ShouldBeValidated", - InputField: "SomeInt", + InputField: "this.SomeInt", ValidFields: allFields, ExpectedResult: true, }, { Name: "ShouldNotBeValidated", - InputField: "SomeInt", + InputField: "this.SomeInt", ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, ExpectedResult: false, }, { Name: "MalFormedInput", - InputField: "this.SomeInt", + InputField: "SomeInt", ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, - ExpectedResult: false, + ExpectedResult: true, }, { Name: "NoFieldsToValidate", - InputField: "SomeInt", + InputField: "this.SomeInt", ValidFields: map[string]string{}, ExpectedResult: false, }, @@ -152,54 +152,101 @@ func TestShouldBeValidated(t *testing.T) { } } -func TestGetTopNameForField(t *testing.T) { +func TestGetProtoNameForField(t *testing.T) { for _, tc := range []struct { Name string - InputTopField string - InputMessage interface{} + InputField string + ValidFields map[string]string ExpectedResult string }{ { - Name: "ValidInputs", - InputTopField: "SomeInt", - InputMessage: &TestMessage{}, + Name: "Exists", + InputField: "SomeInt", + ValidFields: allFields, ExpectedResult: "some_int", }, { - Name: "NilMessage", - InputTopField: "SomeInt", - InputMessage: nil, + Name: "NoMatch", + InputField: "SomeInt", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, ExpectedResult: "", }, { - Name: "MalformedTopField", - InputTopField: "this.SomeInt", - InputMessage: &TestMessage{}, - ExpectedResult: "", + Name: "ExtendedName", + InputField: "this.SomeString", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, + ExpectedResult: "some_string", }, { - Name: "CustomStruct", - InputTopField: "SomeString", - InputMessage: &CustomTagStruct{}, - ExpectedResult: "some_string", + Name: "MalFormedInput", + InputField: "this.", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, + ExpectedResult: "", }, { - Name: "BadStruct", - InputTopField: "SomeInt", - InputMessage: &BadStruct{}, + Name: "NoFieldsToValidate", + InputField: "SomeInt", + ValidFields: map[string]string{}, ExpectedResult: "", }, } { t.Run(tc.Name, func(t *testing.T) { - res := GetTopNameForField(tc.InputTopField, tc.InputMessage) + res := GetProtoNameForField(tc.InputField, tc.ValidFields) if tc.ExpectedResult != res { t.Fatal(fmt.Sprintf("Expected %v, received %v", tc.ExpectedResult, res)) } }) } - } +// func TestGetTopNameForField(t *testing.T) { +// for _, tc := range []struct { +// Name string +// InputTopField string +// InputMessage interface{} +// ExpectedResult string +// }{ +// { +// Name: "ValidInputs", +// InputTopField: "this.SomeInt", +// InputMessage: &TestMessage{}, +// ExpectedResult: "some_int", +// }, +// { +// Name: "NilMessage", +// InputTopField: "this.SomeInt", +// InputMessage: nil, +// ExpectedResult: "", +// }, +// { +// Name: "MalformedTopField", +// InputTopField: "SomeInt", +// InputMessage: &TestMessage{}, +// ExpectedResult: "", +// }, +// { +// Name: "CustomStruct", +// InputTopField: "this.SomeString", +// InputMessage: &CustomTagStruct{}, +// ExpectedResult: "some_string", +// }, +// { +// Name: "BadStruct", +// InputTopField: "this.SomeInt", +// InputMessage: &BadStruct{}, +// ExpectedResult: "", +// }, +// } { +// t.Run(tc.Name, func(t *testing.T) { +// res := GetTopNameForField(tc.InputTopField, tc.InputMessage) +// if tc.ExpectedResult != res { +// t.Fatal(fmt.Sprintf("Expected %v, received %v", tc.ExpectedResult, res)) +// } +// }) +// } + +// } + func TestGetFieldMaskForEmbeddedFields(t *testing.T) { for _, tc := range []struct { Name string From 839cdc51655094a8057725d3687b1122e5abfe0d Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sun, 27 Jan 2019 16:58:00 +0100 Subject: [PATCH 15/17] Support Repeated Fields with tests --- plugin/plugin.go | 6 +- test/generic/generic_test.go | 257 ++++++++++------------------------- util/util.go | 7 +- util/util_test.go | 6 +- 4 files changed, 81 insertions(+), 195 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index 3571f80..00067c7 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -449,11 +449,7 @@ func (p *plugin) generateRepeatedCountValidator(variableName string, ccTypeName } func (p *plugin) generateErrorString(variableName string, errType errors.Types, fieldName string, specificError string, fv *validator.FieldValidator) { - if fv.GetHumanError() == "" { - p.P(`return `, p.errorPkg.Use(), `.FieldError(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, fieldName, `", toBeValidated),`, p.errorPkg.Use(), `.Types_`, errType.String(), `, `, p.fmtPkg.Use(), ".Errorf( `field must ", specificError, "`", `))`) - } else { - p.P(`return `, p.errorPkg.Use(), `.FieldError("`, fieldName, `",`, p.fmtPkg.Use(), ".Errorf(`", fv.GetHumanError(), "`))") - } + p.P(`return `, p.errorPkg.Use(), `.FieldError(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, fieldName, `", toBeValidated),`, p.errorPkg.Use(), `.Types_`, errType.String(), `, `, p.fmtPkg.Use(), ".Errorf( `field must ", specificError, "`", `))`) } func (p *plugin) fieldIsProto3Map(file *generator.FileDescriptor, message *generator.Descriptor, field *descriptor.FieldDescriptorProto) bool { diff --git a/test/generic/generic_test.go b/test/generic/generic_test.go index 300e708..2c8ceb5 100644 --- a/test/generic/generic_test.go +++ b/test/generic/generic_test.go @@ -8,9 +8,9 @@ import ( "github.com/TheThingsIndustries/go-proto-validators/util" ) -var fullFieldMask = []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.inner", "embedded_mandatory.field_mask", "embedded_mandatory.inner.id", "embedded_mandatory.inner.some_int", "embedded_mandatory.inner.field_mask"} -var outerOnlyFieldMask = []string{"some_string", "some_bytes", "some_int", "field_mask"} -var outerAndMiddleOnlyFieldMask = []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask"} +var fullFieldMask = []string{"some_string", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.inner", "embedded_mandatory.field_mask", "embedded_mandatory.inner.id", "embedded_mandatory.inner.field_mask"} +var outerOnlyFieldMask = []string{"some_string", "field_mask"} +var outerAndMiddleOnlyFieldMask = []string{"some_string", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.field_mask"} var fullFieldMaskWithEmbedded = []string{"some_string", "some_bytes", "embedded_repeated", "embedded_repeated_with_check", "embedded_repeated.identifier", "embedded_repeated.some_int", "embedded_repeated.field_mask", "embedded_repeated.inner", "embedded_repeated.inner.id", "embedded_repeated.inner.some_int", "embedded_repeated.inner.field_mask", "embedded_repeated_with_check.identifier", "embedded_repeated_with_check.some_int", "embedded_repeated_with_check.field_mask", "embedded_repeated_with_check.inner", "embedded_repeated_with_check.inner.id", "embedded_repeated_with_check.inner.some_int", "embedded_repeated_with_check.inner.field_mask"} var outerOnlyFieldMaskWithEmbedded = []string{"some_string", "some_bytes", "embedded_repeated"} var outerAndMiddleOnlyFieldMaskWithEmbedded = []string{"some_string", "some_bytes", "embedded_repeated", "embedded_repeated_with_check", "embedded_repeated.identifier", "embedded_repeated.some_int", "embedded_repeated.field_mask", "embedded_repeated_with_check.identifier", "embedded_repeated_with_check.some_int", "embedded_repeated_with_check.field_mask"} @@ -28,14 +28,10 @@ func TestWithNilFieldMask(t *testing.T) { Name: "OuterWithValid", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1, + Id: "test-inner", }, }, }, @@ -45,62 +41,44 @@ func TestWithNilFieldMask(t *testing.T) { { Name: "OuterWithInvalid", Message: &GenericTestMessage{ - SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 499, + SomeString: "&*^", EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1, + Id: "test-inner", }, }, }, - FieldMask: nil, - ErrorExpected: true, - ExpectedErrorFieldName: "SomeInt", - ExpectedErrorType: errors.Types_INT_GT, + FieldMask: nil, + ErrorExpected: false, }, { Name: "MiddleWithInvalid", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ - Identifier: "middle", - SomeInt: 101, + Identifier: "&^&", Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1, + Id: "test-inner", }, }, }, - FieldMask: nil, - ErrorExpected: true, - ExpectedErrorFieldName: "SomeInt", - ExpectedErrorType: errors.Types_INT_LT, + FieldMask: nil, + ErrorExpected: false, }, { Name: "InnerWithInvalid", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "", - SomeInt: 1, + Id: "", }, }, }, - FieldMask: nil, - ErrorExpected: true, - ExpectedErrorFieldName: "Id", - ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + FieldMask: nil, + ErrorExpected: false, }, } { t.Run(tc.Name, func(t *testing.T) { @@ -138,14 +116,10 @@ func TestWithFullFieldMask(t *testing.T) { Name: "ValidMessage", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 1, + Id: "inner", }, }, }, @@ -156,61 +130,49 @@ func TestWithFullFieldMask(t *testing.T) { Name: "InnerInvalid", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "", - SomeInt: 1, + Id: "", }, }, }, FieldMask: fullFieldMask, ErrorExpected: true, - ExpectedErrorFieldName: "Id", + ExpectedErrorFieldName: "embedded_mandatory.inner.id", ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, }, { Name: "MiddleInvalid", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "&*^", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "", - SomeInt: 1, + Id: "", }, }, }, FieldMask: fullFieldMask, ErrorExpected: true, - ExpectedErrorFieldName: "Identifier", + ExpectedErrorFieldName: "embedded_mandatory.identifier", ExpectedErrorType: errors.Types_STRING_REGEX, }, { Name: "OuterInvalid", Message: &GenericTestMessage{ - SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, - SomeInt: 501, + SomeString: "&*sfsafsdfsfsdfdsfsdfsdf", EmbeddedMandatory: &Embedded{ Identifier: "&HR", - SomeInt: 1000, Inner: &InnerEmbedded{ - Id: "", - SomeInt: 1, + Id: "", }, }, }, FieldMask: fullFieldMask, ErrorExpected: true, - ExpectedErrorFieldName: "SomeBytes", - ExpectedErrorType: errors.Types_LENGTH_LT, + ExpectedErrorFieldName: "some_string", + ExpectedErrorType: errors.Types_STRING_REGEX, }, } { t.Run(tc.Name, func(t *testing.T) { @@ -248,14 +210,10 @@ func TestWithPartialFieldMask(t *testing.T) { Name: "ValidOuterWithInvalidMiddleFMNotSet", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "^&^", - SomeInt: 500, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1000, + Id: "test-inner", }, }, }, @@ -266,38 +224,30 @@ func TestWithPartialFieldMask(t *testing.T) { Name: "ValidOuterWithInvalidMiddleFMNotSet1", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ - Identifier: "^&^", - SomeInt: 99, + Identifier: "^&jfskfjdfjsdkfaf;sd^", Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 99, + Id: "test-inner", }, }, }, - FieldMask: []string{"some_string", "some_bytes", "some_int", "field_mask", "embedded_mandatory", "embedded_mandatory.some_int"}, + FieldMask: []string{"some_string", "field_mask", "embedded_mandatory", "embedded_mandatory.some_string"}, ErrorExpected: false, }, { Name: "ValidOuterWithInvalidMiddleFMSet", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "^&^", - SomeInt: 500, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1000, + Id: "test-inner", }, }, }, - FieldMask: []string{"some_string", "some_bytes", "some_int", "field_mask", "embedded_mandatory", "embedded_mandatory.identifier"}, + FieldMask: []string{"some_string", "field_mask", "embedded_mandatory", "embedded_mandatory.identifier"}, ErrorExpected: true, - ExpectedErrorFieldName: "Identifier", + ExpectedErrorFieldName: "embedded_mandatory.identifier", ExpectedErrorType: errors.Types_STRING_REGEX, }, { @@ -317,41 +267,33 @@ func TestWithPartialFieldMask(t *testing.T) { }, FieldMask: []string{"some_string"}, ErrorExpected: true, - ExpectedErrorFieldName: "SomeString", + ExpectedErrorFieldName: "some_string", ExpectedErrorType: errors.Types_STRING_REGEX, }, { Name: "InvalidOuterWithInvalidMiddle", Message: &GenericTestMessage{ - SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, - SomeInt: 501, + SomeString: "*&(&*#&$$", EmbeddedMandatory: &Embedded{ Identifier: "^&^", - SomeInt: 500, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1000, + Id: "test-inner", }, }, }, FieldMask: outerOnlyFieldMask, ErrorExpected: true, - ExpectedErrorFieldName: "SomeBytes", - ExpectedErrorType: errors.Types_LENGTH_LT, + ExpectedErrorFieldName: "some_string", + ExpectedErrorType: errors.Types_STRING_REGEX, }, { Name: "ValidOuterAndMiddleWithInvalidInnerFMNotSet", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1000, + Id: "test-inner", }, }, }, @@ -362,58 +304,30 @@ func TestWithPartialFieldMask(t *testing.T) { Name: "ValidOuterAndMiddleWithInvalidInnerFMNotSet1", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1000, + Id: "test-inner", }, }, }, - FieldMask: []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask", "embedded_mandatory.inner", "embedded_mandatory.inner.id"}, + FieldMask: []string{"some_string", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.field_mask", "embedded_mandatory.inner", "embedded_mandatory.inner.id"}, ErrorExpected: false, }, { Name: "ValidOuterAndMiddleWithInvalidInnerFMSet", Message: &GenericTestMessage{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, EmbeddedMandatory: &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "test-inner", - SomeInt: 1000, + Id: "", }, }, }, - FieldMask: []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask", "embedded_mandatory.inner", "embedded_mandatory.inner.some_int"}, + FieldMask: []string{"some_string", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.field_mask", "embedded_mandatory.inner.id"}, ErrorExpected: true, - ExpectedErrorFieldName: "SomeInt", - ExpectedErrorType: errors.Types_INT_LT, - }, - { - Name: "ValidOuterAndMiddleWithInvalidInnerFMSet2", - Message: &GenericTestMessage{ - SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, - SomeInt: 501, - EmbeddedMandatory: &Embedded{ - Identifier: "middle", - SomeInt: 99, - Inner: &InnerEmbedded{ - Id: "", - SomeInt: 99, - }, - }, - }, - FieldMask: []string{"some_string", "some_bytes", "some_int", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.some_int", "embedded_mandatory.field_mask", "embedded_mandatory.inner", "embedded_mandatory.inner.id"}, - ErrorExpected: true, - ExpectedErrorFieldName: "Id", + ExpectedErrorFieldName: "embedded_mandatory.inner.id", ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, }, } { @@ -452,15 +366,12 @@ func TestWithRepeatedFields(t *testing.T) { Name: "ValidWithNilFieldMask", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: nil, EmbeddedRepeatedWithCheck: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, }, @@ -472,59 +383,48 @@ func TestWithRepeatedFields(t *testing.T) { Name: "InvalidWithNilFieldMask", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: nil, EmbeddedRepeatedWithCheck: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "", - SomeInt: 99, + Id: "", }, }, }, }, - FieldMask: []string{}, - ErrorExpected: true, - ExpectedErrorFieldName: "Id", - ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + FieldMask: nil, + ErrorExpected: false, }, { Name: "NoFieldWithNilFieldMask", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: nil, EmbeddedRepeatedWithCheck: nil, }, FieldMask: []string{}, ErrorExpected: true, - ExpectedErrorFieldName: "EmbeddedRepeatedWithCheck", + ExpectedErrorFieldName: "embedded_repeated_with_check", ExpectedErrorType: errors.Types_REPEATED_COUNT_MIN, }, { Name: "ValidWithFullFieldMask", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, }, EmbeddedRepeatedWithCheck: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, }, @@ -536,53 +436,43 @@ func TestWithRepeatedFields(t *testing.T) { Name: "InvalidWithFullFieldMask", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "", }, }, }, EmbeddedRepeatedWithCheck: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 500, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, }, }, FieldMask: fullFieldMaskWithEmbedded, ErrorExpected: true, - ExpectedErrorFieldName: "SomeInt", - ExpectedErrorType: errors.Types_INT_LT, + ExpectedErrorFieldName: "embedded_repeated.inner.id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, }, { Name: "InvalidRepeatedWithFullFieldMask", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 101, + Id: "inner", }, }, }, @@ -590,41 +480,34 @@ func TestWithRepeatedFields(t *testing.T) { EmbeddedRepeatedWithCheck: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "", }, }, }, }, FieldMask: fullFieldMaskWithEmbedded, ErrorExpected: true, - ExpectedErrorFieldName: "SomeInt", - ExpectedErrorType: errors.Types_INT_LT, + ExpectedErrorFieldName: "embedded_repeated_with_check.inner.id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, }, { Name: "InvalidWithPartialFieldMask", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, }, EmbeddedRepeatedWithCheck: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 500, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, }, @@ -636,24 +519,19 @@ func TestWithRepeatedFields(t *testing.T) { Name: "InvalidWithPartialFieldMask2", Message: &GenericTestMessageWithRepeated{ SomeString: "outer", - SomeBytes: []byte{0x01, 0x02, 0x03, 0x04}, EmbeddedRepeated: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 99, + Id: "inner", }, }, }, EmbeddedRepeatedWithCheck: []*Embedded{ &Embedded{ Identifier: "middle", - SomeInt: 99, Inner: &InnerEmbedded{ - Id: "inner", - SomeInt: 500, + Id: "inner", }, }, }, @@ -683,3 +561,14 @@ func TestWithRepeatedFields(t *testing.T) { }) } } + +// func TestSomething(t *testing.T) { +// msg := GenericTestMessage{ +// SomeString: "hello", +// EmbeddedMandatory: &Embedded{ +// Identifier: "&*&", +// }, +// } + +// fmt.Println(msg.Validate([]string{"some_string", "embedded_mandatory.identifier"})) +// } diff --git a/util/util.go b/util/util.go index ee0445c..6034781 100644 --- a/util/util.go +++ b/util/util.go @@ -33,9 +33,6 @@ func CallValidatorIfExists(candidate interface{}, topLevelPath string, fullPaths // GetFieldsToValidate extracts the names of fields for the corresponding fieldmasks. // If the fieldmask is empty, an empty map is returned which means that nothing will be validated. func GetFieldsToValidate(i interface{}, paths []string) (map[string]string, error) { - if len(paths) == 0 { - return map[string]string{}, nil - } val := reflect.ValueOf(i).Elem() if !val.IsValid() || val.Type().NumField() == 0 { return map[string]string{}, errInvalidMessage @@ -64,6 +61,10 @@ func GetFieldsToValidate(i interface{}, paths []string) (map[string]string, erro break } } + // Repeated items are checked outside the regular validators and need to be accounted for. + if val.Type().Field(i).Type.Kind() == reflect.Slice { + fields[s[0]] = val.Type().Field(i).Name + } } return fields, nil } diff --git a/util/util_test.go b/util/util_test.go index f7125cc..2dc7a3d 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -59,21 +59,21 @@ func TestGetFieldsToValidate(t *testing.T) { Name: "NilFieldMask", InputMessage: &TestMessage{}, InputFieldMaskPaths: nil, - ExpectedFields: map[string]string{}, + ExpectedFields: map[string]string{"some_repeated": "SomeRepeated"}, ExpectedError: nil, }, { Name: "OneField", InputMessage: &TestMessage{}, InputFieldMaskPaths: []string{"some_int"}, - ExpectedFields: map[string]string{"some_int": "SomeInt"}, + ExpectedFields: map[string]string{"some_int": "SomeInt", "some_repeated": "SomeRepeated"}, ExpectedError: nil, }, { Name: "InvalidFieldMask", InputMessage: &TestMessage{}, InputFieldMaskPaths: []string{"somesome_int"}, - ExpectedFields: map[string]string{}, + ExpectedFields: map[string]string{"some_repeated": "SomeRepeated"}, ExpectedError: nil, }, { From 966ac5cf44864b6578c5307449e1869219c5a29f Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Sun, 27 Jan 2019 18:39:39 +0100 Subject: [PATCH 16/17] Improve test coverage --- errors/errors.go | 5 - errors/errors_test.go | 21 +- plugin/plugin.go | 66 +-- test/generic/generic.proto | 2 +- test/generic/generic_test.go | 123 +++++- test/gogo/validator_test.go | 703 ++++++++++++++------------------ test/validator_proto2.proto | 78 ---- test/validator_proto3.proto | 7 +- test/validator_proto3_map.proto | 1 + util/util.go | 5 +- 10 files changed, 479 insertions(+), 532 deletions(-) delete mode 100644 test/validator_proto2.proto diff --git a/errors/errors.go b/errors/errors.go index a0d9b35..b2e329f 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -51,11 +51,6 @@ func GetErrorWithTopField(name string, err error) error { // FieldError wraps a given Validator error providing a message call stack. func FieldError(fieldName string, Type Types, err error) error { - if fErr, ok := err.(*ValidatorFieldError); ok { - fErr.fieldName = fieldName - fErr.errType = Type - return err - } return &ValidatorFieldError{ nestedErr: err, fieldName: fieldName, diff --git a/errors/errors_test.go b/errors/errors_test.go index 5c8d6df..11c2f2c 100644 --- a/errors/errors_test.go +++ b/errors/errors_test.go @@ -1,6 +1,10 @@ package errors -import "testing" +import ( + "errors" + "reflect" + "testing" +) func TestErrorMethods(t *testing.T) { errString := "SomeInt: INT_GT: field must be greater than '500'" @@ -15,6 +19,13 @@ func TestErrorMethods(t *testing.T) { t.Fatal(t) } + if errFieldName := GetFieldName("errString"); errFieldName != "" { + t.Fatal(errFieldName) + } + if errDesc := GetErrorDescripton("errString:Type"); errDesc != "" { + t.Fatal(errDesc) + } + errDesc := GetErrorDescripton(errString) if errDesc != "field must be greater than '500'" { t.Fatal(t) @@ -25,4 +36,12 @@ func TestErrorMethods(t *testing.T) { t.Fatal(t) } + if err := GetErrorWithTopField("top_field", errors.New("inner_field: INT_GT: field must be greater than '500'")); err.Error() != "top_field.inner_field: INT_GT: field must be greater than '500'" { + t.Fatal(err) + } + + if err := FieldError("top_field", Types_INT_GT, errors.New("field must be greater than '500'")); reflect.DeepEqual(err, ValidatorFieldError{nestedErr: errors.New("field must be greater than '500'"), fieldName: "top_field", errType: Types_INT_GT}) { + t.Fatal(err) + } + } diff --git a/plugin/plugin.go b/plugin/plugin.go index 00067c7..b9c929e 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -164,8 +164,10 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * p.P(`return err`) p.Out() p.P(`}`) - p.P("\n") + // TODO: Rewrite this function to create the function in the end after considering all the fields: https://github.com/TheThingsIndustries/go-proto-validators/issues/3 + p.P(`_ = toBeValidated`) + p.P("\n") for _, field := range message.Field { fieldValidator := getFieldValidatorIfAny(field) if fieldValidator == nil && !field.IsMessage() { @@ -174,6 +176,8 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * isOneOf := field.OneofIndex != nil fieldName := p.GetOneOfFieldName(message, field) variableName := "this." + fieldName + itemName := variableName + calledName := variableName repeated := field.IsRepeated() // Golang's proto3 has no concept of unset primitive fields nullable := (gogoproto.IsNullable(field) || !gogoproto.ImportsGoGoProto(file.FileDescriptorProto)) && field.IsMessage() @@ -190,10 +194,11 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * variableName = "oneOfNester." + p.GetOneOfFieldName(message, field) } if repeated { - p.generateRepeatedCountValidator(variableName, ccTypeName, fieldName, fieldValidator) + p.generateRepeatedCountValidator(itemName, variableName, ccTypeName, fieldName, fieldValidator) if field.IsMessage() || p.validatorWithNonRepeatedConstraint(fieldValidator) { p.P(`for _, item := range `, variableName, `{`) p.In() + itemName = "item" } } else if fieldValidator != nil { if fieldValidator.RepeatedCountMin != nil { @@ -204,13 +209,13 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } } if field.IsString() { - p.generateStringValidator(variableName, ccTypeName, fieldName, fieldValidator) + p.generateStringValidator(itemName, variableName, ccTypeName, fieldName, fieldValidator) } else if p.isSupportedInt(field) { - p.generateIntValidator(variableName, ccTypeName, fieldName, fieldValidator) + p.generateIntValidator(itemName, variableName, ccTypeName, fieldName, fieldValidator) } else if p.isSupportedFloat(field) { - p.generateFloatValidator(variableName, ccTypeName, fieldName, fieldValidator) + p.generateFloatValidator(itemName, variableName, ccTypeName, fieldName, fieldValidator) } else if field.IsBytes() { - p.generateLengthValidator(variableName, ccTypeName, fieldName, fieldValidator) + p.generateLengthValidator(itemName, variableName, ccTypeName, fieldName, fieldValidator) } else if field.IsMessage() { if p.validatorWithMessageExists(fieldValidator) { if nullable && !repeated { @@ -225,6 +230,7 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * fmt.Fprintf(os.Stderr, "WARNING: field %v.%v is a nullable=false, validator.msg_exists has no effect\n", ccTypeName, fieldName) } } + if nullable { p.P(`if (`, variableName, ` != nil) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)){`) p.In() @@ -237,13 +243,13 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * } if repeated { - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(item,`, p.validatorPkg.Use(), `.GetProtoNameForField("`, variableName, `", toBeValidated), paths ); err != nil {`) + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(item,`, p.validatorPkg.Use(), `.GetProtoNameForField("`, calledName, `", toBeValidated), paths ); err != nil {`) } else { - p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetProtoNameForField("`, variableName, `", toBeValidated), paths ); err != nil {`) + p.P(`if err := `, p.validatorPkg.Use(), `.CallValidatorIfExists(`, variableName, `,`, p.validatorPkg.Use(), `.GetProtoNameForField("`, calledName, `", toBeValidated), paths ); err != nil {`) } p.In() - p.P(`return `, p.errorPkg.Use(), `.GetErrorWithTopField(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, variableName, `",toBeValidated),err)`) + p.P(`return `, p.errorPkg.Use(), `.GetErrorWithTopField(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, calledName, `",toBeValidated),err)`) p.Out() p.P(`}`) if nullable { @@ -267,9 +273,9 @@ func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message * p.P(`}`) } -func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { +func (p *plugin) generateIntValidator(itemName string, variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv.IntGt != nil { - p.P(`if !(`, variableName, ` > `, fv.IntGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !(`, itemName, ` > `, fv.IntGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`be greater than '%d'`, fv.GetIntGt()) p.generateErrorString(variableName, errors.Types_INT_GT, fieldName, errorStr, fv) @@ -277,7 +283,7 @@ func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fi p.P(`}`) } if fv.IntLt != nil { - p.P(`if !(`, variableName, ` < `, fv.IntLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !(`, itemName, ` < `, fv.IntLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(`be lesser than '%d'`, fv.GetIntLt()) p.generateErrorString(variableName, errors.Types_INT_LT, fieldName, errorStr, fv) @@ -286,9 +292,9 @@ func (p *plugin) generateIntValidator(variableName string, ccTypeName string, fi } } -func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { +func (p *plugin) generateLengthValidator(itemName string, variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv.LengthGt != nil { - p.P(`if !( len(`, variableName, `) > `, fv.LengthGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !( len(`, itemName, `) > `, fv.LengthGt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(` be longer than '%d' elements`, fv.GetLengthGt()) p.generateErrorString(variableName, errors.Types_LENGTH_GT, fieldName, errorStr, fv) @@ -297,7 +303,7 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, } if fv.LengthLt != nil { - p.P(`if !( len(`, variableName, `) < `, fv.LengthLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !( len(`, itemName, `) < `, fv.LengthLt, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(` be shorter than '%d' elements`, fv.GetLengthLt()) p.generateErrorString(variableName, errors.Types_LENGTH_LT, fieldName, errorStr, fv) @@ -306,7 +312,7 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, } if fv.LengthEq != nil { - p.P(`if !( len(`, variableName, `) == `, fv.LengthEq, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !( len(`, itemName, `) == `, fv.LengthEq, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := fmt.Sprintf(` have length equal to '%d' elements`, fv.GetLengthEq()) p.generateErrorString(variableName, errors.Types_LENGTH_EQ, fieldName, errorStr, fv) @@ -316,7 +322,7 @@ func (p *plugin) generateLengthValidator(variableName string, ccTypeName string, } -func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { +func (p *plugin) generateFloatValidator(itemName string, variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { upperIsStrict := true lowerIsStrict := true @@ -357,7 +363,7 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, compareStr := "" var errType errors.Types if fv.FloatGt != nil || fv.FloatGte != nil { - compareStr = fmt.Sprint(`if !(`, variableName) + compareStr = fmt.Sprint(`if !(`, itemName) if lowerIsStrict { errType = errors.Types_FLOAT_GT errorStr = fmt.Sprintf(`be strictly greater than '%g'`, fv.GetFloatGt()) @@ -366,11 +372,11 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, errorStr += fmt.Sprintf(` with a tolerance of '%g'`, fv.GetFloatEpsilon()) compareStr += fmt.Sprint(` + `, fv.GetFloatEpsilon()) } - compareStr += fmt.Sprint(` > `, fv.GetFloatGt(), `) {`) + compareStr += fmt.Sprint(` > `, fv.GetFloatGt(), `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) } else { errType = errors.Types_FLOAT_GTE errorStr = fmt.Sprintf(`be greater than or equal to '%g'`, fv.GetFloatGte()) - compareStr += fmt.Sprint(` >= `, fv.GetFloatGte(), `) {`) + compareStr += fmt.Sprint(` >= `, fv.GetFloatGte(), `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) } p.P(compareStr) p.In() @@ -380,7 +386,7 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, } if fv.FloatLt != nil || fv.FloatLte != nil { - compareStr = fmt.Sprint(`if !(`, variableName) + compareStr = fmt.Sprint(`if !(`, itemName) if upperIsStrict { errType = errors.Types_FLOAT_LT errorStr = fmt.Sprintf(`be strictly lower than '%g'`, fv.GetFloatLt()) @@ -389,11 +395,11 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, errorStr += fmt.Sprintf(` with a tolerance of '%g'`, fv.GetFloatEpsilon()) compareStr += fmt.Sprint(` - `, fv.GetFloatEpsilon()) } - compareStr += fmt.Sprint(` < `, fv.GetFloatLt(), `) {`) + compareStr += fmt.Sprint(` < `, fv.GetFloatLt(), `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) } else { errType = errors.Types_FLOAT_LTE errorStr = fmt.Sprintf(`be lower than or equal to '%g'`, fv.GetFloatLte()) - compareStr += fmt.Sprint(` <= `, fv.GetFloatLte(), `) {`) + compareStr += fmt.Sprint(` <= `, fv.GetFloatLte(), `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) } p.P(compareStr) p.In() @@ -403,9 +409,9 @@ func (p *plugin) generateFloatValidator(variableName string, ccTypeName string, } } -func (p *plugin) generateStringValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { +func (p *plugin) generateStringValidator(itemName string, variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv.Regex != nil { - p.P(`if !`, p.regexName(ccTypeName, fieldName), `.MatchString(`, variableName, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if !`, p.regexName(ccTypeName, fieldName), `.MatchString(`, itemName, `) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := "be a string conforming to the regex " + strconv.Quote(fv.GetRegex()) p.generateErrorString(variableName, errors.Types_STRING_REGEX, fieldName, errorStr, fv) @@ -413,23 +419,23 @@ func (p *plugin) generateStringValidator(variableName string, ccTypeName string, p.P(`}`) } if fv.StringNotEmpty != nil && fv.GetStringNotEmpty() { - p.P(`if (`, variableName, ` == "" ) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) + p.P(`if (`, itemName, ` == "" ) && (`, p.validatorPkg.Use(), `.ShouldBeValidated("`, variableName, `", toBeValidated)) {`) p.In() errorStr := "not be an empty string" p.generateErrorString(variableName, errors.Types_STRING_NOT_EMPTY, fieldName, errorStr, fv) p.Out() p.P(`}`) } - p.generateLengthValidator(variableName, ccTypeName, fieldName, fv) + p.generateLengthValidator(itemName, variableName, ccTypeName, fieldName, fv) } -func (p *plugin) generateRepeatedCountValidator(variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { +func (p *plugin) generateRepeatedCountValidator(itemName string, variableName string, ccTypeName string, fieldName string, fv *validator.FieldValidator) { if fv == nil { return } if fv.RepeatedCountMin != nil { - compareStr := fmt.Sprint(`if len(`, variableName, `) < `, fv.GetRepeatedCountMin(), ` {`) + compareStr := fmt.Sprint(`if len(`, itemName, `) < `, fv.GetRepeatedCountMin(), ` {`) p.P(compareStr) p.In() errorStr := fmt.Sprint(`contain at least `, fv.GetRepeatedCountMin(), ` elements`) @@ -438,7 +444,7 @@ func (p *plugin) generateRepeatedCountValidator(variableName string, ccTypeName p.P(`}`) } if fv.RepeatedCountMax != nil { - compareStr := fmt.Sprint(`if len(`, variableName, `) > `, fv.GetRepeatedCountMax(), ` {`) + compareStr := fmt.Sprint(`if len(`, itemName, `) > `, fv.GetRepeatedCountMax(), ` {`) p.P(compareStr) p.In() errorStr := fmt.Sprint(`contain at most `, fv.GetRepeatedCountMax(), ` elements`) diff --git a/test/generic/generic.proto b/test/generic/generic.proto index 62338ed..ce9c6a5 100644 --- a/test/generic/generic.proto +++ b/test/generic/generic.proto @@ -30,6 +30,6 @@ message GenericTestMessage { message GenericTestMessageWithRepeated{ string some_string = 1 [(validator.field) = {regex: "^.{2,5}$"}]; repeated Embedded embedded_repeated = 3; - repeated Embedded embedded_repeated_with_check = 4 [(validator.field) = {repeated_count_max: 10, repeated_count_min: 1}]; + repeated Embedded embedded_repeated_with_check = 4 [(validator.field) = {repeated_count_max: 3, repeated_count_min: 1}]; } diff --git a/test/generic/generic_test.go b/test/generic/generic_test.go index 2c8ceb5..519424a 100644 --- a/test/generic/generic_test.go +++ b/test/generic/generic_test.go @@ -8,7 +8,7 @@ import ( "github.com/TheThingsIndustries/go-proto-validators/util" ) -var fullFieldMask = []string{"some_string", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.inner", "embedded_mandatory.field_mask", "embedded_mandatory.inner.id", "embedded_mandatory.inner.field_mask"} +var fullFieldMask = []string{"some_string", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.inner", "embedded_mandatory.field_mask", "embedded_mandatory.inner.id", "embedded_mandatory.inner.field_mask", "embedded_not_mandatory.inner.id"} var outerOnlyFieldMask = []string{"some_string", "field_mask"} var outerAndMiddleOnlyFieldMask = []string{"some_string", "embedded_mandatory", "embedded_not_mandatory", "field_mask", "embedded_mandatory.identifier", "embedded_mandatory.field_mask"} var fullFieldMaskWithEmbedded = []string{"some_string", "some_bytes", "embedded_repeated", "embedded_repeated_with_check", "embedded_repeated.identifier", "embedded_repeated.some_int", "embedded_repeated.field_mask", "embedded_repeated.inner", "embedded_repeated.inner.id", "embedded_repeated.inner.some_int", "embedded_repeated.inner.field_mask", "embedded_repeated_with_check.identifier", "embedded_repeated_with_check.some_int", "embedded_repeated_with_check.field_mask", "embedded_repeated_with_check.inner", "embedded_repeated_with_check.inner.id", "embedded_repeated_with_check.inner.some_int", "embedded_repeated_with_check.inner.field_mask"} @@ -142,6 +142,25 @@ func TestWithFullFieldMask(t *testing.T) { ExpectedErrorFieldName: "embedded_mandatory.inner.id", ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, }, + { + Name: "InnerInvalidEmbeddedNotMandatory", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + }, + EmbeddedNotMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_not_mandatory.inner.id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + }, { Name: "MiddleInvalid", Message: &GenericTestMessage{ @@ -259,6 +278,16 @@ func TestWithPartialFieldMask(t *testing.T) { FieldMask: []string{"some_string"}, ErrorExpected: false, }, + { + Name: "MandatoryFields", + Message: &GenericTestMessage{ + SomeString: "outer", + }, + FieldMask: []string{"some_string"}, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_mandatory", + ExpectedErrorType: errors.Types_MSG_EXISTS, + }, { Name: "InvalidOuterWithOnlyOnefield", Message: &GenericTestMessage{ @@ -379,6 +408,42 @@ func TestWithRepeatedFields(t *testing.T) { FieldMask: []string{}, ErrorExpected: false, }, + { + Name: "InvalidOuterOnly", + Message: &GenericTestMessageWithRepeated{ + SomeString: "&*^456", + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + }, + FieldMask: []string{"some_string"}, + ErrorExpected: true, + ExpectedErrorFieldName: "some_string", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "NilEmbedded", + Message: &GenericTestMessageWithRepeated{ + SomeString: "test", + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + }, + FieldMask: []string{"some_string"}, + ErrorExpected: false, + }, { Name: "InvalidWithNilFieldMask", Message: &GenericTestMessageWithRepeated{ @@ -458,6 +523,51 @@ func TestWithRepeatedFields(t *testing.T) { ExpectedErrorFieldName: "embedded_repeated.inner.id", ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, }, + { + Name: "RepeatedInvalidMinFullFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{}, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_repeated_with_check", + ExpectedErrorType: errors.Types_REPEATED_COUNT_MIN, + }, + { + Name: "RepeatedInvalidMaxFullFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + }, + &Embedded{ + Identifier: "middle", + }, + &Embedded{ + Identifier: "middle", + }, + &Embedded{ + Identifier: "middle", + }, + }, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_repeated_with_check", + ExpectedErrorType: errors.Types_REPEATED_COUNT_MAX, + }, { Name: "InvalidRepeatedWithFullFieldMask", Message: &GenericTestMessageWithRepeated{ @@ -561,14 +671,3 @@ func TestWithRepeatedFields(t *testing.T) { }) } } - -// func TestSomething(t *testing.T) { -// msg := GenericTestMessage{ -// SomeString: "hello", -// EmbeddedMandatory: &Embedded{ -// Identifier: "&*&", -// }, -// } - -// fmt.Println(msg.Validate([]string{"some_string", "embedded_mandatory.identifier"})) -// } diff --git a/test/gogo/validator_test.go b/test/gogo/validator_test.go index 5f34906..4284467 100644 --- a/test/gogo/validator_test.go +++ b/test/gogo/validator_test.go @@ -4,10 +4,11 @@ package validatortest import ( - "strings" + fmt "fmt" + "strconv" "testing" - "github.com/stretchr/testify/assert" + "github.com/TheThingsIndustries/go-proto-validators/errors" ) var ( @@ -15,17 +16,17 @@ var ( ) func buildProto3(someString string, someInt uint32, identifier string, someValue int64, someDoubleStrict float64, - someFloatStrict float32, someDouble float64, someFloat float32, nonEmptyString string, repeatedCount uint32, someStringLength string, someBytes []byte) *ValidatorMessage3 { + someFloatStrict float32, someDouble float64, someFloat float32, nonEmptyString string, repeatedCount uint32, someStringLength string, someBytes []byte) ValidatorMessage3 { goodEmbeddedProto3 := &ValidatorMessage3_Embedded{ Identifier: identifier, SomeValue: someValue, } goodProto3 := &ValidatorMessage3{ - SomeString: someString, - SomeStringRep: []string{someString, "xyz34"}, - SomeStringNoQuotes: someString, - SomeStringUnescaped: someString, + SomeString: someString, + SomeStringRep: []string{someString, "xyz34"}, + // SomeStringNoQuotes: someString, + // SomeStringUnescaped: someString, SomeInt: someInt, SomeIntRep: []uint32{someInt, 12, 13, 14, 15, 16}, @@ -64,425 +65,329 @@ func buildProto3(someString string, someInt uint32, identifier string, someValue goodProto3.Repeated = make([]int32, repeatedCount, repeatedCount) - return goodProto3 -} - -func buildProto2(someString string, someInt uint32, identifier string, someValue int64, someDoubleStrict float64, - someFloatStrict float32, someDouble float64, someFloat float32, nonEmptyString string, repeatedCount uint32, someStringLength string, someBytes []byte) *ValidatorMessage { - goodEmbeddedProto2 := &ValidatorMessage_Embedded{ - Identifier: &identifier, - SomeValue: &someValue, - } - - goodProto2 := &ValidatorMessage{ - StringReq: &someString, - StringReqNonNull: someString, - - StringOpt: nil, - StringOptNonNull: someString, - - StringUnescaped: &someString, - - IntReq: &someInt, - IntReqNonNull: someInt, - IntRep: []uint32{someInt, 12, 13, 14, 15, 16}, - IntRepNonNull: []uint32{someInt, 12, 13, 14, 15, 16}, - - EmbeddedReq: goodEmbeddedProto2, - EmbeddedNonNull: *goodEmbeddedProto2, - EmbeddedRep: []*ValidatorMessage_Embedded{goodEmbeddedProto2}, - EmbeddedRepNonNullable: []ValidatorMessage_Embedded{*goodEmbeddedProto2}, - - StrictSomeDoubleReq: &someDoubleStrict, - StrictSomeDoubleReqNonNull: someDoubleStrict, - StrictSomeDoubleRep: []float64{someDoubleStrict, 0.5, 0.55, 0.6}, - StrictSomeDoubleRepNonNull: []float64{someDoubleStrict, 0.5, 0.55, 0.6}, - StrictSomeFloatReq: &someFloatStrict, - StrictSomeFloatReqNonNull: someFloatStrict, - StrictSomeFloatRep: []float32{someFloatStrict, 0.5, 0.55, 0.6}, - StrictSomeFloatRepNonNull: []float32{someFloatStrict, 0.5, 0.55, 0.6}, - - SomeDoubleReq: &someDouble, - SomeDoubleReqNonNull: someDouble, - SomeDoubleRep: []float64{someDouble, 0.5, 0.55, 0.6}, - SomeDoubleRepNonNull: []float64{someDouble, 0.5, 0.55, 0.6}, - SomeFloatReq: &someFloat, - SomeFloatReqNonNull: someFloat, - SomeFloatRep: []float32{someFloat, 0.5, 0.55, 0.6}, - SomeFloatRepNonNull: []float32{someFloat, 0.5, 0.55, 0.6}, - - SomeNonEmptyString: &nonEmptyString, - SomeStringEqReq: &someStringLength, - SomeStringLtReq: &someStringLength, - SomeStringGtReq: &someStringLength, - - SomeBytesLtReq: someBytes, - SomeBytesGtReq: someBytes, - SomeBytesEqReq: someBytes, - RepeatedBaseType: []int32{}, - } - - goodProto2.Repeated = make([]int32, repeatedCount, repeatedCount) - - return goodProto2 + return *goodProto3 } func TestGoodProto3(t *testing.T) { var err error goodProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - err = goodProto3.Validate() - if err != nil { - t.Fatalf("unexpected fail in validator: %v", err) - } -} - -func TestGoodProto2(t *testing.T) { - var err error - goodProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - err = goodProto2.Validate() + err = goodProto3.Validate([]string{"SomeString", "SomeInt", "StrictSomeDouble", "StrictSomeFloat", "SomeDouble", "SomeFloat", "SomeNonEmptyString", "SomeStringLtReq", "SomeStringGtReq", "SomeStringEqReq", "SomeBytesLtReq", "SomeBytesGtReq", "SomeBytesEqReq"}) if err != nil { t.Fatalf("unexpected fail in validator: %v", err) } } -func TestStringRegex(t *testing.T) { - tooLong1Proto3 := buildProto3("toolong", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong1Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooLong2Proto3 := buildProto3("-%ab", 11, "bad#", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong2Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooLong1Proto2 := buildProto2("toolong", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong1Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooLong2Proto2 := buildProto2("-%ab", 11, "bad#", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong2Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } -} - -func TestIntLowerBounds(t *testing.T) { - lowerThan10Proto3 := buildProto3("-%ab", 9, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan10Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan10Proto2 := buildProto2("-%ab", 9, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan10Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", -1, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto2 := buildProto2("-%ab", 11, "abba", -1, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } -} - -func TestIntUpperBounds(t *testing.T) { - greaterThan100Proto3 := buildProto3("-%ab", 11, "abba", 101, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan100Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan100Proto2 := buildProto2("-%ab", 11, "abba", 101, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan100Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } -} - -func TestDoubleStrictLowerBounds(t *testing.T) { - lowerThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.3, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.3, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.300000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan035EpsilonProto3.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } - greaterThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.300000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan035EpsilonProto2.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } -} - -func TestDoubleStrictUpperBounds(t *testing.T) { - greaterThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.70000000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.70000000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.6999999999, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan065EpsilonProto3.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } - lowerThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.6999999999, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan065EpsilonProto2.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } -} - -func TestFloatStrictLowerBounds(t *testing.T) { - lowerThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.2999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.2999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.3000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := greaterThan035EpsilonProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - greaterThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.3000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := greaterThan035EpsilonProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestFloatStrictUpperBounds(t *testing.T) { - greaterThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.7000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.7000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.6999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := lowerThan065EpsilonProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - lowerThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.6999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := lowerThan065EpsilonProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestDoubleNonStrictLowerBounds(t *testing.T) { - lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.2499999, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.2499999, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.25, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.25, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestDoubleNonStrictUpperBounds(t *testing.T) { - higherThan1Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75111111, 0.5, "x", 4, "1234567890", stableBytes) - if higherThan1Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - higherThan1Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75111111, 0.5, "x", 4, "1234567890", stableBytes) - if higherThan1Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestFloatNonStrictLowerBounds(t *testing.T) { - lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.2499999, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.2499999, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.25, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.25, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestFloatNonStrictUpperBounds(t *testing.T) { - higherThan1Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75111111, "x", 4, "1234567890", stableBytes) - if higherThan1Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - higherThan1Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75111111, "x", 4, "1234567890", stableBytes) - if higherThan1Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestStringNonEmpty(t *testing.T) { - emptyStringProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "", 4, "1234567890", stableBytes) - if emptyStringProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - emptyStringProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "", 4, "1234567890", stableBytes) - if emptyStringProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - nonEmptyStringProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := nonEmptyStringProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - nonEmptyStringProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := nonEmptyStringProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) +func TestMessages(t *testing.T) { + test1Proto3 := buildProto3("toolong", 0, "1234", -1, 0.5, 0.5, 0.5, 0.5, "", 1, "1234567890", stableBytes) + for i, tc := range []struct { + FieldMaskPaths []string + ExpectedErrorField string + ExpectedErrorType errors.Types + }{ + { + []string{"SomeString"}, + "SomeString", + errors.Types_STRING_REGEX, + }, + { + []string{"someEmbeddedExists.Identifier"}, + "someEmbeddedExists.Identifier", + errors.Types_STRING_REGEX, + }, + { + []string{"someEmbeddedNonNullable.Identifier"}, + "someEmbeddedNonNullable.Identifier", + errors.Types_STRING_REGEX, + }, + { + []string{"SomeInt"}, + "SomeInt", + errors.Types_INT_GT, + }, + { + []string{"someEmbeddedNonNullable.SomeValue"}, + "someEmbeddedNonNullable.SomeValue", + errors.Types_INT_GT, + }, + { + []string{"SomeNonEmptyString"}, + "SomeNonEmptyString", + errors.Types_STRING_NOT_EMPTY, + }, + { + []string{"SomeNonEmptyString"}, + "SomeNonEmptyString", + errors.Types_STRING_NOT_EMPTY, + }, + { + []string{"Repeated"}, + "Repeated", + errors.Types_REPEATED_COUNT_MIN, + }, + } { + t.Run(fmt.Sprintf("Case%s", strconv.Itoa(i)), func(t *testing.T) { + err := test1Proto3.Validate(tc.FieldMaskPaths) + if err == nil { + t.Fatal(fmt.Sprintf("Error %s Expected on field %s", tc.ExpectedErrorType, tc.ExpectedErrorField)) + } else if tc.ExpectedErrorField != errors.GetFieldName(err.Error()) || tc.ExpectedErrorType.String() != errors.GetType(err.Error()) { + t.Fatal(err) + } + }) + } + + test2Proto3 := buildProto3("-", 9, "bad#", 101, 0.5, 0.5, 0.5, 0.5, "x", 6, "1234567890", stableBytes) + for i, tc := range []struct { + FieldMaskPaths []string + ExpectedErrorField string + ExpectedErrorType errors.Types + }{ + { + []string{"someEmbeddedNonNullable.SomeValue"}, + "someEmbeddedNonNullable.SomeValue", + errors.Types_INT_LT, + }, + { + []string{"Repeated"}, + "Repeated", + errors.Types_REPEATED_COUNT_MAX, + }, + } { + t.Run(fmt.Sprintf("AdditionalCases/%s", strconv.Itoa(i)), func(t *testing.T) { + err := test2Proto3.Validate(tc.FieldMaskPaths) + if err == nil { + t.Fatal(fmt.Sprintf("Error %s Expected on field %s", tc.ExpectedErrorType, tc.ExpectedErrorField)) + } else if tc.ExpectedErrorField != errors.GetFieldName(err.Error()) || tc.ExpectedErrorType.String() != errors.GetType(err.Error()) { + t.Fatal(err) + } + }) } -} + test3Proto3 := buildProto3("-", 9, "bad#", 101, 0.3, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) -func TestRepeatedEltsCount(t *testing.T) { - notEnoughEltsProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 1, "1234567890", stableBytes) - if notEnoughEltsProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - notEnoughEltsProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 1, "1234567890", stableBytes) - if notEnoughEltsProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooManyEltsProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 14, "1234567890", stableBytes) - if tooManyEltsProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooManyEltsProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 14, "1234567890", stableBytes) - if tooManyEltsProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") + // Nil FieldMask + if err := test3Proto3.Validate([]string{}); err != nil { + t.Fatal(err) } - validEltsCountProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := validEltsCountProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - validEltsCountProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := validEltsCountProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} -func TestMsgExist(t *testing.T) { - someProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - someProto3.SomeEmbedded = nil - if err := someProto3.Validate(); err != nil { - t.Fatalf("validate shouldn't fail on missing SomeEmbedded, not annotated") - } - someProto3.SomeEmbeddedExists = nil - if err := someProto3.Validate(); err == nil { - t.Fatalf("expected fail due to lacking SomeEmbeddedExists") - } else if !strings.HasPrefix(err.Error(), "invalid field SomeEmbeddedExists:") { - t.Fatalf("expected fieldError, got '%v'", err) + //Misc + if err := test3Proto3.Validate([]string{"SomeEmbedded"}); err != nil { + t.Fatal(err) } -} - -func TestNestedError3(t *testing.T) { - someProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - someProto3.SomeEmbeddedExists.SomeValue = 101 // should be less than 101 - if err := someProto3.Validate(); err == nil { - t.Fatalf("expected fail due to nested SomeEmbeddedExists.SomeValue being wrong") - } else if !strings.HasPrefix(err.Error(), "invalid field SomeEmbeddedExists.SomeValue:") { - t.Fatalf("expected fieldError, got '%v'", err) + test3Proto3.SomeEmbeddedExists = nil + if err := test3Proto3.Validate([]string{}); err == nil { + t.Fatal("No Error when MSG_EXISTS expected") + } else { + t.Logf("Successfully errored: %s", err) } -} -func TestCustomError_Proto3(t *testing.T) { - someProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - someProto3.CustomErrorInt = 30 - expectedErr := "invalid field CustomErrorInt: My Custom Error" - if err := someProto3.Validate(); err == nil { - t.Fatalf("validate should fail on missing CustomErrorInt") - } else if err.Error() != expectedErr { - t.Fatalf("validation error should be '%s' but was '%s'", expectedErr, err.Error()) + test3Proto3.SomeEmbedded = &ValidatorMessage3_Embedded{Identifier: "&*&#&$(#$*(#"} + if err := test3Proto3.Validate([]string{"SomeEmbedded.Identifier"}); err == nil { + t.Fatal("No Error when STRING_REGEX expected") + } else { + t.Logf("Successfully errored: %s", err) } -} -func TestMapAlwaysPassesUntilFixedProperly(t *testing.T) { - example := &ValidatorMapMessage3{} - if err := example.Validate(); err != nil { - t.Fatalf("map validators should always pass") + test3Proto3.SomeEmbeddedRep = []*ValidatorMessage3_Embedded{&ValidatorMessage3_Embedded{Identifier: "&*&#&$(#$*(#"}} + if err := test3Proto3.Validate([]string{"SomeEmbeddedRep.Identifier"}); err == nil { + t.Fatal("No Error when STRING_REGEX expected") + } else { + t.Logf("Successfully errored: %s", err) } } -func TestOneOf_NestedMessage(t *testing.T) { - example := &OneOfMessage3{ - SomeInt: 30, - Type: &OneOfMessage3_OneMsg{ - OneMsg: &ExternalMsg{ - Identifier: "999", // bad - SomeValue: 99, // good - }, +func TestFloat(t *testing.T) { + for i, tc := range []struct { + Message ValidatorMessage3 + FieldMaskPaths []string + ExpectedErrorField string + ExpectedErrorType errors.Types + }{ + { + buildProto3("toolong", 0, "1234", -1, 0.3, 0.5, 0.5, 0.5, "", 2, "1234567890", stableBytes), + []string{"StrictSomeDouble"}, + "StrictSomeDouble", + errors.Types_FLOAT_ELIPSON, }, - Something: &OneOfMessage3_ThreeInt{ - ThreeInt: 100, // > 20 + { + buildProto3("toolong", 0, "1234", -1, 0.70000000001, 0.5, 0.5, 0.5, "", 2, "1234567890", stableBytes), + []string{"StrictSomeDouble"}, + "StrictSomeDouble", + errors.Types_FLOAT_ELIPSON, }, - } - err := example.Validate() - assert.Error(t, err, "nested message in oneof should fail validation on ExternalMsg") - assert.Contains(t, err.Error(), "OneMsg.Identifier", "error must err on the ExternalMsg.Identifier") -} - -func TestOneOf_NestedInt(t *testing.T) { - example := &OneOfMessage3{ - SomeInt: 30, - Type: &OneOfMessage3_OneMsg{ - OneMsg: &ExternalMsg{ - Identifier: "abba", // good - SomeValue: 99, // good - }, + { + buildProto3("toolong", 0, "1234", -1, 0.2999999, 0.5, 0.5, 0.5, "", 2, "1234567890", stableBytes), + []string{"StrictSomeDouble"}, + "StrictSomeDouble", + errors.Types_FLOAT_ELIPSON, }, - Something: &OneOfMessage3_ThreeInt{ - ThreeInt: 19, // > 20 + { + buildProto3("toolong", 0, "1234", -1, 0.25, 0.5, 0.2499999, 0.5, "", 2, "1234567890", stableBytes), + []string{"SomeDouble"}, + "SomeDouble", + errors.Types_FLOAT_GTE, }, - } - err := example.Validate() - assert.Error(t, err, "nested message in oneof should fail validation on ThreeInt") - assert.Contains(t, err.Error(), "ThreeInt", "error must err on the ThreeInt.ThreeInt") -} - -func TestOneOf_Passes(t *testing.T) { - example := &OneOfMessage3{ - SomeInt: 30, - Type: &OneOfMessage3_OneMsg{ - OneMsg: &ExternalMsg{ - Identifier: "abba", // good - SomeValue: 99, // good - }, + { + buildProto3("toolong", 0, "1234", -1, 0.25, 0.5, 0.75111111, 0.5, "", 2, "1234567890", stableBytes), + []string{"SomeDouble"}, + "SomeDouble", + errors.Types_FLOAT_LTE, }, - Something: &OneOfMessage3_FourInt{ - FourInt: 101, // > 101 + { + buildProto3("toolong", 0, "1234", -1, 0.25, 0.5, 0.75111111, 0.2499999, "", 2, "1234567890", stableBytes), + []string{"SomeFloat"}, + "SomeFloat", + errors.Types_FLOAT_GTE, }, + { + buildProto3("toolong", 0, "1234", -1, 0.25, 0.5, 0.75111111, 0.75111111, "", 2, "1234567890", stableBytes), + []string{"SomeFloat"}, + "SomeFloat", + errors.Types_FLOAT_LTE, + }, + } { + t.Run(fmt.Sprintf("AdditionalCases/%s", strconv.Itoa(i)), func(t *testing.T) { + err := tc.Message.Validate(tc.FieldMaskPaths) + if err == nil { + t.Fatal(fmt.Sprintf("Error %s Expected on field %s", tc.ExpectedErrorType, tc.ExpectedErrorField)) + } else if tc.ExpectedErrorField != errors.GetFieldName(err.Error()) || tc.ExpectedErrorType.String() != errors.GetType(err.Error()) { + t.Fatal(err) + } + }) } - err := example.Validate() - assert.NoError(t, err, "This message should pass all validation") } + +// func TestDoubleStrictUpperBounds(t *testing.T) { +// greaterThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.70000000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) +// if greaterThan065EpsilonProto3.Validate() == nil { +// t.Fatalf("expected fail in validator, but it didn't happen") +// } +// lowerThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.6999999999, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) +// if lowerThan065EpsilonProto3.Validate() != nil { +// t.Fatalf("unexpected fail in validator") +// } +// } + +// func TestFloatStrictLowerBounds(t *testing.T) { +// lowerThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.2999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) +// if lowerThan035EpsilonProto3.Validate() == nil { +// t.Fatalf("expected fail in validator, but it didn't happen") +// } +// greaterThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.3000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) +// if err := greaterThan035EpsilonProto3.Validate(); err != nil { +// t.Fatalf("unexpected fail in validator %v", err) +// } +// } + +// func TestFloatStrictUpperBounds(t *testing.T) { +// greaterThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.7000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) +// if greaterThan065EpsilonProto3.Validate() == nil { +// t.Fatalf("expected fail in validator, but it didn't happen") +// } +// lowerThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.6999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) +// if err := lowerThan065EpsilonProto3.Validate(); err != nil { +// t.Fatalf("unexpected fail in validator %v", err) +// } +// } + +// func TestDoubleNonStrictLowerBounds(t *testing.T) { +// lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.2499999, 0.5, "x", 4, "1234567890", stableBytes) +// if lowerThan0Proto3.Validate() == nil { +// t.Fatalf("expected fail in validator, but it didn't happen") +// } +// equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.25, 0.5, "x", 4, "1234567890", stableBytes) +// if err := equalTo0Proto3.Validate(); err != nil { +// t.Fatalf("unexpected fail in validator %v", err) +// } +// } + +// func TestDoubleNonStrictUpperBounds(t *testing.T) { +// higherThan1Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75111111, 0.5, "x", 4, "1234567890", stableBytes) +// if higherThan1Proto3.Validate() == nil { +// t.Fatalf("expected fail in validator, but it didn't happen") +// } +// equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75, 0.5, "x", 4, "1234567890", stableBytes) +// if err := equalTo0Proto3.Validate(); err != nil { +// t.Fatalf("unexpected fail in validator %v", err) +// } +// } + +// func TestFloatNonStrictLowerBounds(t *testing.T) { +// lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.2499999, "x", 4, "1234567890", stableBytes) +// if lowerThan0Proto3.Validate() == nil { +// t.Fatalf("expected fail in validator, but it didn't happen") +// } +// equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.25, "x", 4, "1234567890", stableBytes) +// if err := equalTo0Proto3.Validate(); err != nil { +// t.Fatalf("unexpected fail in validator %v", err) +// } +// } + +// func TestFloatNonStrictUpperBounds(t *testing.T) { +// higherThan1Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75111111, "x", 4, "1234567890", stableBytes) +// if higherThan1Proto3.Validate() == nil { +// t.Fatalf("expected fail in validator, but it didn't happen") +// } +// equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75, "x", 4, "1234567890", stableBytes) +// if err := equalTo0Proto3.Validate(); err != nil { +// t.Fatalf("unexpected fail in validator %v", err) +// } +// } + +// func TestMapAlwaysPassesUntilFixedProperly(t *testing.T) { +// example := &ValidatorMapMessage3{} +// if err := example.Validate(); err != nil { +// t.Fatalf("map validators should always pass") +// } +// } + +// func TestOneOf_NestedMessage(t *testing.T) { +// example := &OneOfMessage3{ +// SomeInt: 30, +// Type: &OneOfMessage3_OneMsg{ +// OneMsg: &ExternalMsg{ +// Identifier: "999", // bad +// SomeValue: 99, // good +// }, +// }, +// Something: &OneOfMessage3_ThreeInt{ +// ThreeInt: 100, // > 20 +// }, +// } +// err := example.Validate() +// assert.Error(t, err, "nested message in oneof should fail validation on ExternalMsg") +// assert.Contains(t, err.Error(), "OneMsg.Identifier", "error must err on the ExternalMsg.Identifier") +// } + +// func TestOneOf_NestedInt(t *testing.T) { +// example := &OneOfMessage3{ +// SomeInt: 30, +// Type: &OneOfMessage3_OneMsg{ +// OneMsg: &ExternalMsg{ +// Identifier: "abba", // good +// SomeValue: 99, // good +// }, +// }, +// Something: &OneOfMessage3_ThreeInt{ +// ThreeInt: 19, // > 20 +// }, +// } +// err := example.Validate() +// assert.Error(t, err, "nested message in oneof should fail validation on ThreeInt") +// assert.Contains(t, err.Error(), "ThreeInt", "error must err on the ThreeInt.ThreeInt") +// } + +// func TestOneOf_Passes(t *testing.T) { +// example := &OneOfMessage3{ +// SomeInt: 30, +// Type: &OneOfMessage3_OneMsg{ +// OneMsg: &ExternalMsg{ +// Identifier: "abba", // good +// SomeValue: 99, // good +// }, +// }, +// Something: &OneOfMessage3_FourInt{ +// FourInt: 101, // > 101 +// }, +// } +// err := example.Validate() +// assert.NoError(t, err, "This message should pass all validation") +// } diff --git a/test/validator_proto2.proto b/test/validator_proto2.proto deleted file mode 100644 index 344b994..0000000 --- a/test/validator_proto2.proto +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2016 Michal Witkowski. All Rights Reserved. -// See LICENSE for licensing terms. - -syntax = "proto2"; -package validatortest; - -import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/TheThingsIndustries/go-proto-validators/validator.proto"; - -message ValidatorMessage { - // Embedded message test structure. - message Embedded { - optional string Identifier = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}]; - required int64 SomeValue = 2 [(validator.field) = {int_gt: 0, int_lt: 100}]; - } - - // String regex constraint tests. - required string StringReq = 1 [(validator.field) = {regex: "^.{2,5}$"}]; - required string StringReqNonNull = 2 [(validator.field) = {regex: "^.{2,5}$"}, (gogoproto.nullable) = false]; - optional string StringOpt = 3 [(validator.field) = {regex: "^.{2,5}$"}]; - optional string StringOptNonNull = 4 [(validator.field) = {regex: "^.{2,5}$"}, (gogoproto.nullable) = false]; - required string StringUnescaped = 5 [(validator.field) = {regex: "[\\p{L}\\p{N}]({\\p{L}\\p{N}_- ]{0,28}[\\p{L}\\p{N}])?."}]; - - // Strict integer inequality constraint tests. - required uint32 IntReq = 6 [(validator.field) = {int_gt: 10}]; - required uint32 IntReqNonNull = 7 [(validator.field) = {int_gt: 0}, (gogoproto.nullable) = false]; - repeated uint32 IntRep = 8 [(validator.field) = {int_gt: 10}]; - repeated uint32 IntRepNonNull = 9 [(validator.field) = {int_gt: 0}]; - - // Embedded message recursive constraint tests. - required Embedded embeddedReq = 10; - required Embedded embeddedNonNull = 11 [(gogoproto.nullable) = false]; - repeated Embedded embeddedRep = 12; - repeated Embedded embeddedRepNonNullable = 13 [(gogoproto.nullable) = false]; - - // Custom error tests. - optional int32 CustomErrorInt = 16 [(validator.field) = {int_gt: 10, human_error: "My Custom Error"}]; - - // Strict floating-point inequality constraint tests. - // With this epsilon value, the limits become - // SomeFloat+0.05 > 0.35 - // SomeFloat-0.05 < 0.65 - required double StrictSomeDoubleReq = 17 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}]; - required double StrictSomeDoubleReqNonNull = 18 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}, (gogoproto.nullable) = false]; - repeated double StrictSomeDoubleRep = 19 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}]; - repeated double StrictSomeDoubleRepNonNull = 20 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}]; - required float StrictSomeFloatReq = 21 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}]; - required float StrictSomeFloatReqNonNull = 22 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}, (gogoproto.nullable) = false]; - repeated float StrictSomeFloatRep = 23 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}]; - repeated float StrictSomeFloatRepNonNull = 24 [(validator.field) = {float_gt: 0.35, float_lt: 0.65, float_epsilon: 0.05}]; - - // Non-strict floating-point inequality constraint tests. - required double SomeDoubleReq = 25 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}]; - required double SomeDoubleReqNonNull = 26 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}, (gogoproto.nullable) = false]; - repeated double SomeDoubleRep = 27 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}]; - repeated double SomeDoubleRepNonNull = 28 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}]; - required float SomeFloatReq = 29 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}]; - required float SomeFloatReqNonNull = 30 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}, (gogoproto.nullable) = false]; - repeated float SomeFloatRep = 31 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}]; - repeated float SomeFloatRepNonNull = 32 [(validator.field) = {float_gte: 0.25, float_lte: 0.75}]; - - // String not-empty constraint tests. - required string SomeNonEmptyString = 33 [(validator.field) = {string_not_empty: true}]; - - // Repeated base-type without constraint tests. - repeated int32 RepeatedBaseType = 34; - - // Repeated element count constraint tests. - repeated int32 Repeated = 35 [(validator.field) = {repeated_count_min: 2, repeated_count_max: 5}]; - - optional string SomeStringLtReq = 36 [(validator.field) = {length_gt: 2}]; - optional string SomeStringGtReq = 37 [(validator.field) = {length_lt: 12}]; - optional string SomeStringEqReq = 38 [(validator.field) = {length_eq: 10}]; - optional bytes SomeBytesLtReq = 39 [(validator.field) = {length_gt: 5}]; - optional bytes SomeBytesGtReq = 40 [(validator.field) = {length_lt: 20}]; - optional bytes SomeBytesEqReq = 41 [(validator.field) = {length_eq: 12}]; - -} diff --git a/test/validator_proto3.proto b/test/validator_proto3.proto index 96500c7..c63dd1f 100644 --- a/test/validator_proto3.proto +++ b/test/validator_proto3.proto @@ -17,8 +17,8 @@ message ValidatorMessage3 { // String regex constraint tests. string SomeString = 1 [(validator.field) = {regex: "^.{2,5}$"}]; repeated string SomeStringRep = 2 [(validator.field) = {regex: "^.{2,5}$"}]; - string SomeStringNoQuotes = 3 [(validator.field) = {regex: "^[^\"]{2,5}$"}]; - string SomeStringUnescaped = 4 [(validator.field) = {regex: "[\\p{L}\\p{N}]({\\p{L}\\p{N}_- ]{0,28}[\\p{L}\\p{N}])?."}]; + // string SomeStringNoQuotes = 3 [(validator.field) = {regex: "^[^\"]{2,5}$"}]; + // string SomeStringUnescaped = 4 [(validator.field) = {regex: "[\\p{L}\\p{N}]({\\p{L}\\p{N}_- ]{0,28}[\\p{L}\\p{N}])?."}]; // Strict integer inequality constraint tests. uint32 SomeInt = 6 [(validator.field) = {int_gt: 10}]; @@ -32,9 +32,6 @@ message ValidatorMessage3 { repeated Embedded someEmbeddedRep = 14; repeated Embedded someEmbeddedRepNonNullable = 15 [(gogoproto.nullable) = false]; - // Custom error tests. - int32 CustomErrorInt = 16 [(validator.field) = {int_lt: 10, human_error: "My Custom Error"}]; - // Strict floating-point inequality constraint tests. // With this epsilon value, the limits become // SomeFloat+0.05 > 0.35 diff --git a/test/validator_proto3_map.proto b/test/validator_proto3_map.proto index 1047b8a..73fcfc5 100644 --- a/test/validator_proto3_map.proto +++ b/test/validator_proto3_map.proto @@ -21,6 +21,7 @@ message ValidatorMapMessage3 { map SomeExtMap = 2; map SomeNestedMap = 3; + string something = 4; } diff --git a/util/util.go b/util/util.go index 6034781..8a75229 100644 --- a/util/util.go +++ b/util/util.go @@ -62,7 +62,10 @@ func GetFieldsToValidate(i interface{}, paths []string) (map[string]string, erro } } // Repeated items are checked outside the regular validators and need to be accounted for. - if val.Type().Field(i).Type.Kind() == reflect.Slice { + if val.Type().Field(i).Type.Kind() == reflect.Slice && val.Type().Field(i).Type.Elem().Kind() == reflect.Ptr { + fields[s[0]] = val.Type().Field(i).Name + } + if val.Type().Field(i).Type.Kind() == reflect.Ptr { fields[s[0]] = val.Type().Field(i).Name } } From a0d93c13d375b3cf6d592754ffdb23945b133dfc Mon Sep 17 00:00:00 2001 From: KrishnaIyer Date: Mon, 28 Jan 2019 18:31:07 +0100 Subject: [PATCH 17/17] Cleanup codebase --- Makefile | 14 +- README.md | 74 ++--- examples/nested.validator.pb.go | 51 ++-- test/golang/validator_test.go | 512 -------------------------------- 4 files changed, 66 insertions(+), 585 deletions(-) delete mode 100644 test/golang/validator_test.go diff --git a/Makefile b/Makefile index 25565ca..d9c7e65 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ install: go install github.com/TheThingsIndustries/go-proto-validators/protoc-gen-govalidators -regenerate_test_generic: +regenerate_test_generic: install @echo "Regenerating the generic test .proto files (with gogo)" (protoc \ --proto_path=${GOPATH}/src \ @@ -19,7 +19,7 @@ regenerate_test_generic: --gogo_out=test \ --govalidators_out=gogoimport=true:test test/generic/*.proto) -regenerate_test_gogo: +regenerate_test_gogo: install @echo "Regenerating test .proto files with gogo imports" (protoc \ --proto_path=${GOPATH}/src \ @@ -27,14 +27,6 @@ regenerate_test_gogo: --gogo_out=test/gogo \ --govalidators_out=gogoimport=true:test/gogo test/*.proto) -regenerate_test_golang: - @echo "--- Regenerating test .proto files with golang imports" - (protoc \ - --proto_path=${GOPATH}/src \ - --proto_path=test \ - --go_out=test/golang \ - --govalidators_out=test/golang test/*.proto) - regenerate_example: install @echo "--- Regenerating example directory" (protoc \ @@ -50,7 +42,7 @@ errors: install --proto_path=errors \ --go_out=errors errors/*.proto) -test: install regenerate_test_gogo regenerate_test_golang +test: regenerate_test_generic regenerate_test_gogo @echo "Running tests" go test -v ./... diff --git a/README.md b/README.md index 3df5ea8..d2dee0a 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,10 @@ [![Travis Build](https://travis-ci.org/mwitkow/go-proto-validators.svg)](https://travis-ci.org/mwitkow/go-proto-validators) [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) -A `protoc` plugin that generates `Validate() error` functions on Go proto `struct`s based on field options inside `.proto` -files. The validation functions are code-generated and thus don't suffer on performance from tag-based reflection on -deeply-nested messages. +A `protoc` plugin that generates `Validate([]paths) error` functions on Go proto `struct`s based on field options inside `.proto` +files. + +It incorporates [fieldmasks](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/field-mask) into the validators thereby providing optional validation on fields. By default, no fields are validated. This allows for each field that needs to be validated to be specified by the fieldmask. ## Paint me a code picture @@ -38,34 +39,40 @@ Second, the expected values in fields are now part of the contract `.proto` file Third, the generated code is understandable and has clear understandable error messages. Take a look: ```go -func (this *InnerMessage) Validate() error { - if !(this.SomeInteger > 0) { - return fmt.Errorf("validation error: InnerMessage.SomeInteger must be greater than '0'") - } - if !(this.SomeInteger < 100) { - return fmt.Errorf("validation error: InnerMessage.SomeInteger must be less than '100'") +func (this *InnerMessage) Validate(paths []string) error { + toBeValidated, err := github_com_TheThingsIndustries_go_proto_validators_util.GetFieldsToValidate(this, paths) + if err != nil { + return err } - if !(this.SomeFloat >= 0) { - return fmt.Errorf("validation error: InnerMessage.SomeFloat must be greater than or equal to '0'") + _ = toBeValidated + + if !(this.SomeInteger > 0) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.SomeInteger", toBeValidated)) { + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("SomeInteger", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_INT_GT, fmt.Errorf(`field must be greater than '0'`)) } - if !(this.SomeFloat <= 1) { - return fmt.Errorf("validation error: InnerMessage.SomeFloat must be less than or equal to '1'") + if !(this.SomeInteger < 100) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.SomeInteger", toBeValidated)) { + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("SomeInteger", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_INT_LT, fmt.Errorf(`field must be lesser than '100'`)) } return nil } -var _regex_OuterMessage_ImportantString = regexp.MustCompile("^[a-z]{2,5}$") +var _regex_OuterMessage_ImportantString = regexp.MustCompile(`^[a-z]{2,5}$`) + +func (this *OuterMessage) Validate(paths []string) error { + toBeValidated, err := github_com_TheThingsIndustries_go_proto_validators_util.GetFieldsToValidate(this, paths) + if err != nil { + return err + } + _ = toBeValidated -func (this *OuterMessage) Validate() error { - if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) { - return fmt.Errorf("validation error: OuterMessage.ImportantString must conform to regex '^[a-z]{2,5}$'") + if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.ImportantString", toBeValidated)) { + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("ImportantString", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_STRING_REGEX, fmt.Errorf(`field must be a string conforming to the regex "^[a-z]{2,5}$"`)) } if nil == this.Inner { - return fmt.Errorf("validation error: OuterMessage.Inner message must exist") + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("Inner", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_MSG_EXISTS, fmt.Errorf("message must exist")) } - if this.Inner != nil { - if err := validators.CallValidatorIfExists(this.Inner); err != nil { - return err + if (this.Inner != nil) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.Inner", toBeValidated)) { + if err := github_com_TheThingsIndustries_go_proto_validators_util.CallValidatorIfExists(this.Inner, github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("this.Inner", toBeValidated), paths); err != nil { + return github_com_TheThingsIndustries_go_proto_validators_errors.GetErrorWithTopField(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("this.Inner", toBeValidated), err) } } return nil @@ -86,30 +93,9 @@ Then, do the usual go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators ``` -Your `protoc` builds probably look very simple like: - -```sh -protoc \ - --proto_path=. \ - --go_out=. \ - *.proto -``` - -That's fine, until you encounter `.proto` includes. Because `go-proto-validators` uses field options inside the `.proto` -files themselves, it's `.proto` definition (and the Google `descriptor.proto` itself) need to on the `protoc` include -path. Hence the above becomes: - -```sh -protoc \ - --proto_path=${GOPATH}/src \ - --proto_path=${GOPATH}/src/github.com/google/protobuf/src \ - --proto_path=. \ - --go_out=. \ - --govalidators_out=. \ - *.proto -``` +Check the [Makefile](/Makefile) for installing this plugin. -Or with gogo protobufs: +The following is an example of using this plugin as part of your proto generation. ```sh protoc \ diff --git a/examples/nested.validator.pb.go b/examples/nested.validator.pb.go index eac3ac4..2e2b232 100644 --- a/examples/nested.validator.pb.go +++ b/examples/nested.validator.pb.go @@ -3,40 +3,55 @@ package validator_examples -import regexp "regexp" -import fmt "fmt" -import github_com_TheThingsIndustries_go_proto_validators "github.com/TheThingsIndustries/go-proto-validators" -import proto "github.com/golang/protobuf/proto" -import math "math" -import _ "github.com/TheThingsIndustries/go-proto-validators" +import ( + fmt "fmt" + math "math" + proto "github.com/golang/protobuf/proto" + _ "github.com/TheThingsIndustries/go-proto-validators" + regexp "regexp" + github_com_TheThingsIndustries_go_proto_validators_util "github.com/TheThingsIndustries/go-proto-validators/util" + github_com_TheThingsIndustries_go_proto_validators_errors "github.com/TheThingsIndustries/go-proto-validators/errors" +) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -func (this *InnerMessage) Validate() error { - if !(this.SomeInteger > 0) { - return github_com_TheThingsIndustries_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be greater than '0'`, this.SomeInteger)) +func (this *InnerMessage) Validate(paths []string) error { + toBeValidated, err := github_com_TheThingsIndustries_go_proto_validators_util.GetFieldsToValidate(this, paths) + if err != nil { + return err } - if !(this.SomeInteger < 100) { - return github_com_TheThingsIndustries_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be less than '100'`, this.SomeInteger)) + _ = toBeValidated + + if !(this.SomeInteger > 0) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.SomeInteger", toBeValidated)) { + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("SomeInteger", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_INT_GT, fmt.Errorf(`field must be greater than '0'`)) + } + if !(this.SomeInteger < 100) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.SomeInteger", toBeValidated)) { + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("SomeInteger", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_INT_LT, fmt.Errorf(`field must be lesser than '100'`)) } return nil } var _regex_OuterMessage_ImportantString = regexp.MustCompile(`^[a-z]{2,5}$`) -func (this *OuterMessage) Validate() error { - if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) { - return github_com_TheThingsIndustries_go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, this.ImportantString)) +func (this *OuterMessage) Validate(paths []string) error { + toBeValidated, err := github_com_TheThingsIndustries_go_proto_validators_util.GetFieldsToValidate(this, paths) + if err != nil { + return err + } + _ = toBeValidated + + if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.ImportantString", toBeValidated)) { + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("ImportantString", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_STRING_REGEX, fmt.Errorf(`field must be a string conforming to the regex "^[a-z]{2,5}$"`)) } if nil == this.Inner { - return github_com_TheThingsIndustries_go_proto_validators.FieldError("Inner", fmt.Errorf("message must exist")) + return github_com_TheThingsIndustries_go_proto_validators_errors.FieldError(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("Inner", toBeValidated), github_com_TheThingsIndustries_go_proto_validators_errors.Types_MSG_EXISTS, fmt.Errorf("message must exist")) } - if this.Inner != nil { - if err := github_com_TheThingsIndustries_go_proto_validators.CallValidatorIfExists(this.Inner); err != nil { - return github_com_TheThingsIndustries_go_proto_validators.FieldError("Inner", err) + if (this.Inner != nil) && (github_com_TheThingsIndustries_go_proto_validators_util.ShouldBeValidated("this.Inner", toBeValidated)) { + if err := github_com_TheThingsIndustries_go_proto_validators_util.CallValidatorIfExists(this.Inner, github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("this.Inner", toBeValidated), paths); err != nil { + return github_com_TheThingsIndustries_go_proto_validators_errors.GetErrorWithTopField(github_com_TheThingsIndustries_go_proto_validators_util.GetProtoNameForField("this.Inner", toBeValidated), err) } } return nil diff --git a/test/golang/validator_test.go b/test/golang/validator_test.go deleted file mode 100644 index 8668359..0000000 --- a/test/golang/validator_test.go +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright 2016 Michal Witkowski. All Rights Reserved. -// See LICENSE for licensing terms. - -package validatortest - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - stableBytes = make([]byte, 12) -) - -func buildProto3(someString string, someInt uint32, identifier string, someValue int64, someDoubleStrict float64, - someFloatStrict float32, someDouble float64, someFloat float32, nonEmptyString string, repeatedCount uint32, - someStringLength string, someBytes []byte) *ValidatorMessage3 { - goodEmbeddedProto3 := &ValidatorMessage3_Embedded{ - Identifier: identifier, - SomeValue: someValue, - } - - goodProto3 := &ValidatorMessage3{ - SomeString: someString, - SomeStringRep: []string{someString, "xyz34"}, - SomeStringNoQuotes: someString, - SomeStringUnescaped: someString, - - SomeInt: someInt, - SomeIntRep: []uint32{someInt, 12, 13, 14, 15, 16}, - SomeIntRepNonNull: []uint32{someInt, 102}, - - SomeEmbedded: nil, - SomeEmbeddedNonNullable: goodEmbeddedProto3, - SomeEmbeddedExists: goodEmbeddedProto3, - SomeEmbeddedRep: []*ValidatorMessage3_Embedded{goodEmbeddedProto3}, - SomeEmbeddedRepNonNullable: []*ValidatorMessage3_Embedded{goodEmbeddedProto3}, - - StrictSomeDouble: someDoubleStrict, - StrictSomeDoubleRep: []float64{someDoubleStrict, 0.5, 0.55, 0.6}, - StrictSomeDoubleRepNonNull: []float64{someDoubleStrict, 0.5, 0.55, 0.6}, - StrictSomeFloat: someFloatStrict, - StrictSomeFloatRep: []float32{someFloatStrict, 0.5, 0.55, 0.6}, - StrictSomeFloatRepNonNull: []float32{someFloatStrict, 0.5, 0.55, 0.6}, - - SomeDouble: someDouble, - SomeDoubleRep: []float64{someDouble, 0.5, 0.55, 0.6}, - SomeDoubleRepNonNull: []float64{someDouble, 0.5, 0.55, 0.6}, - SomeFloat: someFloat, - SomeFloatRep: []float32{someFloat, 0.5, 0.55, 0.6}, - SomeFloatRepNonNull: []float32{someFloat, 0.5, 0.55, 0.6}, - - SomeNonEmptyString: nonEmptyString, - SomeStringEqReq: someStringLength, - SomeStringLtReq: someStringLength, - SomeStringGtReq: someStringLength, - - SomeBytesLtReq: someBytes, - SomeBytesGtReq: someBytes, - SomeBytesEqReq: someBytes, - - RepeatedBaseType: []int32{}, - } - - goodProto3.Repeated = make([]int32, repeatedCount, repeatedCount) - - return goodProto3 -} - -func buildProto2(someString string, someInt uint32, identifier string, someValue int64, someDoubleStrict float64, someFloatStrict float32, someDouble float64, someFloat float32, nonEmptyString string, repeatedCount uint32, someStringLength string, someBytes []byte) *ValidatorMessage { - goodEmbeddedProto2 := &ValidatorMessage_Embedded{ - Identifier: &identifier, - SomeValue: &someValue, - } - - goodProto2 := &ValidatorMessage{ - StringReq: &someString, - StringReqNonNull: &someString, - - StringOpt: nil, - StringOptNonNull: &someString, - - StringUnescaped: &someString, - - IntReq: &someInt, - IntReqNonNull: &someInt, - IntRep: []uint32{someInt, 12, 13, 14, 15, 16}, - IntRepNonNull: []uint32{someInt, 12, 13, 14, 15, 16}, - - EmbeddedReq: goodEmbeddedProto2, - EmbeddedNonNull: goodEmbeddedProto2, - EmbeddedRep: []*ValidatorMessage_Embedded{goodEmbeddedProto2}, - EmbeddedRepNonNullable: []*ValidatorMessage_Embedded{goodEmbeddedProto2}, - - StrictSomeDoubleReq: &someDoubleStrict, - StrictSomeDoubleReqNonNull: &someDoubleStrict, - StrictSomeDoubleRep: []float64{someDoubleStrict, 0.5, 0.55, 0.6}, - StrictSomeDoubleRepNonNull: []float64{someDoubleStrict, 0.5, 0.55, 0.6}, - StrictSomeFloatReq: &someFloatStrict, - StrictSomeFloatReqNonNull: &someFloatStrict, - StrictSomeFloatRep: []float32{someFloatStrict, 0.5, 0.55, 0.6}, - StrictSomeFloatRepNonNull: []float32{someFloatStrict, 0.5, 0.55, 0.6}, - - SomeDoubleReq: &someDouble, - SomeDoubleReqNonNull: &someDouble, - SomeDoubleRep: []float64{someDouble, 0.5, 0.55, 0.6}, - SomeDoubleRepNonNull: []float64{someDouble, 0.5, 0.55, 0.6}, - SomeFloatReq: &someFloat, - SomeFloatReqNonNull: &someFloat, - SomeFloatRep: []float32{someFloat, 0.5, 0.55, 0.6}, - SomeFloatRepNonNull: []float32{someFloat, 0.5, 0.55, 0.6}, - - SomeNonEmptyString: &nonEmptyString, - SomeStringEqReq: &someStringLength, - SomeStringLtReq: &someStringLength, - SomeStringGtReq: &someStringLength, - SomeBytesLtReq: someBytes, - SomeBytesGtReq: someBytes, - SomeBytesEqReq: someBytes, - RepeatedBaseType: []int32{}, - } - - goodProto2.Repeated = make([]int32, repeatedCount, repeatedCount) - - return goodProto2 -} - -func TestGoodProto3(t *testing.T) { - var err error - goodProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - err = goodProto3.Validate() - if err != nil { - t.Fatalf("unexpected fail in validator: %v", err) - } -} - -func TestGoodProto2(t *testing.T) { - var err error - goodProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - err = goodProto2.Validate() - if err != nil { - t.Fatalf("unexpected fail in validator: %v", err) - } -} - -func TestStringRegex(t *testing.T) { - tooLong1Proto3 := buildProto3("toolong", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong1Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooLong2Proto3 := buildProto3("-%ab", 11, "bad#", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong2Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooLong1Proto2 := buildProto2("toolong", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong1Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooLong2Proto2 := buildProto2("-%ab", 11, "bad#", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if tooLong2Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } -} - -func TestIntLowerBounds(t *testing.T) { - lowerThan10Proto3 := buildProto3("-%ab", 9, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan10Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan10Proto2 := buildProto2("-%ab", 9, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan10Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", -1, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto2 := buildProto2("-%ab", 11, "abba", -1, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } -} - -func TestIntUpperBounds(t *testing.T) { - greaterThan100Proto3 := buildProto3("-%ab", 11, "abba", 101, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan100Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan100Proto2 := buildProto2("-%ab", 11, "abba", 101, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan100Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } -} - -func TestDoubleStrictLowerBounds(t *testing.T) { - lowerThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.3, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.3, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.300000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan035EpsilonProto3.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } - greaterThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.300000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan035EpsilonProto2.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } -} - -func TestDoubleStrictUpperBounds(t *testing.T) { - greaterThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.70000000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.70000000001, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.6999999999, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan065EpsilonProto3.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } - lowerThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.6999999999, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan065EpsilonProto2.Validate() != nil { - t.Fatalf("unexpected fail in validator") - } -} - -func TestFloatStrictLowerBounds(t *testing.T) { - lowerThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.2999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.2999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan035EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan035EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.3000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := greaterThan035EpsilonProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - greaterThan035EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.3000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := greaterThan035EpsilonProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestFloatStrictUpperBounds(t *testing.T) { - greaterThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.7000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - greaterThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.7000001, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if greaterThan065EpsilonProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan065EpsilonProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.6999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := lowerThan065EpsilonProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - lowerThan065EpsilonProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.6999999, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := lowerThan065EpsilonProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestDoubleNonStrictLowerBounds(t *testing.T) { - lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.2499999, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.2499999, 0.5, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.25, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.25, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestDoubleNonStrictUpperBounds(t *testing.T) { - higherThan1Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75111111, 0.5, "x", 4, "1234567890", stableBytes) - if higherThan1Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - higherThan1Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75111111, 0.5, "x", 4, "1234567890", stableBytes) - if higherThan1Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.75, 0.5, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestFloatNonStrictLowerBounds(t *testing.T) { - lowerThan0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.2499999, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - lowerThan0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.2499999, "x", 4, "1234567890", stableBytes) - if lowerThan0Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.25, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.25, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestFloatNonStrictUpperBounds(t *testing.T) { - higherThan1Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75111111, "x", 4, "1234567890", stableBytes) - if higherThan1Proto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - higherThan1Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75111111, "x", 4, "1234567890", stableBytes) - if higherThan1Proto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - equalTo0Proto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - equalTo0Proto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.75, "x", 4, "1234567890", stableBytes) - if err := equalTo0Proto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestStringNonEmpty(t *testing.T) { - emptyStringProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "", 4, "1234567890", stableBytes) - if emptyStringProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - emptyStringProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "", 4, "1234567890", stableBytes) - if emptyStringProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - nonEmptyStringProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := nonEmptyStringProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - nonEmptyStringProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := nonEmptyStringProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestRepeatedEltsCount(t *testing.T) { - notEnoughEltsProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 1, "1234567890", stableBytes) - if notEnoughEltsProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - notEnoughEltsProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 1, "1234567890", stableBytes) - if notEnoughEltsProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooManyEltsProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 14, "1234567890", stableBytes) - if tooManyEltsProto3.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - tooManyEltsProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 14, "1234567890", stableBytes) - if tooManyEltsProto2.Validate() == nil { - t.Fatalf("expected fail in validator, but it didn't happen") - } - validEltsCountProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := validEltsCountProto3.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } - validEltsCountProto2 := buildProto2("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := validEltsCountProto2.Validate(); err != nil { - t.Fatalf("unexpected fail in validator %v", err) - } -} - -func TestMsgExist(t *testing.T) { - someProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - someProto3.SomeEmbedded = nil - if err := someProto3.Validate(); err != nil { - t.Fatalf("validate shouldn't fail on missing SomeEmbedded, not annotated") - } - someProto3.SomeEmbeddedExists = nil - if err := someProto3.Validate(); err == nil { - t.Fatalf("expected fail due to lacking SomeEmbeddedExists") - } else if !strings.HasPrefix(err.Error(), "invalid field SomeEmbeddedExists:") { - t.Fatalf("expected fieldError, got '%v'", err) - } -} - -func TestStringLengthValidator(t *testing.T) { - StringLengthErrorProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "abc456", stableBytes) - if err := StringLengthErrorProto3.Validate(); err == nil { - t.Fatalf("validate shouldn't fail on error length") - } - - StringLengthSuccess := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := StringLengthSuccess.Validate(); err != nil { - t.Fatalf("validate shouldn't fail on equal length") - } -} - -func TestBytesLengthValidator(t *testing.T) { - StringLengthErrorProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "abc456", []byte("anc")) - if err := StringLengthErrorProto3.Validate(); err == nil { - t.Fatalf("validate shouldn't fail on error length") - } - - StringLengthSuccess := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - if err := StringLengthSuccess.Validate(); err != nil { - t.Fatalf("validate shouldn't fail on equal length") - } -} - -func TestNestedError3(t *testing.T) { - someProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - someProto3.SomeEmbeddedExists.SomeValue = 101 // should be less than 101 - if err := someProto3.Validate(); err == nil { - t.Fatalf("expected fail due to nested SomeEmbeddedNonNullable.SomeValue being wrong") - } else if !strings.HasPrefix(err.Error(), "invalid field SomeEmbeddedNonNullable.SomeValue:") { - t.Fatalf("expected fieldError, got '%v'", err) - } -} - -func TestCustomError_Proto3(t *testing.T) { - someProto3 := buildProto3("-%ab", 11, "abba", 99, 0.5, 0.5, 0.5, 0.5, "x", 4, "1234567890", stableBytes) - someProto3.CustomErrorInt = 30 - expectedErr := "invalid field CustomErrorInt: My Custom Error" - if err := someProto3.Validate(); err == nil { - t.Fatalf("validate should fail on missing CustomErrorInt") - } else if err.Error() != expectedErr { - t.Fatalf("validation error should be '%s' but was '%s'", expectedErr, err.Error()) - } -} - -func TestMapAlwaysPassesUntilFixedProperly(t *testing.T) { - example := &ValidatorMapMessage3{} - if err := example.Validate(); err != nil { - t.Fatalf("map validators should always pass") - } -} - -func TestOneOf_NestedMessage(t *testing.T) { - example := &OneOfMessage3{ - SomeInt: 30, - Type: &OneOfMessage3_OneMsg{ - OneMsg: &ExternalMsg{ - Identifier: "999", // bad - SomeValue: 99, // good - }, - }, - Something: &OneOfMessage3_ThreeInt{ - ThreeInt: 100, // > 20 - }, - } - err := example.Validate() - assert.Error(t, err, "nested message in oneof should fail validation on ExternalMsg") - assert.Contains(t, err.Error(), "OneMsg.Identifier", "error must err on the ExternalMsg.Identifier") -} - -func TestOneOf_NestedInt(t *testing.T) { - example := &OneOfMessage3{ - SomeInt: 30, - Type: &OneOfMessage3_OneMsg{ - OneMsg: &ExternalMsg{ - Identifier: "abba", // good - SomeValue: 99, // good - }, - }, - Something: &OneOfMessage3_ThreeInt{ - ThreeInt: 19, // > 20 - }, - } - err := example.Validate() - assert.Error(t, err, "nested message in oneof should fail validation on ThreeInt") - assert.Contains(t, err.Error(), "ThreeInt", "error must err on the ThreeInt.ThreeInt") -} - -func TestOneOf_Passes(t *testing.T) { - example := &OneOfMessage3{ - SomeInt: 30, - Type: &OneOfMessage3_OneMsg{ - OneMsg: &ExternalMsg{ - Identifier: "abba", // good - SomeValue: 99, // good - }, - }, - Something: &OneOfMessage3_FourInt{ - FourInt: 101, // > 101 - }, - } - err := example.Validate() - assert.NoError(t, err, "This message should pass all validation") -}