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/Makefile b/Makefile index ff1bc0d..d9c7e65 100644 --- a/Makefile +++ b/Makefile @@ -3,25 +3,29 @@ 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" + +regenerate_test_generic: install + @echo "Regenerating the generic test .proto files (with gogo)" (protoc \ --proto_path=${GOPATH}/src \ --proto_path=test \ - --gogo_out=test/gogo \ - --govalidators_out=gogoimport=true:test/gogo test/*.proto) + --gogo_out=test \ + --govalidators_out=gogoimport=true:test test/generic/*.proto) -regenerate_test_golang: - @echo "--- Regenerating test .proto files with golang imports" +regenerate_test_gogo: install + @echo "Regenerating test .proto files with gogo imports" (protoc \ --proto_path=${GOPATH}/src \ --proto_path=test \ - --go_out=test/golang \ - --govalidators_out=test/golang test/*.proto) + --gogo_out=test/gogo \ + --govalidators_out=gogoimport=true:test/gogo test/*.proto) regenerate_example: install @echo "--- Regenerating example directory" @@ -31,7 +35,14 @@ regenerate_example: install --go_out=. \ --govalidators_out=. examples/*.proto) -test: install regenerate_test_gogo regenerate_test_golang +errors: install + @echo "--- Regenerating error definitions" + (protoc \ + --proto_path=${GOPATH}/src \ + --proto_path=errors \ + --go_out=errors errors/*.proto) + +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/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..b2e329f --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,59 @@ +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 { + nestedErr error + fieldName string + errType Types +} + +// Error returns the error as a string +func (f *ValidatorFieldError) Error() string { + return fmt.Sprintf("%s: %s: %s", f.fieldName, f.errType.String(), 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[0] +} + +// 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[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] +} + +// 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 { + return &ValidatorFieldError{ + nestedErr: err, + fieldName: fieldName, + errType: Type, + } +} diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 0000000..11c2f2c --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,47 @@ +package errors + +import ( + "errors" + "reflect" + "testing" +) + +func TestErrorMethods(t *testing.T) { + errString := "SomeInt: INT_GT: field must be greater than '500'" + + errType := GetType(errString) + if errType != "INT_GT" { + t.Fatal(t) + } + + errFieldName := GetFieldName(errString) + if errFieldName != "SomeInt" { + 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) + } + + nilErrType := GetType("") + if nilErrType != "" { + 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/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/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..2e2b232 100644 --- a/examples/nested.validator.pb.go +++ b/examples/nested.validator.pb.go @@ -1,53 +1,57 @@ -// 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 proto "github.com/golang/protobuf/proto" -import math "math" -import _ "github.com/mwitkow/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 fmt.Errorf("validation error: InnerMessage.SomeInteger must be greater than '0'") +func (this *InnerMessage) Validate(paths []string) error { + toBeValidated, err := github_com_TheThingsIndustries_go_proto_validators_util.GetFieldsToValidate(this, paths) + if err != nil { + return err + } + _ = 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) { - return fmt.Errorf("validation error: InnerMessage.SomeInteger must be less than '100'") + 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 := github_com_mwitkow_go_proto_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 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/plugin/plugin.go b/plugin/plugin.go index 221c0a5..b9c929e 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -55,12 +55,13 @@ import ( "strconv" "strings" + validator "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" "github.com/gogo/protobuf/protoc-gen-gogo/generator" "github.com/gogo/protobuf/vanity" - "github.com/mwitkow/go-proto-validators" ) type plugin struct { @@ -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} } @@ -92,7 +95,8 @@ 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/util") + p.errorPkg = p.NewImport("github.com/TheThingsIndustries/go-proto-validators/errors") for _, msg := range file.Messages() { if msg.DescriptorProto.GetOptions().GetMapEntry() { @@ -101,10 +105,7 @@ func (p *plugin) Generate(file *generator.FileDescriptor) { p.generateRegexVars(file, msg) if gogoproto.IsProto3(file.FileDescriptorProto) { p.generateProto3Message(file, msg) - } else { - p.generateProto2Message(file, msg) } - } } @@ -153,91 +154,20 @@ func (p *plugin) generateRegexVars(file *generator.FileDescriptor, message *gene } } -func (p *plugin) generateProto2Message(file *generator.FileDescriptor, message *generator.Descriptor) { +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() - 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.P(`toBeValidated, err := `, p.validatorPkg.Use(), `.GetFieldsToValidate(this, paths)`) + p.P(`if err != nil {`) + p.In() + p.P(`return err`) p.Out() p.P(`}`) -} + // 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`) -func (p *plugin) generateProto3Message(file *generator.FileDescriptor, message *generator.Descriptor) { - ccTypeName := generator.CamelCaseSlice(message.TypeName()) - p.P(`func (this *`, ccTypeName, `) Validate() error {`) - p.In() + p.P("\n") for _, field := range message.Field { fieldValidator := getFieldValidatorIfAny(field) if fieldValidator == nil && !field.IsMessage() { @@ -246,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() @@ -262,11 +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() - variableName = "item" + itemName = "item" } } else if fieldValidator != nil { if fieldValidator.RepeatedCountMin != nil { @@ -277,19 +209,19 @@ 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 { 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(`, 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 { @@ -298,16 +230,26 @@ 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.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, `); err != nil {`) + + if repeated { + 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("`, calledName, `", toBeValidated), paths ); err != nil {`) + } + p.In() - p.P(`return `, p.validatorPkg.Use(), `.FieldError("`, fieldName, `", err)`) + p.P(`return `, p.errorPkg.Use(), `.GetErrorWithTopField(`, p.validatorPkg.Use(), `.GetProtoNameForField("`, calledName, `",toBeValidated),err)`) p.Out() p.P(`}`) if nullable { @@ -331,56 +273,56 @@ 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.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, 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.P(`if !(`, itemName, ` < `, 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(`}`) } } -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.P(`if !( len(`, itemName, `) > `, 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(`}`) } if fv.LengthLt != nil { - p.P(`if !( len(`, variableName, `) < `, fv.LengthLt, `) {`) + p.P(`if !( len(`, itemName, `) < `, 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(`}`) } if fv.LengthEq != nil { - p.P(`if !( len(`, variableName, `) == `, fv.LengthEq, `) {`) + p.P(`if !( len(`, itemName, `) == `, 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(`}`) } } -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 @@ -419,99 +361,101 @@ 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) + compareStr = fmt.Sprint(`if !(`, itemName) 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(), `) {`) + 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() - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errType, fieldName, errorStr, fv) p.Out() p.P(`}`) } 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()) 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(), `) {`) + 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() - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errType, fieldName, errorStr, fv) p.Out() p.P(`}`) } } -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.P(`if !`, p.regexName(ccTypeName, fieldName), `.MatchString(`, itemName, `) && (`, 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(`}`) } if fv.StringNotEmpty != nil && fv.GetStringNotEmpty() { - p.P(`if `, variableName, ` == "" {`) + p.P(`if (`, itemName, ` == "" ) && (`, 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(`}`) } - 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`) - p.generateErrorString(variableName, fieldName, errorStr, fv) + p.generateErrorString(variableName, errors.Types_REPEATED_COUNT_MIN, fieldName, errorStr, fv) p.Out() 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`) - 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) { - if fv.GetHumanError() == "" { - p.P(`return `, p.validatorPkg.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(), "`))") - } - +func (p *plugin) generateErrorString(variableName string, errType errors.Types, fieldName string, specificError string, fv *validator.FieldValidator) { + 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 { @@ -577,9 +521,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 } } 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/generic/generic.proto b/test/generic/generic.proto new file mode 100644 index 0000000..ce9c6a5 --- /dev/null +++ b/test/generic/generic.proto @@ -0,0 +1,35 @@ +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}]; + 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}$"}]; + 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}$"}]; + Embedded embedded_mandatory = 4 [(validator.field) = {msg_exists: true}]; + Embedded embedded_not_mandatory = 5; + 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}$"}]; + repeated Embedded embedded_repeated = 3; + 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 new file mode 100644 index 0000000..519424a --- /dev/null +++ b/test/generic/generic_test.go @@ -0,0 +1,673 @@ +package validatortest + +import ( + "fmt" + "testing" + + "github.com/TheThingsIndustries/go-proto-validators/errors" + "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", "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"} +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 { + Name string + Message interface{} + FieldMask []string + ErrorExpected bool + ExpectedErrorFieldName string + ExpectedErrorType errors.Types + }{ + { + Name: "OuterWithValid", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: nil, + ErrorExpected: false, + }, + { + Name: "OuterWithInvalid", + Message: &GenericTestMessage{ + SomeString: "&*^", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: nil, + ErrorExpected: false, + }, + { + Name: "MiddleWithInvalid", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "&^&", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: nil, + ErrorExpected: false, + }, + { + Name: "InnerWithInvalid", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + FieldMask: nil, + 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 + } + }) + } +} + +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", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: false, + }, + { + Name: "InnerInvalid", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: true, + 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{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "&*^", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_mandatory.identifier", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "OuterInvalid", + Message: &GenericTestMessage{ + SomeString: "&*sfsafsdfsfsdfdsfsdfsdf", + EmbeddedMandatory: &Embedded{ + Identifier: "&HR", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + FieldMask: fullFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "some_string", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + } { + 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", + EmbeddedMandatory: &Embedded{ + Identifier: "^&^", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: outerOnlyFieldMask, + ErrorExpected: false, + }, + { + Name: "ValidOuterWithInvalidMiddleFMNotSet1", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "^&jfskfjdfjsdkfaf;sd^", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: []string{"some_string", "field_mask", "embedded_mandatory", "embedded_mandatory.some_string"}, + ErrorExpected: false, + }, + { + Name: "ValidOuterWithInvalidMiddleFMSet", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "^&^", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: []string{"some_string", "field_mask", "embedded_mandatory", "embedded_mandatory.identifier"}, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_mandatory.identifier", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "ValidOuterWithOnlyOnefield", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{}, + }, + 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{ + SomeString: "&%*$sd", + EmbeddedMandatory: &Embedded{}, + }, + FieldMask: []string{"some_string"}, + ErrorExpected: true, + ExpectedErrorFieldName: "some_string", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "InvalidOuterWithInvalidMiddle", + Message: &GenericTestMessage{ + SomeString: "*&(&*#&$$", + EmbeddedMandatory: &Embedded{ + Identifier: "^&^", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: outerOnlyFieldMask, + ErrorExpected: true, + ExpectedErrorFieldName: "some_string", + ExpectedErrorType: errors.Types_STRING_REGEX, + }, + { + Name: "ValidOuterAndMiddleWithInvalidInnerFMNotSet", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + FieldMask: outerAndMiddleOnlyFieldMask, + ErrorExpected: false, + }, + { + Name: "ValidOuterAndMiddleWithInvalidInnerFMNotSet1", + Message: &GenericTestMessage{ + SomeString: "outer", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "test-inner", + }, + }, + }, + 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", + EmbeddedMandatory: &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + 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: "embedded_mandatory.inner.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 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", + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + }, + 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{ + SomeString: "outer", + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + }, + FieldMask: nil, + ErrorExpected: false, + }, + { + Name: "NoFieldWithNilFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + EmbeddedRepeated: nil, + EmbeddedRepeatedWithCheck: nil, + }, + FieldMask: []string{}, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_repeated_with_check", + ExpectedErrorType: errors.Types_REPEATED_COUNT_MIN, + }, + { + Name: "ValidWithFullFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: false, + }, + { + Name: "InvalidWithFullFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: true, + 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{ + SomeString: "outer", + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "", + }, + }, + }, + }, + FieldMask: fullFieldMaskWithEmbedded, + ErrorExpected: true, + ExpectedErrorFieldName: "embedded_repeated_with_check.inner.id", + ExpectedErrorType: errors.Types_STRING_NOT_EMPTY, + }, + { + Name: "InvalidWithPartialFieldMask", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + }, + FieldMask: outerOnlyFieldMaskWithEmbedded, + ErrorExpected: false, + }, + { + Name: "InvalidWithPartialFieldMask2", + Message: &GenericTestMessageWithRepeated{ + SomeString: "outer", + EmbeddedRepeated: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + EmbeddedRepeatedWithCheck: []*Embedded{ + &Embedded{ + Identifier: "middle", + Inner: &InnerEmbedded{ + Id: "inner", + }, + }, + }, + }, + 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 + } + }) + } +} 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/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") -} diff --git a/test/validator_proto2.proto b/test/validator_proto2.proto deleted file mode 100644 index e0e95ae..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/mwitkow/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 dd0b9dd..c63dd1f 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. @@ -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 f8799b1..73fcfc5 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 ; @@ -21,6 +21,7 @@ message ValidatorMapMessage3 { map SomeExtMap = 2; map SomeNestedMap = 3; + string something = 4; } 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/util/util.go b/util/util.go new file mode 100644 index 0000000..8a75229 --- /dev/null +++ b/util/util.go @@ -0,0 +1,142 @@ +package util + +import ( + "errors" + "reflect" + "strings" +) + +const ( + fieldMaskDelimiter string = "." + jsonTagDelimiter string = "," +) + +var ( + errInvalidMessage = errors.New("Invalid Message") + errEmptyMessage = 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. +func CallValidatorIfExists(candidate interface{}, topLevelPath string, fullPaths []string) error { + if validator, ok := candidate.(Validator); ok { + return validator.Validate(getFieldMaskForEmbeddedFields(topLevelPath, fullPaths)) + } + return nil +} + +// 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) { + val := reflect.ValueOf(i).Elem() + if !val.IsValid() || val.Type().NumField() == 0 { + return map[string]string{}, errInvalidMessage + } + 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 == "-" { + continue + } + s := strings.Split(jsonTag, jsonTagDelimiter) + if len(s) > 2 { + return map[string]string{}, errInvalidMessage + } + // Add a field if it a part of the supplied list. + for _, st := range topPaths { + if s[0] == st { + fields[s[0]] = val.Type().Field(i).Name + break + } + } + // Repeated items are checked outside the regular validators and need to be accounted for. + 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 + } + } + 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, fields map[string]string) bool { + names := strings.Split(name, fieldMaskDelimiter) + if len(names) != 2 { + return true + } + for _, fieldName := range fields { + if names[1] == fieldName { + return true + } + } + return false +} + +// 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 protoName, fieldName := range fields { + if field == fieldName { + return protoName + } + } + return "" +} + +// 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 + } + 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 +} + +// GetFieldMaskForOneOfFields ... +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..2dc7a3d --- /dev/null +++ b/util/util_test.go @@ -0,0 +1,361 @@ +package util + +import ( + "fmt" + "reflect" + "testing" +) + +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"} + +// 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,omitempty,repeatable"` +} + +type CustomTagStruct struct { + SomeString string `json:"some_string"` +} + +func (m *TestMessage) Validate(fieldMask []string) error { + return nil +} +func (m *Embedded) Validate(fieldMask []string) error { + return nil +} + +func TestGetFieldsToValidate(t *testing.T) { + + for _, tc := range []struct { + Name string + InputMessage interface{} + InputFieldMaskPaths []string + ExpectedFields map[string]string + ExpectedError interface{} + }{ + { + Name: "NilFieldMask", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: nil, + ExpectedFields: map[string]string{"some_repeated": "SomeRepeated"}, + ExpectedError: nil, + }, + { + Name: "OneField", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: []string{"some_int"}, + ExpectedFields: map[string]string{"some_int": "SomeInt", "some_repeated": "SomeRepeated"}, + ExpectedError: nil, + }, + { + Name: "InvalidFieldMask", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: []string{"somesome_int"}, + ExpectedFields: map[string]string{"some_repeated": "SomeRepeated"}, + ExpectedError: nil, + }, + { + Name: "FullFieldMask", + InputMessage: &TestMessage{}, + InputFieldMaskPaths: completeFieldMask, + ExpectedFields: allFields, + ExpectedError: nil, + }, + { + Name: "InvalidStructTag", + InputMessage: &BadStruct{}, + InputFieldMaskPaths: completeFieldMask, + ExpectedFields: map[string]string{}, + ExpectedError: errInvalidMessage, + }, + { + Name: "CustomStructTag", + InputMessage: &CustomTagStruct{}, + InputFieldMaskPaths: completeFieldMask, + ExpectedFields: map[string]string{"some_string": "SomeString"}, + ExpectedError: nil, + }, + } { + 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 map[string]string + ExpectedResult bool + }{ + { + Name: "ShouldBeValidated", + InputField: "this.SomeInt", + ValidFields: allFields, + ExpectedResult: true, + }, + { + Name: "ShouldNotBeValidated", + InputField: "this.SomeInt", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, + ExpectedResult: false, + }, + { + Name: "MalFormedInput", + InputField: "SomeInt", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, + ExpectedResult: true, + }, + { + Name: "NoFieldsToValidate", + InputField: "this.SomeInt", + ValidFields: map[string]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 TestGetProtoNameForField(t *testing.T) { + for _, tc := range []struct { + Name string + InputField string + ValidFields map[string]string + ExpectedResult string + }{ + { + Name: "Exists", + InputField: "SomeInt", + ValidFields: allFields, + ExpectedResult: "some_int", + }, + { + Name: "NoMatch", + InputField: "SomeInt", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, + 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: "MalFormedInput", + InputField: "this.", + ValidFields: map[string]string{"some_string": "SomeString", "some_double": "SomeDouble", "some_repeated": "SomeRepeated", "some_embedded": "SomeEmbedded"}, + ExpectedResult: "", + }, + { + Name: "NoFieldsToValidate", + InputField: "SomeInt", + ValidFields: map[string]string{}, + ExpectedResult: "", + }, + } { + t.Run(tc.Name, func(t *testing.T) { + 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 + 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, + }, + { + 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) + 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, + }, + { + 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) + if err != nil { + t.Fatal("Validator not called") + } + }) + } +} 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,