├── .whitesource ├── gokay ├── doc.go ├── bcp47.go ├── hex.go ├── uuid.go ├── validate.go ├── bcp47_test.go ├── hex_test.go ├── uuid_test.go ├── length.go ├── length_test.go ├── errors.go └── errors_test.go ├── internal └── gkexample │ ├── customgen_test.go │ ├── customgen.go │ ├── example.go │ ├── example_test.go │ └── example_validators.go ├── .gitignore ├── doc.go ├── go.mod ├── LICENSE ├── .circleci └── config.yml ├── Makefile ├── gkgen ├── hexgen_test.go ├── lengthgen_test.go ├── notnil.go ├── notnil_test.go ├── uuidgen_test.go ├── uuidgen.go ├── hexgen.go ├── bcp47gen.go ├── minlength.go ├── bcp47gen_test.go ├── generator_test.go ├── lengthgen.go ├── not_equal.go ├── not_equal_test.go ├── data_test.go ├── minlength_test.go ├── set.go ├── tag_parser.go ├── tag_parser_test.go ├── set_test.go └── generator.go ├── go.sum ├── gokay.go └── README.md /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "settingsInheritedFrom": "zencoder/whitesource-config@main" 3 | } -------------------------------------------------------------------------------- /gokay/doc.go: -------------------------------------------------------------------------------- 1 | // Package gokay is used for defining validation functions that are called in generated Validate functions. 2 | package gokay 3 | -------------------------------------------------------------------------------- /internal/gkexample/customgen_test.go: -------------------------------------------------------------------------------- 1 | package gkexample 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestNewCustomGenerator(t *testing.T) { 10 | require.NotNil(t, NewCustomGenerator()) 11 | } 12 | -------------------------------------------------------------------------------- /gokay/bcp47.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import "golang.org/x/text/language" 4 | 5 | func IsBCP47(s *string) error { 6 | if s == nil || *s == "" { 7 | return nil 8 | } 9 | 10 | _, err := language.Parse(*s) 11 | 12 | // Pass tags that are well-formed, but not in the spec 13 | if _, ok := err.(language.ValueError); ok { 14 | return nil 15 | } 16 | 17 | return err 18 | } 19 | -------------------------------------------------------------------------------- /internal/gkexample/customgen.go: -------------------------------------------------------------------------------- 1 | package gkexample 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/zencoder/gokay/gkgen" 7 | ) 8 | 9 | // To run: `gokay gkexample NewCustomGenerator` 10 | func NewCustomGenerator() *gkgen.ValidateGenerator { 11 | fmt.Println("Generating code with a custom validator that's the same as the default validator") 12 | return gkgen.NewValidateGenerator() 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea 26 | coverage/ 27 | vendor/ 28 | bin/ 29 | -------------------------------------------------------------------------------- /gokay/hex.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var ( 9 | hexRegexp = regexp.MustCompile("^(0x)?[0-9a-fA-F]+$") 10 | ) 11 | 12 | // IsHex validates that the given string is a hex value 13 | func IsHex(s *string) error { 14 | if s == nil { 15 | return nil 16 | } 17 | 18 | if !hexRegexp.MatchString(*s) { 19 | return fmt.Errorf("'%s' is not a hexadecimal string", *s) 20 | } 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // gokay is a tool that parses the structs defined in a go source file and generates a Validate() function for each of 2 | // those structs when necessary. See gkgen documentation for information on how Validate functions are built. 3 | // 4 | // Usage: 5 | // gokay {file_name} ({generator package} {generator contructor}) 6 | // Example: 7 | // gokay file.go gkcustom NewCustomGKGenerator 8 | // It relies on goimports tool to resolve import path of custom generator 9 | package main 10 | -------------------------------------------------------------------------------- /gokay/uuid.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var ( 9 | uuidRegexp = regexp.MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") 10 | ) 11 | 12 | // IsUUID validates that the given string is a UUID value 13 | func IsUUID(s *string) error { 14 | if s == nil { 15 | return nil 16 | } 17 | 18 | if !uuidRegexp.MatchString(*s) { 19 | return fmt.Errorf("'%s' is not a UUID", *s) 20 | } 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zencoder/gokay 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c 8 | github.com/stretchr/testify v1.1.4 9 | golang.org/x/text v0.3.7 10 | golang.org/x/tools v0.1.10 11 | ) 12 | 13 | require ( 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect 16 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect 17 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /gokay/validate.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | // Validateable specifies a generic error return type instead of an 4 | // ErrorMap return type in order to allow for handwritten Validate 5 | // methods to work in tandem with gokay generated Validate methods. 6 | type Validateable interface { 7 | Validate() error 8 | } 9 | 10 | // Validate calls validate on structs that implement the Validateable 11 | // interface. If they do not, then that struct is valid. 12 | func Validate(i interface{}) error { 13 | if v, ok := i.(Validateable); ok { 14 | return v.Validate() 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Brightcove, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/golang 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: go-mod-{{ checksum "go.sum" }} 11 | - run: 12 | name: Download Go dependencies 13 | command: go mod download 14 | - save_cache: 15 | key: go-mod-{{ checksum "go.sum" }} 16 | paths: 17 | - /go/pkg/mod 18 | - run: 19 | name: Run unit tests 20 | command: | 21 | make 22 | experimental: 23 | notify: 24 | branches: 25 | only: 26 | - master 27 | -------------------------------------------------------------------------------- /gokay/bcp47_test.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIsBCP47_Nil(t *testing.T) { 10 | require.NoError(t, IsBCP47(nil)) 11 | } 12 | 13 | func TestIsBCP47_English(t *testing.T) { 14 | str := "English" 15 | require.EqualError(t, IsBCP47(&str), "language: tag is not well-formed") 16 | } 17 | 18 | func TestIsBCP47_en(t *testing.T) { 19 | str := "en" 20 | require.NoError(t, IsBCP47(&str)) 21 | } 22 | 23 | func TestIsBCP47_en_AB(t *testing.T) { 24 | str := "en-AB" 25 | require.NoError(t, IsBCP47(&str)) 26 | } 27 | 28 | func TestIsBCP47_Empty(t *testing.T) { 29 | str := "" 30 | require.NoError(t, IsBCP47(&str)) 31 | } 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Force-enable Go modules even if this project has been cloned within a user's GOPATH 2 | export GO111MODULE = on 3 | 4 | # Specify VERBOSE=1 to get verbose output from all executed commands 5 | ifdef VERBOSE 6 | V = -v 7 | X = -x 8 | else 9 | .SILENT: 10 | endif 11 | 12 | .PHONY: all 13 | all: build test 14 | 15 | .PHONY: clean 16 | clean: 17 | rm -rf bin/ coverage/ cucumber/logs/ 18 | go clean -i $(X) -cache -testcache 19 | 20 | .PHONY: build 21 | build: 22 | mkdir -p bin 23 | go build $(V) -o bin/gokay 24 | 25 | .PHONY: fmt 26 | fmt: 27 | go fmt $(X) ./... 28 | 29 | .PHONY: test 30 | test: 31 | mkdir -p coverage 32 | go test $(V) -race -cover -coverprofile coverage/cover.profile ./... 33 | 34 | .PHONY: cover 35 | cover: 36 | go tool cover -html coverage/cover.profile 37 | -------------------------------------------------------------------------------- /gokay/hex_test.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestNilString(t *testing.T) { 10 | var str *string 11 | require.NoError(t, IsHex(str)) 12 | } 13 | 14 | func TestIsHex_No0x(t *testing.T) { 15 | str := "1a3F" 16 | require.NoError(t, IsHex(&str)) 17 | } 18 | 19 | func TestIsHex_0x(t *testing.T) { 20 | str := "0x1a3F" 21 | require.NoError(t, IsHex(&str)) 22 | } 23 | 24 | func TestIsHex_NotHex(t *testing.T) { 25 | str := "0x1Gbcq" 26 | require.EqualError(t, IsHex(&str), "'0x1Gbcq' is not a hexadecimal string") 27 | } 28 | 29 | func BenchmarkIsHex(b *testing.B) { 30 | benchHex := "0x1234567890ABCDEF" 31 | var err error 32 | for n := 0; n < b.N; n++ { 33 | err = IsHex(&benchHex) 34 | } 35 | devNull(err) 36 | } 37 | 38 | func devNull(i interface{}) {} 39 | -------------------------------------------------------------------------------- /gokay/uuid_test.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIsUUID_No0x(t *testing.T) { 10 | str := "603c9a2a-38dB-4987-932a-2f57733a29f1" 11 | require.NoError(t, IsUUID(&str)) 12 | } 13 | 14 | func TestNilUUID(t *testing.T) { 15 | var str *string 16 | require.NoError(t, IsUUID(str)) 17 | } 18 | 19 | func TestIsUUID_NotMatch(t *testing.T) { 20 | str := "603c9a2a-38db-4987-932a-2f57733a29fQ" 21 | require.EqualError(t, IsUUID(&str), "'603c9a2a-38db-4987-932a-2f57733a29fQ' is not a UUID") 22 | } 23 | 24 | func TestIsUUID_NotUUIDTooLong(t *testing.T) { 25 | str := "AB603c9a2a-38db-4987-932a-2f57733a29fQ" 26 | require.EqualError(t, IsUUID(&str), "'AB603c9a2a-38db-4987-932a-2f57733a29fQ' is not a UUID") 27 | } 28 | 29 | func BenchmarkIsUUID(b *testing.B) { 30 | benchUUID := "603C9a2a-38dB-4987-932a-2f57733a29f1" 31 | var err error 32 | for n := 0; n < b.N; n++ { 33 | err = IsUUID(&benchUUID) 34 | } 35 | devNull(err) 36 | } 37 | -------------------------------------------------------------------------------- /gokay/length.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import "fmt" 4 | 5 | // LengthString checks if the value of a string pointer has a length of exactly 'expected' 6 | func LengthString(expected int64, str *string) error { 7 | if str == nil { 8 | return nil // Covers the case where a value can be Nil OR has a length constraint 9 | } 10 | 11 | if expected != int64(len(*str)) { 12 | return fmt.Errorf("Length was '%d', needs to be '%d'", len(*str), expected) 13 | } 14 | return nil 15 | } 16 | 17 | // LengthSlice not yet implemented 18 | func LengthSlice(expected int64, actual int64) error { 19 | return nil 20 | } 21 | 22 | // MinLengthString checks if the value of a string pointer has a length of at least 'expected' 23 | func MinLengthString(expected int64, str *string) error { 24 | if str == nil { 25 | return nil // Covers the case where a value can be Nil OR has a length constraint 26 | } 27 | 28 | if expected > int64(len(*str)) { 29 | return fmt.Errorf("Length was '%d', needs to be at least '%d'", len(*str), expected) 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /gkgen/hexgen_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestGenerateHexValidationCode_String(t *testing.T) { 12 | hv := NewHexValidator() 13 | e := ExampleStruct{} 14 | et := reflect.TypeOf(e) 15 | field, _ := et.FieldByName("HexString") 16 | 17 | code, err := hv.Generate(et, field, []string{}) 18 | require.NoError(t, err) 19 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 20 | require.Equal(t, "if err := gokay.IsHex(&s.HexString); err != nil {\nerrorsHexString = append(errorsHexString, err)\n}", code) 21 | } 22 | 23 | func TestGenerateHexValidationCode_StringPtr(t *testing.T) { 24 | hv := NewHexValidator() 25 | e := ExampleStruct{} 26 | et := reflect.TypeOf(e) 27 | field, _ := et.FieldByName("HexStringPtr") 28 | code, err := hv.Generate(et, field, []string{}) 29 | require.NoError(t, err) 30 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 31 | require.Equal(t, "if err := gokay.IsHex(s.HexStringPtr); err != nil {\nerrorsHexStringPtr = append(errorsHexStringPtr, err)\n}", code) 32 | } 33 | -------------------------------------------------------------------------------- /gkgen/lengthgen_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestGenerateValidationCode_String(t *testing.T) { 12 | lv := NewLengthValidator() 13 | e := ExampleStruct{} 14 | et := reflect.TypeOf(e) 15 | field, _ := et.FieldByName("HexString") 16 | 17 | code, err := lv.Generate(et, field, []string{"12"}) 18 | require.NoError(t, err) 19 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 20 | require.Equal( 21 | t, 22 | "if err := gokay.LengthString(12, &s.HexString); err != nil {\nerrorsHexString = append(errorsHexString, err)\n}", 23 | code, 24 | ) 25 | } 26 | 27 | func TestGenerateValidationCode_StringPtr(t *testing.T) { 28 | lv := NewLengthValidator() 29 | e := ExampleStruct{} 30 | et := reflect.TypeOf(e) 31 | field, _ := et.FieldByName("HexStringPtr") 32 | code, err := lv.Generate(et, field, []string{"16"}) 33 | require.NoError(t, err) 34 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 35 | require.Equal( 36 | t, 37 | "if err := gokay.LengthString(16, s.HexStringPtr); err != nil {\nerrorsHexStringPtr = append(errorsHexStringPtr, err)\n}", 38 | code, 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /gkgen/notnil.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // NotNilValidator generates code that will verify if a pointer is nil 10 | // Slice and Array support coming later. 11 | // It will flag nil string pointers as valid, use in conjunction with NotNil validator if you don't want nil values 12 | type NotNilValidator struct { 13 | name string 14 | } 15 | 16 | // NewNotNilValidator holds the NotNilValidator state 17 | func NewNotNilValidator() *NotNilValidator { 18 | return &NotNilValidator{name: "NotNil"} 19 | } 20 | 21 | // Generate generates validation code 22 | func (s *NotNilValidator) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 23 | if len(params) != 0 { 24 | return "", errors.New("NotNil takes no parameters") 25 | } 26 | 27 | field := fieldStruct.Type 28 | 29 | switch field.Kind() { 30 | case reflect.Ptr, reflect.Array, reflect.Slice, reflect.Map: 31 | return fmt.Sprintf(` 32 | if s.%[1]s == nil { 33 | errors%[1]s = append(errors%[1]s, errors.New("is Nil")) 34 | }`, fieldStruct.Name), nil 35 | default: 36 | // TODO: Add support for nil slices 37 | return "", fmt.Errorf("NotNil only works on pointer type Fields. %s has type '%s'", fieldStruct.Name, field.Kind()) 38 | } 39 | } 40 | 41 | // Name provides access to the name field 42 | func (s *NotNilValidator) Name() string { 43 | return s.name 44 | } 45 | -------------------------------------------------------------------------------- /gkgen/notnil_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestNotNil(t *testing.T) { 12 | nv := NewNotNilValidator() 13 | e := ExampleStruct{} 14 | et := reflect.TypeOf(e) 15 | 16 | field, _ := et.FieldByName("HexStringPtr") 17 | code, err := nv.Generate(et, field, []string{}) 18 | require.NoError(t, err) 19 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 20 | require.Equal( 21 | t, 22 | "if s.HexStringPtr == nil {\nerrorsHexStringPtr = append(errorsHexStringPtr, errors.New(\"is Nil\"))\n}", 23 | code, 24 | ) 25 | } 26 | 27 | func TestNotNil_Map(t *testing.T) { 28 | nv := NewNotNilValidator() 29 | e := NotNilTestStruct{} 30 | et := reflect.TypeOf(e) 31 | 32 | field, _ := et.FieldByName("NotNilMap") 33 | code, err := nv.Generate(et, field, []string{}) 34 | require.NoError(t, err) 35 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 36 | require.Equal( 37 | t, 38 | "if s.NotNilMap == nil {\nerrorsNotNilMap = append(errorsNotNilMap, errors.New(\"is Nil\"))\n}", 39 | code, 40 | ) 41 | } 42 | 43 | func TestNotNil_Slice(t *testing.T) { 44 | nv := NewNotNilValidator() 45 | e := NotNilTestStruct{} 46 | et := reflect.TypeOf(e) 47 | 48 | field, _ := et.FieldByName("NotNilSlice") 49 | code, err := nv.Generate(et, field, []string{}) 50 | require.NoError(t, err) 51 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 52 | require.Equal( 53 | t, 54 | "if s.NotNilSlice == nil {\nerrorsNotNilSlice = append(errorsNotNilSlice, errors.New(\"is Nil\"))\n}", 55 | code, 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /gkgen/uuidgen_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestGenerateUUIDValidationCode_String(t *testing.T) { 13 | v := NewUUIDValidator() 14 | e := UUIDTestStruct{} 15 | et := reflect.TypeOf(e) 16 | field, _ := et.FieldByName("UUIDString") 17 | 18 | code, err := v.Generate(et, field, []string{}) 19 | require.NoError(t, err) 20 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 21 | require.Equal(t, "if err := gokay.IsUUID(&s.UUIDString); err != nil {\nerrorsUUIDString = append(errorsUUIDString, err)\n}", code) 22 | } 23 | 24 | func TestGenerateUUIDValidationCode_StringPtr(t *testing.T) { 25 | v := NewUUIDValidator() 26 | e := UUIDTestStruct{} 27 | et := reflect.TypeOf(e) 28 | field, _ := et.FieldByName("UUIDStringPtr") 29 | code, err := v.Generate(et, field, []string{}) 30 | require.NoError(t, err) 31 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 32 | require.Equal(t, "if err := gokay.IsUUID(s.UUIDStringPtr); err != nil {\nerrorsUUIDStringPtr = append(errorsUUIDStringPtr, err)\n}", code) 33 | } 34 | 35 | func TestGenerateUUIDValidationCode_NonString(t *testing.T) { 36 | v := NewUUIDValidator() 37 | e := UUIDTestStruct{} 38 | et := reflect.TypeOf(e) 39 | 40 | field, _ := et.FieldByName("UUIDNonString") 41 | _, err := v.Generate(et, field, []string{}) 42 | require.Equal(t, errors.New("UUIDValidator does not support fields of type: 'int'"), err) 43 | } 44 | 45 | type UUIDTestStruct struct { 46 | UUIDString string `valid:"UUID"` 47 | UUIDStringPtr *string `valid:"UUID"` 48 | UUIDNonString int `valid:"UUID"` 49 | } 50 | -------------------------------------------------------------------------------- /gokay/length_test.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestLengthNilString(t *testing.T) { 10 | var expected = int64(10) 11 | var str *string 12 | require.NoError(t, LengthString(expected, str)) 13 | } 14 | 15 | func TestNotLengthNonMatch(t *testing.T) { 16 | var expected = int64(10) 17 | var str string = "012345678" 18 | require.Error(t, LengthString(expected, &str)) 19 | } 20 | 21 | func TestNotLengthMatch(t *testing.T) { 22 | var expected = int64(10) 23 | var str string = "0123456789" 24 | require.NoError(t, LengthString(expected, &str)) 25 | } 26 | 27 | func TestLengthSlice(t *testing.T) { 28 | var expected = int64(10) 29 | var actual = int64(99) 30 | require.NoError(t, LengthSlice(expected, actual)) 31 | } 32 | 33 | func TestMinLengthString(t *testing.T) { 34 | var validCases = []struct { 35 | String string 36 | MinLength int64 37 | }{ 38 | {String: "foo", MinLength: 1}, 39 | {String: "foo", MinLength: 3}, 40 | {String: "", MinLength: 0}, 41 | {String: "a", MinLength: 1}, 42 | } 43 | for _, tc := range validCases { 44 | tc := tc 45 | t.Run("", func(t *testing.T) { 46 | t.Parallel() 47 | require.NoError(t, MinLengthString(tc.MinLength, &tc.String)) 48 | }) 49 | } 50 | 51 | var invalidCases = []struct { 52 | String string 53 | MinLength int64 54 | }{ 55 | {String: "1", MinLength: 2}, 56 | {String: "a", MinLength: 100}, 57 | {String: "", MinLength: 1}, 58 | } 59 | for _, tc := range invalidCases { 60 | tc := tc 61 | t.Run("", func(t *testing.T) { 62 | t.Parallel() 63 | require.Error(t, MinLengthString(tc.MinLength, &tc.String)) 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gkgen/uuidgen.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // UUIDValidator generates code that will verify if a field is a UUID string 10 | type UUIDValidator struct { 11 | name string 12 | } 13 | 14 | // NewUUIDValidator holds the UUIDValidator state 15 | func NewUUIDValidator() *UUIDValidator { 16 | return &UUIDValidator{name: "UUID"} 17 | } 18 | 19 | // Generate generates validation code 20 | func (s *UUIDValidator) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 21 | if len(params) != 0 { 22 | return "", errors.New("Hex takes no parameters") 23 | } 24 | 25 | field := fieldStruct.Type 26 | 27 | isPtr := false 28 | if field.Kind() == reflect.Ptr { 29 | field = field.Elem() 30 | isPtr = true 31 | } 32 | 33 | switch field.Kind() { 34 | case reflect.Ptr: 35 | return "", errors.New("UUIDValidator does not support nested pointer fields") 36 | case reflect.String: 37 | if isPtr { 38 | return fmt.Sprintf(` 39 | if err := gokay.IsUUID(s.%[1]s); err != nil { 40 | errors%[1]s = append(errors%[1]s, err) 41 | } 42 | `, fieldStruct.Name), nil 43 | } 44 | return fmt.Sprintf(` 45 | if err := gokay.IsUUID(&s.%[1]s); err != nil { 46 | errors%[1]s = append(errors%[1]s, err) 47 | } 48 | `, fieldStruct.Name), nil 49 | default: 50 | if isPtr { 51 | return "", fmt.Errorf("UUIDValidator does not support fields of type: '*%v'", field.Kind()) 52 | } 53 | return "", fmt.Errorf("UUIDValidator does not support fields of type: '%v'", field.Kind()) 54 | } 55 | } 56 | 57 | // Name provides access to the name field 58 | func (s *UUIDValidator) Name() string { 59 | return s.name 60 | } 61 | -------------------------------------------------------------------------------- /gkgen/hexgen.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // HexValidator generates code that will verify if a field is a hex string 10 | // 0x prefix is optional 11 | type HexValidator struct { 12 | name string 13 | } 14 | 15 | // NewHexValidator holds the HexValidator state 16 | func NewHexValidator() *HexValidator { 17 | return &HexValidator{name: "Hex"} 18 | } 19 | 20 | // Generate generates validation code 21 | func (s *HexValidator) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 22 | if len(params) != 0 { 23 | return "", errors.New("Hex takes no parameters") 24 | } 25 | 26 | field := fieldStruct.Type 27 | 28 | isPtr := false 29 | if field.Kind() == reflect.Ptr { 30 | field = field.Elem() 31 | isPtr = true 32 | } 33 | 34 | switch field.Kind() { 35 | case reflect.Ptr: 36 | return "", errors.New("HexValidator does not support nested pointer fields") 37 | case reflect.String: 38 | if isPtr { 39 | return fmt.Sprintf(` 40 | if err := gokay.IsHex(s.%[1]s); err != nil { 41 | errors%[1]s = append(errors%[1]s, err) 42 | } 43 | `, fieldStruct.Name), nil 44 | } 45 | return fmt.Sprintf(` 46 | if err := gokay.IsHex(&s.%[1]s); err != nil { 47 | errors%[1]s = append(errors%[1]s, err) 48 | } 49 | `, fieldStruct.Name), nil 50 | default: 51 | if isPtr { 52 | return "", fmt.Errorf("HexValidator does not support fields of type: '*%v'", field.Kind()) 53 | } 54 | return "", fmt.Errorf("HexValidator does not support fields of type: '%v'", field.Kind()) 55 | } 56 | } 57 | 58 | // Name provides access to the name field 59 | func (s *HexValidator) Name() string { 60 | return s.name 61 | } 62 | -------------------------------------------------------------------------------- /gkgen/bcp47gen.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // BCP47Validator generates code that will verify if a field is a BCP47 compatible string 10 | // https://tools.ietf.org/html/bcp47 11 | type BCP47Validator struct { 12 | name string 13 | } 14 | 15 | // NewBCP47Validator holds the BCP47Validator state 16 | func NewBCP47Validator() *BCP47Validator { 17 | return &BCP47Validator{name: "BCP47"} 18 | } 19 | 20 | // Generate generates validation code 21 | func (s *BCP47Validator) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 22 | if len(params) != 0 { 23 | return "", errors.New("BCP47 takes no parameters") 24 | } 25 | 26 | field := fieldStruct.Type 27 | 28 | isPtr := false 29 | if field.Kind() == reflect.Ptr { 30 | field = field.Elem() 31 | isPtr = true 32 | } 33 | 34 | switch field.Kind() { 35 | case reflect.Ptr: 36 | return "", errors.New("BCP47Validator does not support nested pointer fields") 37 | case reflect.String: 38 | if isPtr { 39 | return fmt.Sprintf(` 40 | if err := gokay.IsBCP47(s.%[1]s); err != nil { 41 | errors%[1]s = append(errors%[1]s, err) 42 | } 43 | `, fieldStruct.Name), nil 44 | } 45 | return fmt.Sprintf(` 46 | if err := gokay.IsBCP47(&s.%[1]s); err != nil { 47 | errors%[1]s = append(errors%[1]s, err) 48 | } 49 | `, fieldStruct.Name), nil 50 | default: 51 | if isPtr { 52 | return "", fmt.Errorf("BCP47Validator does not support fields of type: '*%v'", field.Kind()) 53 | } 54 | return "", fmt.Errorf("BCP47Validator does not support fields of type: '%v'", field.Kind()) 55 | } 56 | } 57 | 58 | // Name provides access to the name field 59 | func (s *BCP47Validator) Name() string { 60 | return s.name 61 | } 62 | -------------------------------------------------------------------------------- /gokay/errors.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // ErrorMap contains an entry for each invalid field in a struct. Values can be 10 | // any struct that implements the go Error interface, including nested ErrorMaps. 11 | type ErrorMap map[string]error 12 | 13 | // Error returns a JSON formatted representation of the ErrorMap. 14 | func (em ErrorMap) Error() string { 15 | out := &bytes.Buffer{} 16 | out.WriteRune('{') 17 | for k, v := range em { 18 | if out.Len() > 1 { 19 | out.WriteRune(',') 20 | } 21 | if v != nil { 22 | switch v.(type) { 23 | case ErrorSlice, ErrorMap: 24 | fmt.Fprintf(out, `"%s": %s`, k, v.Error()) 25 | default: 26 | fmt.Fprintf(out, `"%s": "%s"`, k, strings.Replace(v.Error(), `"`, `\"`, -1)) 27 | } 28 | } else { 29 | fmt.Fprintf(out, `"%s": null`, k) 30 | } 31 | } 32 | out.WriteRune('}') 33 | return out.String() 34 | } 35 | 36 | // ErrorSlice is a slice of errors. Typically an ErrorSlice will be an entry 37 | // in the ErrorMap outputted by a generated Validate function, each element 38 | // of the array represents a failed validation on that field. 39 | type ErrorSlice []error 40 | 41 | // Returns a JSON formatted representation of the ErrorSlice 42 | func (ea ErrorSlice) Error() string { 43 | out := &bytes.Buffer{} 44 | out.WriteRune('[') 45 | for i := range ea { 46 | if i != 0 { 47 | out.WriteRune(',') 48 | } 49 | if ea[i] != nil { 50 | switch ea[i].(type) { 51 | case ErrorSlice, ErrorMap: 52 | fmt.Fprintf(out, `%s`, ea[i].Error()) 53 | default: 54 | fmt.Fprintf(out, `"%s"`, strings.Replace(ea[i].Error(), `"`, `\"`, -1)) 55 | } 56 | } else { 57 | out.WriteString("null") 58 | } 59 | } 60 | out.WriteRune(']') 61 | return out.String() 62 | } 63 | -------------------------------------------------------------------------------- /gkgen/minlength.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | // MinLengthValidateGen generates code that will verify the MinLength of a String or String pointer. 11 | // It will flag nil pointers as valid, use in conjunction with NotNil validator if you don't want nil values 12 | type MinLengthValidateGen struct { 13 | name string 14 | } 15 | 16 | // NewMinLengthValidator holds MinLengthValidator state 17 | func NewMinLengthValidator() *MinLengthValidateGen { 18 | return &MinLengthValidateGen{name: "MinLength"} 19 | } 20 | 21 | // Generate generates validation code 22 | func (s *MinLengthValidateGen) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 23 | if len(params) != 1 { 24 | return "", errors.New("MinLength validation requires exactly 1 parameter") 25 | } 26 | 27 | expectedMinLength, err := strconv.Atoi(params[0]) 28 | if err != nil { 29 | return "", err 30 | } 31 | field := fieldStruct.Type 32 | 33 | if field.Kind() == reflect.Ptr { 34 | field = field.Elem() 35 | switch field.Kind() { 36 | case reflect.String: 37 | return fmt.Sprintf(` 38 | if err := gokay.MinLengthString(%d, s.%[2]s); err != nil { 39 | errors%[2]s = append(errors%[2]s, err) 40 | } 41 | `, expectedMinLength, fieldStruct.Name), nil 42 | default: 43 | return "", fmt.Errorf("MinLengthValidator does not support fields of type: '*%v'", field.Kind()) 44 | } 45 | } 46 | 47 | switch field.Kind() { 48 | case reflect.String: 49 | return fmt.Sprintf(` 50 | if err := gokay.MinLengthString(%d, &s.%[2]s); err != nil { 51 | errors%[2]s = append(errors%[2]s, err) 52 | } 53 | `, expectedMinLength, fieldStruct.Name), nil 54 | default: 55 | return "", fmt.Errorf("MinLengthValidator does not support fields of type: '%v'", field.Kind()) 56 | } 57 | } 58 | 59 | // Name provides access to the name field 60 | func (s *MinLengthValidateGen) Name() string { 61 | return s.name 62 | } 63 | -------------------------------------------------------------------------------- /gkgen/bcp47gen_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | type TestData struct { 12 | BCP47String *string 13 | } 14 | 15 | func TestIsBCP47_ParamsLength(t *testing.T) { 16 | params := []string{"I'll break..."} 17 | et := reflect.TypeOf(ExampleStruct{}) 18 | field, _ := et.FieldByName("BCP47String") 19 | b := NewBCP47Validator() 20 | _, err := b.Generate(et, field, params) 21 | require.Error(t, err) 22 | } 23 | 24 | func TestIsBCP47_FieldPtr(t *testing.T) { 25 | et := reflect.TypeOf(ExampleStruct{}) 26 | field, _ := et.FieldByName("BCP47NonString") 27 | b := NewBCP47Validator() 28 | _, err := b.Generate(et, field, []string{}) 29 | require.Error(t, err) 30 | } 31 | 32 | func TestIsBCP47_FieldNestedPtr(t *testing.T) { 33 | et := reflect.TypeOf(ExampleStruct{}) 34 | field, _ := et.FieldByName("BCP47NonStringPtr") 35 | b := NewBCP47Validator() 36 | _, err := b.Generate(et, field, []string{}) 37 | require.Error(t, err) 38 | } 39 | 40 | func TestGenerateBCP47ValidationCode_String(t *testing.T) { 41 | hv := NewBCP47Validator() 42 | e := ExampleStruct{} 43 | et := reflect.TypeOf(e) 44 | field, _ := et.FieldByName("BCP47String") 45 | 46 | code, err := hv.Generate(et, field, []string{}) 47 | require.NoError(t, err) 48 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 49 | require.Equal( 50 | t, 51 | "if err := gokay.IsBCP47(&s.BCP47String); err != nil {\nerrorsBCP47String = append(errorsBCP47String, err)\n}", 52 | code, 53 | ) 54 | } 55 | 56 | func TestGenerateBCP47ValidationCode_StringPtr(t *testing.T) { 57 | hv := NewBCP47Validator() 58 | e := ExampleStruct{} 59 | et := reflect.TypeOf(e) 60 | field, _ := et.FieldByName("BCP47StringPtr") 61 | code, err := hv.Generate(et, field, []string{}) 62 | require.NoError(t, err) 63 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 64 | require.Equal( 65 | t, 66 | "if err := gokay.IsBCP47(s.BCP47StringPtr); err != nil {\nerrorsBCP47StringPtr = append(errorsBCP47StringPtr, err)\n}", 67 | code, 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /gkgen/generator_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestAddValidation(t *testing.T) { 13 | v := ValidateGenerator{ 14 | Generators: make(map[string]Generater), 15 | } 16 | generator := NewNotNilValidator() 17 | 18 | _, ok := v.Generators[generator.Name()] 19 | require.False(t, ok) 20 | 21 | v.AddValidation(generator) 22 | _, ok = v.Generators[generator.Name()] 23 | require.True(t, ok) 24 | } 25 | 26 | // TestExampleStruct tests single no-param validation 27 | func TestExampleStruct(t *testing.T) { 28 | out := &bytes.Buffer{} 29 | key := "abc123" 30 | e := ExampleStruct{ 31 | HexStringPtr: &key, 32 | } 33 | 34 | v := NewValidateGenerator() 35 | 36 | err := v.Generate(out, e) 37 | require.NoError(t, err) 38 | } 39 | 40 | type UnknownTagStruct struct { 41 | Field string `valid:"Length=(5),Unknown"` 42 | } 43 | 44 | func TestGenerateWithUnknownTag(t *testing.T) { 45 | out := &bytes.Buffer{} 46 | v := NewValidateGenerator() 47 | err := v.Generate(out, UnknownTagStruct{}) 48 | require.Equal(t, errors.New("Unknown validation generator name: 'Unknown'"), err) 49 | } 50 | 51 | func TestGenerateMapValidationCodeNonArrayOrSlice(t *testing.T) { 52 | et := reflect.TypeOf(ExampleStruct{}) 53 | field, _ := et.FieldByName("BCP47NonString") 54 | out := &bytes.Buffer{} 55 | err := generateMapValidationCode(out, field.Type, "BCP47NonString", int64(1)) 56 | require.Error(t, err) 57 | } 58 | 59 | func TestGenerateSliceValidationCodeNonSlice(t *testing.T) { 60 | et := reflect.TypeOf(ExampleStruct{}) 61 | field, _ := et.FieldByName("BCP47NonString") 62 | out := &bytes.Buffer{} 63 | err := generateSliceValidationCode(out, field.Type, "BCP47NonString", int64(1)) 64 | require.Error(t, err) 65 | } 66 | 67 | func TestGenerateSliceValidationCodeSlice(t *testing.T) { 68 | et := reflect.TypeOf(NotNilTestStruct{}) 69 | field, _ := et.FieldByName("NotNilSlice") 70 | out := &bytes.Buffer{} 71 | err := generateSliceValidationCode(out, field.Type, field.Name, int64(1)) 72 | require.Error(t, err) 73 | } 74 | -------------------------------------------------------------------------------- /gkgen/lengthgen.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | // LengthValidateGen generates code that will verify the exact length of a String or String Pointer field. 11 | // Slice and Array support coming later. 12 | // It will flag nil string pointers as valid, use in conjunction with NotNil validator if you don't want nil values 13 | type LengthValidateGen struct { 14 | name string 15 | } 16 | 17 | // NewLengthValidator holds LengthValidator state 18 | func NewLengthValidator() *LengthValidateGen { 19 | return &LengthValidateGen{name: "Length"} 20 | } 21 | 22 | // Generate generates validation code 23 | func (s *LengthValidateGen) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 24 | if len(params) != 1 { 25 | return "", errors.New("Length validation requires exactly 1 parameter") 26 | } 27 | 28 | expectedLength, err := strconv.Atoi(params[0]) 29 | if err != nil { 30 | return "", err 31 | } 32 | field := fieldStruct.Type 33 | 34 | isPtr := false 35 | if field.Kind() == reflect.Ptr { 36 | field = field.Elem() 37 | isPtr = true 38 | } 39 | 40 | switch field.Kind() { 41 | case reflect.Ptr: 42 | return "", errors.New("LengthValidator does not support nested pointer fields") 43 | case reflect.String: 44 | if isPtr { 45 | return fmt.Sprintf(` 46 | if err := gokay.LengthString(%d, s.%[2]s); err != nil { 47 | errors%[2]s = append(errors%[2]s, err) 48 | } 49 | `, expectedLength, fieldStruct.Name), nil 50 | } 51 | return fmt.Sprintf(` 52 | if err := gokay.LengthString(%d, &s.%[2]s); err != nil { 53 | errors%[2]s = append(errors%[2]s, err) 54 | } 55 | `, expectedLength, fieldStruct.Name), nil 56 | case reflect.Slice, reflect.Array: 57 | return "", errors.New("Length validator does not yet support Slice or Arrays") 58 | default: 59 | if isPtr { 60 | return "", fmt.Errorf("LengthValidator does not support fields of type: '*%v'", field.Kind()) 61 | } 62 | return "", fmt.Errorf("LengthValidator does not support fields of type: '%v'", field.Kind()) 63 | } 64 | } 65 | 66 | // Name provides access to the name field 67 | func (s *LengthValidateGen) Name() string { 68 | return s.name 69 | } 70 | -------------------------------------------------------------------------------- /internal/gkexample/example.go: -------------------------------------------------------------------------------- 1 | package gkexample 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/zencoder/gokay/gokay" 7 | ) 8 | 9 | type NoImplicitValidate struct { 10 | StringField string 11 | 12 | NonStringKeyMap map[int]TestValidate 13 | NestedNonStringKeyMap map[string]map[int]map[string]*TestValidate 14 | NestedMapWithNonStructValue map[string]map[string]map[string]*int64 15 | 16 | SliceOfBuiltins []string 17 | NestedSliceofBuiltins [][][]*int 18 | } 19 | 20 | type HasValidateImplicit struct { 21 | InvalidStruct *TestValidate 22 | ValidStruct AlwaysValid 23 | 24 | MapOfStruct map[string]TestValidate 25 | MapOfStructPtrs map[string]*TestValidate 26 | MapOfMaps map[string]map[string]*TestValidate 27 | MapMapsOfSlices map[string]map[string][]*TestValidate 28 | MapOfInterfaces map[string]interface{} 29 | 30 | SimpleSlice []*TestValidate 31 | SliceOfSlicesOfSlices [][][]*TestValidate 32 | 33 | MapOfSlicesOfMaps map[string][]map[string]*TestValidate 34 | } 35 | 36 | type NotNilTestStruct struct { 37 | NotNilMap map[string]interface{} `valid:"NotNil"` 38 | NotNilSlice []string `valid:"NotNil"` 39 | NotNilInterface interface{} 40 | } 41 | 42 | // Example struct definition with tags 43 | type ExampleStruct struct { 44 | HexStringPtr *string `valid:"Length=(16),NotNil,Hex"` 45 | HexString string `valid:"Length=(12),Hex"` 46 | BCP47StringPtr *string `valid:"NotNil,BCP47"` 47 | BCP47String string `valid:"BCP47"` 48 | CanBeNilWithConstraints *string `valid:"Length=(12)"` 49 | BCP47NonString int 50 | BCP47NonStringPtr *int 51 | } 52 | 53 | type NotEqualTestStruct struct { 54 | NotEqualString string `valid:"NotEqual=()"` 55 | NotEqualStringPtr *string `valid:"NotEqual=(gokay)"` 56 | NotEqualInt64 int64 `valid:"NotEqual=(0)"` 57 | NotEqualInt64Ptr *int64 `valid:"NotEqual=(7)"` 58 | } 59 | 60 | type SetTestStruct struct { 61 | SetString string `valid:"Set=(cat)(dog)(mouse)"` 62 | SetStringPtr *string `valid:"Set=(cat)(dog)(mouse)"` 63 | } 64 | 65 | type TestValidate struct { 66 | Valid bool 67 | } 68 | 69 | func (s TestValidate) Validate() error { 70 | if !s.Valid { 71 | return gokay.ErrorMap{ 72 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | type AlwaysValid struct{} 79 | 80 | func (s AlwaysValid) Validate() error { 81 | return nil 82 | } 83 | 84 | type Example struct { 85 | MapOfInterfaces map[string]interface{} `valid:"NotNil"` 86 | } 87 | -------------------------------------------------------------------------------- /gokay/errors_test.go: -------------------------------------------------------------------------------- 1 | package gokay 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | type NoValidate struct{} 12 | 13 | type HasValidate struct{} 14 | 15 | func (s HasValidate) Validate() error { 16 | return errors.New("Validating 'HasValidate' instance") 17 | } 18 | 19 | func TestValidate(t *testing.T) { 20 | a := HasValidate{} 21 | b := &HasValidate{} 22 | c := NoValidate{} 23 | 24 | err := Validate(a) 25 | require.Equal(t, errors.New("Validating 'HasValidate' instance"), err) 26 | 27 | err = Validate(b) 28 | require.Equal(t, errors.New("Validating 'HasValidate' instance"), err) 29 | 30 | err = Validate(c) 31 | require.NoError(t, err) 32 | } 33 | 34 | func TestErrorSliceError_Empty(t *testing.T) { 35 | ea := ErrorSlice{} 36 | 37 | require.Equal(t, "[]", ea.Error()) 38 | } 39 | 40 | func TestErrorSliceError_MultiElements(t *testing.T) { 41 | ea := ErrorSlice{ 42 | errors.New("foo"), 43 | errors.New("bar"), 44 | nil, 45 | ErrorSlice{errors.New("this is"), errors.New("nested")}, 46 | } 47 | 48 | require.Equal(t, "[\"foo\",\"bar\",null,[\"this is\",\"nested\"]]", ea.Error()) 49 | } 50 | 51 | func TestErrorMapError_Empty(t *testing.T) { 52 | em := ErrorMap{} 53 | require.Equal(t, "{}", em.Error()) 54 | } 55 | 56 | func TestErrorMapError_NilValue(t *testing.T) { 57 | em := ErrorMap{ 58 | "flat": nil, 59 | "nestedErrorSlice": ErrorSlice{errors.New("this is"), errors.New("nested")}, 60 | "nestedEmptyErrorMap": make(ErrorMap), 61 | } 62 | 63 | expectedJSONAsMap := make(map[string]interface{}) 64 | actualJSONAsMap := make(map[string]interface{}) 65 | json.Unmarshal([]byte(`{"flat": null,"nestedErrorSlice": ["this is","nested"],"nestedEmptyErrorMap": {}}`), &expectedJSONAsMap) 66 | json.Unmarshal([]byte(em.Error()), &actualJSONAsMap) 67 | 68 | require.Equal(t, expectedJSONAsMap, actualJSONAsMap) 69 | } 70 | 71 | func TestErrorMapError_MultipleValues(t *testing.T) { 72 | em := ErrorMap{ 73 | "flat": errors.New(`"flat" "error"`), 74 | "nestedErrorSlice": ErrorSlice{errors.New("this is"), errors.New("nested")}, 75 | "nestedEmptyErrorMap": make(ErrorMap), 76 | } 77 | 78 | expectedJSONAsMap := make(map[string]interface{}) 79 | actualJSONAsMap := make(map[string]interface{}) 80 | json.Unmarshal([]byte(`{"flat": "\"flat\" \"error\"","nestedErrorSlice": ["this is","nested"],"nestedEmptyErrorMap": {}}`), &expectedJSONAsMap) 81 | json.Unmarshal([]byte(em.Error()), &actualJSONAsMap) 82 | 83 | require.Equal(t, expectedJSONAsMap, actualJSONAsMap) 84 | } 85 | -------------------------------------------------------------------------------- /gkgen/not_equal.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // NotEqualValidator generates code that will verify a fields does not equal a set value 10 | // The validator will look at the field or the dereferenced value of the field 11 | // nil values for a field are not considered invalid 12 | type NotEqualValidator struct { 13 | name string 14 | } 15 | 16 | // NewNotEqualValidator holds the NotEqualValidator state 17 | func NewNotEqualValidator() *NotEqualValidator { 18 | return &NotEqualValidator{name: "NotEqual"} 19 | } 20 | 21 | // Generate generates validation code 22 | func (s *NotEqualValidator) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 23 | if len(params) != 1 { 24 | return "", errors.New("NotEqual validation requires exactly 1 parameter") 25 | } 26 | 27 | restrictedValue := params[0] 28 | field := fieldStruct.Type 29 | 30 | switch field.Kind() { 31 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 32 | reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 33 | return fmt.Sprintf(` 34 | if s.%[1]s == %[2]s { 35 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s cannot equal '%[2]s'")) 36 | }`, fieldStruct.Name, restrictedValue), nil 37 | case reflect.String: 38 | return fmt.Sprintf(` 39 | if s.%[1]s == "%[2]s" { 40 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s cannot equal '%[2]s'")) 41 | }`, fieldStruct.Name, restrictedValue), nil 42 | case reflect.Ptr: 43 | field = field.Elem() 44 | switch field.Kind() { 45 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 46 | reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 47 | return fmt.Sprintf(` 48 | if s.%[1]s != nil && *s.%[1]s == %[2]s { 49 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s cannot equal '%[2]s'")) 50 | }`, fieldStruct.Name, restrictedValue), nil 51 | case reflect.String: 52 | return fmt.Sprintf(` 53 | if s.%[1]s != nil && *s.%[1]s == "%[2]s" { 54 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s cannot equal '%[2]s'")) 55 | }`, fieldStruct.Name, restrictedValue), nil 56 | default: 57 | return "", fmt.Errorf("NotEqual does not work on type '%s'", field.Kind()) 58 | } 59 | default: 60 | return "", fmt.Errorf("NotEqual does not work on type '%s'", field.Kind()) 61 | } 62 | } 63 | 64 | // Name provides access to the name field 65 | func (s *NotEqualValidator) Name() string { 66 | return s.name 67 | } 68 | -------------------------------------------------------------------------------- /gkgen/not_equal_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestNotEqual(t *testing.T) { 13 | nv := NewNotEqualValidator() 14 | e := NotEqualTestStruct{} 15 | et := reflect.TypeOf(e) 16 | 17 | for i := 0; i < et.NumField()-1; i++ { 18 | field := et.Field(i) 19 | expectedComparer := `0` 20 | if field.Name == "NotEqualString" { 21 | expectedComparer = `""` 22 | } 23 | code, err := nv.Generate(et, field, []string{expectedComparer}) 24 | require.NoError(t, err) 25 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 26 | require.Equal( 27 | t, 28 | fmt.Sprintf("if s.%[1]s == %[2]s {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s cannot equal '%[2]s'\"))\n}", field.Name, expectedComparer), 29 | code, 30 | ) 31 | } 32 | } 33 | 34 | func TestNotEqualPointer(t *testing.T) { 35 | nv := NewNotEqualValidator() 36 | e := NotEqualTestPointerStruct{} 37 | et := reflect.TypeOf(e) 38 | 39 | for i := 0; i < et.NumField()-1; i++ { 40 | field := et.Field(i) 41 | expectedComparer := `0` 42 | if field.Name == "NotEqualString" { 43 | expectedComparer = `""` 44 | } 45 | code, err := nv.Generate(et, field, []string{expectedComparer}) 46 | require.NoError(t, err) 47 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 48 | require.Equal( 49 | t, 50 | fmt.Sprintf("if s.%[1]s != nil && *s.%[1]s == %[2]s {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s cannot equal '%[2]s'\"))\n}", field.Name, expectedComparer), 51 | code, 52 | ) 53 | } 54 | } 55 | 56 | func TestNotEqualInvalidTypes(t *testing.T) { 57 | nv := NewNotEqualValidator() 58 | e := NotNilTestStruct{} 59 | et := reflect.TypeOf(e) 60 | 61 | field, _ := et.FieldByName("NotNilMap") 62 | _, err := nv.Generate(et, field, []string{"0"}) 63 | require.Error(t, err) 64 | require.Equal( 65 | t, 66 | "NotEqual does not work on type 'map'", 67 | err.Error(), 68 | ) 69 | 70 | field, _ = et.FieldByName("NotNilSlice") 71 | _, err = nv.Generate(et, field, []string{"test"}) 72 | require.Error(t, err) 73 | require.Equal( 74 | t, 75 | "NotEqual does not work on type 'slice'", 76 | err.Error(), 77 | ) 78 | } 79 | 80 | func TestNotEqualInvalidParameters(t *testing.T) { 81 | nv := NewNotEqualValidator() 82 | e := NotEqualTestStruct{} 83 | et := reflect.TypeOf(e) 84 | 85 | field, _ := et.FieldByName("NotEqualInt") 86 | _, err := nv.Generate(et, field, []string{}) 87 | require.Error(t, err) 88 | require.Equal( 89 | t, 90 | "NotEqual validation requires exactly 1 parameter", 91 | err.Error(), 92 | ) 93 | 94 | field, _ = et.FieldByName("NotEqualInt") 95 | _, err = nv.Generate(et, field, []string{"0", "-0"}) 96 | require.Error(t, err) 97 | require.Equal( 98 | t, 99 | "NotEqual validation requires exactly 1 parameter", 100 | err.Error(), 101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /gkgen/data_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | // ExampleStruct is used in a number of unit tests in this package 4 | type ExampleStruct struct { 5 | HexStringPtr *string `valid:"Length=(16),NotNil,Hex"` 6 | HexString string `valid:"Length=(12),Hex"` 7 | BCP47StringPtr *string `valid:"NotNil,BCP47"` 8 | BCP47String string `valid:"BCP47"` 9 | CanBeNilWithConstraints *string `valid:"Length=(12)"` 10 | BCP47NonString int 11 | BCP47NonStringPtr *int 12 | } 13 | 14 | // NotNilTestStruct is used in the NotNil unit tests 15 | type NotNilTestStruct struct { 16 | NotNilMap map[string]interface{} `valid:"NotNil"` 17 | NotNilSlice []string `valid:"NotNil"` 18 | NotNilInterface interface{} 19 | } 20 | 21 | // NotEqualTestStruct is used in the NotEqual unit tests 22 | type NotEqualTestStruct struct { 23 | NotEqualInt int `valid:"NotEqual=(0)"` 24 | NotEqualInt8 int8 `valid:"NotEqual=(0)"` 25 | NotEqualInt16 int16 `valid:"NotEqual=(0)"` 26 | NotEqualInt32 int32 `valid:"NotEqual=(0)"` 27 | NotEqualInt64 int64 `valid:"NotEqual=(0)"` 28 | NotEqualUint uint `valid:"NotEqual=(0)"` 29 | NotEqualUint8 uint8 `valid:"NotEqual=(0)"` 30 | NotEqualUint16 uint16 `valid:"NotEqual=(0)"` 31 | NotEqualUint32 uint32 `valid:"NotEqual=(0)"` 32 | NotEqualUint64 uint64 `valid:"NotEqual=(0)"` 33 | NotEqualUintptr uintptr `valid:"NotEqual=(0)"` 34 | NotEqualFloat32 float32 `valid:"NotEqual=(0)"` 35 | NotEqualFloat64 float64 `valid:"NotEqual=(0)"` 36 | NotEqualComplex64 complex64 `valid:"NotEqual=(0)"` 37 | NotEqualComplex128 complex128 `valid:"NotEqual=(0)"` 38 | NotEqualString string `valid:"NotEqual=()"` 39 | } 40 | 41 | // NotEqualTestPointerStruct is used in the NotEqual unit tests 42 | type NotEqualTestPointerStruct struct { 43 | NotEqualInt *int `valid:"NotEqual=(0)"` 44 | NotEqualInt8 *int8 `valid:"NotEqual=(0)"` 45 | NotEqualInt16 *int16 `valid:"NotEqual=(0)"` 46 | NotEqualInt32 *int32 `valid:"NotEqual=(0)"` 47 | NotEqualInt64 *int64 `valid:"NotEqual=(0)"` 48 | NotEqualUint *uint `valid:"NotEqual=(0)"` 49 | NotEqualUint8 *uint8 `valid:"NotEqual=(0)"` 50 | NotEqualUint16 *uint16 `valid:"NotEqual=(0)"` 51 | NotEqualUint32 *uint32 `valid:"NotEqual=(0)"` 52 | NotEqualUint64 *uint64 `valid:"NotEqual=(0)"` 53 | NotEqualUintptr *uintptr `valid:"NotEqual=(0)"` 54 | NotEqualFloat32 *float32 `valid:"NotEqual=(0)"` 55 | NotEqualFloat64 *float64 `valid:"NotEqual=(0)"` 56 | NotEqualComplex64 *complex64 `valid:"NotEqual=(0)"` 57 | NotEqualComplex128 *complex128 `valid:"NotEqual=(0)"` 58 | NotEqualString string `valid:"NotEqual=(0)"` 59 | } 60 | 61 | // SetTestStruct is used in the Set unit tests 62 | type SetTestStruct struct { 63 | SetString string `valid:"Set=(cat)(dog)(mouse)"` 64 | SetStringPtr *string `valid:"Set=(cat)(dog)(mouse)"` 65 | SetInt int `valid:"Set=(1)(3)(7)"` 66 | SetIntPtr *int `valid:"Set=(1)(3)(7)"` 67 | } 68 | -------------------------------------------------------------------------------- /gkgen/minlength_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestMinLengthGenerator(t *testing.T) { 12 | type minLengthStruct struct { 13 | Bool bool 14 | BoolPtr *bool 15 | String string 16 | StringPtr *string 17 | } 18 | 19 | t.Run("RequireParam", func(t *testing.T) { 20 | t.Parallel() 21 | assert := require.New(t) 22 | validator := NewMinLengthValidator() 23 | s := minLengthStruct{String: "foo"} 24 | st := reflect.TypeOf(s) 25 | field, found := st.FieldByName("String") 26 | assert.True(found) 27 | 28 | _, err := validator.Generate(st, field, []string{}) 29 | assert.Error(err) 30 | }) 31 | 32 | t.Run("RequireParamIsANumber", func(t *testing.T) { 33 | t.Parallel() 34 | assert := require.New(t) 35 | validator := NewMinLengthValidator() 36 | s := minLengthStruct{String: "foo"} 37 | st := reflect.TypeOf(s) 38 | field, found := st.FieldByName("String") 39 | assert.True(found) 40 | 41 | _, err := validator.Generate(st, field, []string{"wat"}) 42 | assert.Error(err) 43 | }) 44 | 45 | t.Run("RejectUnsupportedType", func(t *testing.T) { 46 | t.Parallel() 47 | assert := require.New(t) 48 | validator := NewMinLengthValidator() 49 | s := minLengthStruct{Bool: true} 50 | st := reflect.TypeOf(s) 51 | field, found := st.FieldByName("Bool") 52 | assert.True(found) 53 | 54 | _, err := validator.Generate(st, field, []string{"3"}) 55 | assert.Error(err) 56 | }) 57 | 58 | t.Run("RejectUnsupportedTypePointer", func(t *testing.T) { 59 | t.Parallel() 60 | assert := require.New(t) 61 | validator := NewMinLengthValidator() 62 | var foo = true 63 | s := minLengthStruct{BoolPtr: &foo} 64 | st := reflect.TypeOf(s) 65 | field, found := st.FieldByName("BoolPtr") 66 | assert.True(found) 67 | 68 | _, err := validator.Generate(st, field, []string{"3"}) 69 | assert.Error(err) 70 | }) 71 | 72 | t.Run("String", func(t *testing.T) { 73 | t.Parallel() 74 | assert := require.New(t) 75 | validator := NewMinLengthValidator() 76 | s := minLengthStruct{String: "foo"} 77 | st := reflect.TypeOf(s) 78 | field, found := st.FieldByName("String") 79 | assert.True(found) 80 | 81 | code, err := validator.Generate(st, field, []string{"3"}) 82 | assert.NoError(err) 83 | 84 | assert.Equal("if err := gokay.MinLengthString(3, &s.String); err != nil {errorsString = append(errorsString, err)}", collapseToOneLine(code)) 85 | }) 86 | 87 | t.Run("StringPtr", func(t *testing.T) { 88 | t.Parallel() 89 | assert := require.New(t) 90 | validator := NewMinLengthValidator() 91 | var foo = "foo" 92 | s := minLengthStruct{StringPtr: &foo} 93 | st := reflect.TypeOf(s) 94 | field, found := st.FieldByName("StringPtr") 95 | assert.True(found) 96 | 97 | code, err := validator.Generate(st, field, []string{"3"}) 98 | assert.NoError(err) 99 | 100 | assert.Equal("if err := gokay.MinLengthString(3, s.StringPtr); err != nil {errorsStringPtr = append(errorsStringPtr, err)}", collapseToOneLine(code)) 101 | }) 102 | } 103 | 104 | func collapseToOneLine(str string) string { 105 | s := strings.Replace(strings.TrimSpace(str), "\t", "", -1) 106 | return strings.Replace(strings.TrimSpace(s), "\n", "", -1) 107 | } 108 | -------------------------------------------------------------------------------- /gkgen/set.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // SetValidator generates code that will verify a fields does Set one of an allowed set of values 12 | // The SetValidator will look at the field or the dereferenced value of the field 13 | // nil values for a field are not considered invalid 14 | type SetValidator struct { 15 | name string 16 | } 17 | 18 | // NewSetValidator holds the SetValidator state 19 | func NewSetValidator() *SetValidator { 20 | return &SetValidator{name: "Set"} 21 | } 22 | 23 | // Generate generates validation code 24 | func (s *SetValidator) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) { 25 | if len(params) == 0 { 26 | return "", errors.New("Set validation requires at least 1 parameter") 27 | } 28 | 29 | field := fieldStruct.Type 30 | 31 | switch field.Kind() { 32 | case reflect.String: 33 | conditions := make([]string, len(params)) 34 | for i, param := range params { 35 | conditions[i] = fmt.Sprintf(`s.%[1]s == "%[2]s"`, fieldStruct.Name, param) 36 | } 37 | condition := strings.Join(conditions, " || ") 38 | return fmt.Sprintf(` 39 | if s.%[1]s != "" && !(%[2]s) { 40 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s must equal %[3]s")) 41 | }`, fieldStruct.Name, condition, strings.Join(params, " or ")), nil 42 | case reflect.Int, reflect.Int32, reflect.Int64: 43 | conditions := make([]string, len(params)) 44 | for i, param := range params { 45 | intParam, err := strconv.Atoi(param) 46 | if err != nil { 47 | return "", fmt.Errorf("Expected a set of Ints, but got param %q", param) 48 | } 49 | 50 | conditions[i] = fmt.Sprintf(`s.%[1]s == %[2]d`, fieldStruct.Name, intParam) 51 | } 52 | condition := strings.Join(conditions, " || ") 53 | return fmt.Sprintf(` 54 | if s.%[1]s != 0 && !(%[2]s) { 55 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s must equal %[3]s")) 56 | }`, fieldStruct.Name, condition, strings.Join(params, " or ")), nil 57 | case reflect.Ptr: 58 | field = field.Elem() 59 | switch field.Kind() { 60 | case reflect.String: 61 | conditions := make([]string, len(params)) 62 | for i, param := range params { 63 | conditions[i] = fmt.Sprintf(`*s.%[1]s == "%[2]s"`, fieldStruct.Name, param) 64 | } 65 | condition := strings.Join(conditions, " || ") 66 | return fmt.Sprintf(` 67 | if s.%[1]s != nil && !(%[2]s) { 68 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s must equal %[3]s")) 69 | }`, fieldStruct.Name, condition, strings.Join(params, " or ")), nil 70 | case reflect.Int, reflect.Int32, reflect.Int64: 71 | conditions := make([]string, len(params)) 72 | for i, param := range params { 73 | intParam, err := strconv.Atoi(param) 74 | if err != nil { 75 | return "", fmt.Errorf("Expected a set of Ints, but got param %q", param) 76 | } 77 | 78 | conditions[i] = fmt.Sprintf(`*s.%[1]s == %[2]d`, fieldStruct.Name, intParam) 79 | } 80 | condition := strings.Join(conditions, " || ") 81 | return fmt.Sprintf(` 82 | if s.%[1]s != nil && !(%[2]s) { 83 | errors%[1]s = append(errors%[1]s, errors.New("%[1]s must equal %[3]s")) 84 | }`, fieldStruct.Name, condition, strings.Join(params, " or ")), nil 85 | default: 86 | return "", fmt.Errorf("Set does not work on type '%s'", field.Kind()) 87 | } 88 | default: 89 | return "", fmt.Errorf("Set does not work on type '%s'", field.Kind()) 90 | } 91 | } 92 | 93 | // Name provides access to the name field 94 | func (s *SetValidator) Name() string { 95 | return s.name 96 | } 97 | -------------------------------------------------------------------------------- /gkgen/tag_parser.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | // errValidTagSyntax is returned when an error is found in 11 | // the tag syntax 12 | var errValidTagSyntax = errors.New("error in 'valid' tag syntax") 13 | 14 | // ValidationCommand holds the ValidationCommand state 15 | type ValidationCommand struct { 16 | name string 17 | Params []string 18 | } 19 | 20 | // NewValidationCommand creates a new value of type ValidationCommand 21 | func NewValidationCommand(n string) ValidationCommand { 22 | return ValidationCommand{name: n} 23 | } 24 | 25 | // Name provides access to the name field 26 | func (s *ValidationCommand) Name() string { 27 | return s.name 28 | } 29 | 30 | // ParseTag parses given struct tags 31 | func ParseTag(interf interface{}, tag string) ([]ValidationCommand, error) { 32 | inr := strings.NewReader(tag) 33 | out := new(bytes.Buffer) 34 | vcs := make([]ValidationCommand, 0, 8) 35 | 36 | ch, _, readerErr := inr.ReadRune() 37 | for readerErr == nil { 38 | var name string 39 | switch ch { 40 | case '=': 41 | if len(out.Bytes()) == 0 { 42 | return nil, errValidTagSyntax 43 | } 44 | name = out.String() 45 | out.Reset() 46 | params, err := parseParams(interf, inr) 47 | if err != nil { 48 | return nil, err 49 | } 50 | vc := ValidationCommand{ 51 | name: name, 52 | Params: params, 53 | } 54 | vcs = append(vcs, vc) 55 | case ',': 56 | if len(out.Bytes()) == 0 { 57 | return nil, errValidTagSyntax 58 | } 59 | name = out.String() 60 | vc := ValidationCommand{ 61 | name: name, 62 | } 63 | vcs = append(vcs, vc) 64 | out.Reset() 65 | default: 66 | out.WriteRune(ch) 67 | } 68 | 69 | ch, _, readerErr = inr.ReadRune() 70 | if readerErr == io.EOF && len(out.Bytes()) > 0 { 71 | name = out.String() 72 | vc := ValidationCommand{ 73 | name: name, 74 | } 75 | vcs = append(vcs, vc) 76 | } 77 | } 78 | 79 | if readerErr == io.EOF { 80 | return vcs, nil 81 | } 82 | return nil, readerErr 83 | } 84 | 85 | // parseParams parses the given parameters 86 | func parseParams(interf interface{}, inr *strings.Reader) ([]string, error) { 87 | ch, _, readerErr := inr.ReadRune() 88 | params := make([]string, 0, 8) 89 | if readerErr != nil { 90 | return nil, readerErr 91 | } 92 | 93 | for readerErr == nil { 94 | switch ch { 95 | case '(': 96 | param, err := parseParam(inr) 97 | if err != nil { 98 | return nil, err 99 | } 100 | params = append(params, param) 101 | ch, _, readerErr = inr.ReadRune() 102 | 103 | case ',': 104 | if len(params) == 0 { 105 | return nil, errValidTagSyntax 106 | } 107 | return params, nil 108 | default: 109 | if len(params) == 0 { 110 | return nil, errValidTagSyntax 111 | } 112 | ch, _, readerErr = inr.ReadRune() 113 | } 114 | } 115 | return params, nil 116 | } 117 | 118 | // parseParam parses the given reader 119 | func parseParam(inr *strings.Reader) (string, error) { 120 | ch, _, readerErr := inr.ReadRune() 121 | out := new(bytes.Buffer) 122 | 123 | if readerErr != nil { 124 | return "", readerErr 125 | } 126 | 127 | for readerErr == nil { 128 | switch ch { 129 | case ')': 130 | return out.String(), nil 131 | case '\\': 132 | ch, _, readerErr = inr.ReadRune() 133 | if readerErr != nil { 134 | return "", readerErr 135 | } 136 | out.WriteRune(ch) 137 | default: 138 | out.WriteRune(ch) 139 | } 140 | ch, _, readerErr = inr.ReadRune() 141 | } 142 | 143 | // Reader should reach ')' before EOF 144 | if readerErr == io.EOF { 145 | return "", errValidTagSyntax 146 | } 147 | return "", readerErr 148 | } 149 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c h1:MUyE44mTvnI5A0xrxIxaMqoWFzPfQvtE2IWUollMDMs= 4 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ= 8 | github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 9 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 10 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 11 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 12 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= 13 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 14 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 15 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 16 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 17 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 18 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 19 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 20 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= 24 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 26 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 27 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 28 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 29 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 30 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 31 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 32 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 33 | golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= 34 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 35 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 37 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 38 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 39 | -------------------------------------------------------------------------------- /gkgen/tag_parser_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | type EmptyStruct struct{} 11 | 12 | // TestParseTagSingleNoParamValidation tests single no-param validation 13 | func TestParseTagSingleNoParamValidation(t *testing.T) { 14 | s := new(EmptyStruct) 15 | tag := "bar" 16 | vcs, err := ParseTag(s, tag) 17 | require.NoError(t, err) 18 | 19 | expectedCommand := NewValidationCommand("bar") 20 | require.Equal(t, expectedCommand, vcs[0]) 21 | require.Equal(t, 1, len(vcs)) 22 | } 23 | 24 | // Test single no-param validation 25 | func TestExampleValidStruct(t *testing.T) { 26 | key := "abc123" 27 | s := ExampleStruct{ 28 | HexStringPtr: &key, 29 | } 30 | 31 | _, err := ParseTag(s, "valid") 32 | require.NoError(t, err) 33 | } 34 | 35 | func TestParseTagMultipleNoParamValidations(t *testing.T) { 36 | s := new(EmptyStruct) 37 | tag := "bar,biz,buz" 38 | vcs, err := ParseTag(s, tag) 39 | 40 | require.NoError(t, err) 41 | barCommand := NewValidationCommand("bar") 42 | bizCommand := NewValidationCommand("biz") 43 | buzCommand := NewValidationCommand("buz") 44 | 45 | expectedVcs := []ValidationCommand{barCommand, bizCommand, buzCommand} 46 | 47 | require.Equal(t, expectedVcs, vcs) 48 | } 49 | 50 | // Test leading comma 51 | func TestParseTagLeadingComma(t *testing.T) { 52 | s := new(EmptyStruct) 53 | tag := ",bar" 54 | _, err := ParseTag(s, tag) 55 | require.Error(t, err) 56 | } 57 | 58 | // Test trailing commas 59 | func TestParseTagTrailingCommas(t *testing.T) { 60 | s := new(EmptyStruct) 61 | tag := "bar," 62 | vcs, err := ParseTag(s, tag) 63 | require.NoError(t, err) 64 | expectedVcs := []ValidationCommand{NewValidationCommand("bar")} 65 | require.Equal(t, expectedVcs, vcs) 66 | 67 | tag = "two_commas,," 68 | _, err = ParseTag(s, tag) 69 | require.Error(t, err) 70 | } 71 | 72 | // Test validation with multiple parameters 73 | func TestParseTagWithConstParam(t *testing.T) { 74 | s := new(EmptyStruct) 75 | tag := "bar=(hello world,\\)How are you?)" 76 | vcs, err := ParseTag(s, tag) 77 | require.NoError(t, err) 78 | require.Equal(t, 1, len(vcs)) 79 | require.Equal(t, "bar", vcs[0].Name()) 80 | require.Equal(t, 1, len(vcs[0].Params)) 81 | require.Equal(t, "hello world,)How are you?", vcs[0].Params[0]) 82 | } 83 | 84 | func TestParseTagWithConstParamSyntaxError(t *testing.T) { 85 | s := new(EmptyStruct) 86 | tag := "bar=(?foo\\)[biz]" 87 | _, err := ParseTag(s, tag) 88 | require.Error(t, err) 89 | } 90 | 91 | func TestParseTagMissingParamSyntaxError(t *testing.T) { 92 | s := new(EmptyStruct) 93 | tag := "bar=,foo" 94 | _, err := ParseTag(s, tag) 95 | require.Error(t, err) 96 | 97 | tag = "bar=" 98 | _, err = ParseTag(s, tag) 99 | require.Equal(t, io.EOF, err) 100 | } 101 | 102 | func TestParseTagLeadingEquals(t *testing.T) { 103 | s := new(EmptyStruct) 104 | tag := "=" 105 | _, err := ParseTag(s, tag) 106 | require.Error(t, err) 107 | } 108 | 109 | func TestParseTagWithMultipleParams(t *testing.T) { 110 | s := new(EmptyStruct) 111 | tag := "bar=(bar0)(bar1)" 112 | vcs, err := ParseTag(s, tag) 113 | require.NoError(t, err) 114 | require.Equal(t, 1, len(vcs)) 115 | require.Equal(t, "bar", vcs[0].Name()) 116 | require.Equal(t, 2, len(vcs[0].Params)) 117 | require.Equal(t, "bar0", vcs[0].Params[0]) 118 | require.Equal(t, "bar1", vcs[0].Params[1]) 119 | } 120 | 121 | func TestParseTag2ValidationsWith1ParamEach(t *testing.T) { 122 | s := new(EmptyStruct) 123 | tag := "bar=(bar0)(bar1),foo=(foo0)" 124 | vcs, err := ParseTag(s, tag) 125 | require.NoError(t, err) 126 | require.Equal(t, 2, len(vcs)) 127 | 128 | require.Equal(t, "bar", vcs[0].Name()) 129 | require.Equal(t, 2, len(vcs[0].Params)) 130 | require.Equal(t, "bar0", vcs[0].Params[0]) 131 | require.Equal(t, "bar1", vcs[0].Params[1]) 132 | 133 | require.Equal(t, "foo", vcs[1].Name()) 134 | require.Equal(t, 1, len(vcs[1].Params)) 135 | require.Equal(t, "foo0", vcs[1].Params[0]) 136 | } 137 | -------------------------------------------------------------------------------- /gokay.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "sort" 15 | "strings" 16 | 17 | "github.com/pborman/uuid" 18 | "golang.org/x/tools/imports" 19 | ) 20 | 21 | // usage is a string used to provide a user with the application usage 22 | const usage = `usage: gokay [generator-package generator-contructor] 23 | generator-package custom package 24 | generator-contructor custom generator 25 | 26 | examples: 27 | gokay file.go 28 | gokay file.go gkcustom NewCustomGKGenerator 29 | ` 30 | 31 | func main() { 32 | args := os.Args[1:] 33 | if len(args) < 1 { 34 | fmt.Fprintf(os.Stderr, usage) 35 | return 36 | } 37 | 38 | genPackage := "gkgen" 39 | genConstructor := "NewValidator" 40 | if len(args) >= 3 { 41 | genPackage = args[1] 42 | genConstructor = args[2] 43 | } 44 | 45 | fileName := args[0] 46 | 47 | fileName, _ = filepath.Abs(fileName) 48 | fileDir := filepath.Dir(fileName) 49 | 50 | tempName := uuid.NewRandom().String() 51 | 52 | tempDir := fmt.Sprintf("%s/tmp/%s", fileDir, tempName) 53 | tempFile := fmt.Sprintf("%s/%s.go", tempDir, tempName) 54 | 55 | // os.Mkdir errors when the dir already exists, which is fine. 56 | // Real errors will still be detected when we try use the actual tmpfile. 57 | os.Mkdir(fileDir+"/tmp", os.ModePerm) 58 | os.Mkdir(tempDir, os.ModePerm) 59 | 60 | outFilePath := fmt.Sprintf("%s_validators.go", strings.TrimSuffix(fileName, filepath.Ext(fileName))) 61 | tempOut, err := os.Create(tempFile) 62 | if err != nil { 63 | log.Fatalf("Error while opening %v: %v\n", tempFile, err) 64 | } 65 | defer tempOut.Close() 66 | 67 | outWriter := io.MultiWriter(tempOut) 68 | 69 | fset := token.NewFileSet() // positions are relative to fset 70 | 71 | // Parse the file given in arguments 72 | f, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments) 73 | if err != nil { 74 | log.Fatalf("Error while parsing %v: %v\n", fileName, err) 75 | } 76 | if _, err = ioutil.ReadFile(fileName); err != nil { 77 | log.Fatalf("IO Error while reading %v: %v\n", fileName, err) 78 | } 79 | 80 | fmt.Fprintf(outWriter, `package main 81 | func main() { 82 | out, err := os.Create("%s") 83 | defer out.Close() 84 | fmt.Fprint(out, "// Code in this file generated by gokay: github.com/zencoder/gokay\n") 85 | fmt.Fprint(out, "package %s\n") 86 | if err != nil { 87 | panic(err.Error()) 88 | } 89 | v := %s.%s() 90 | `, outFilePath, f.Name.String(), genPackage, genConstructor) 91 | 92 | sortedObjectKeys := make([]string, len(f.Scope.Objects)) 93 | for k := range f.Scope.Objects { 94 | sortedObjectKeys = append(sortedObjectKeys, k) 95 | } 96 | sort.Strings(sortedObjectKeys) 97 | 98 | for _, k := range sortedObjectKeys { 99 | if k == "" { 100 | continue 101 | } 102 | d := f.Scope.Objects[k] 103 | ts, ok := d.Decl.(*ast.TypeSpec) 104 | if !ok { 105 | continue 106 | } 107 | switch ts.Type.(type) { 108 | case *ast.StructType: 109 | fmt.Fprintf(outWriter, "if err := v.Generate(out, %s.%s{}); err != nil {\npanic(err.Error())\n}\n", f.Name.String(), ts.Name.String()) 110 | } 111 | } 112 | 113 | fmt.Fprintf(outWriter, "}\n") 114 | 115 | if err := formatFile(tempOut.Name(), nil); err != nil { 116 | log.Fatalf("Failed formatting intermediate executable code: %s", err.Error()) 117 | } 118 | 119 | generateCmd := exec.Command("go", "run", tempFile) 120 | generateCmd.Stderr = os.Stderr 121 | generateCmd.Stdout = os.Stdout 122 | if err := generateCmd.Run(); err != nil { 123 | log.Fatalf("Failed executing intermediate executable to generate gokay validators failed: %v\n", err.Error()) 124 | } 125 | 126 | if err := formatFile(outFilePath, nil); err != nil { 127 | log.Fatalf("Failed running imports on gokay validators: %v\n", err.Error()) 128 | } 129 | 130 | // remove the temp directory 131 | if err := os.RemoveAll(tempDir); err != nil { 132 | log.Printf("Warning: Deleting intermediate temp files %v failed (although gokay run appears to have succeeded): %v\n", tempDir, err.Error()) 133 | } 134 | 135 | log.Println("gokay finished file:", args[0]) 136 | } 137 | 138 | func formatFile(filename string, src []byte) error { 139 | mode := os.FileMode(0644) 140 | formatted, err := imports.Process(filename, src, nil) 141 | if err != nil { 142 | return fmt.Errorf("failed formatting: %w", err) 143 | } 144 | 145 | err = ioutil.WriteFile(filename, formatted, mode) 146 | if err != nil { 147 | return fmt.Errorf("failed writing file: %w", err) 148 | } 149 | 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /gkgen/set_test.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSet(t *testing.T) { 13 | nv := NewSetValidator() 14 | e := SetTestStruct{} 15 | et := reflect.TypeOf(e) 16 | 17 | field, ok := et.FieldByName("SetString") 18 | require.True(t, ok) 19 | code, err := nv.Generate(et, field, []string{"cat"}) 20 | require.NoError(t, err) 21 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 22 | require.Equal( 23 | t, 24 | fmt.Sprintf("if s.%[1]s != \"\" && !(s.%[1]s == \"cat\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat\"))\n}", field.Name), 25 | code, 26 | ) 27 | 28 | code, err = nv.Generate(et, field, []string{"cat", "dog", "mouse"}) 29 | require.NoError(t, err) 30 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 31 | require.Equal( 32 | t, 33 | fmt.Sprintf("if s.%[1]s != \"\" && !(s.%[1]s == \"cat\" || s.%[1]s == \"dog\" || s.%[1]s == \"mouse\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat or dog or mouse\"))\n}", field.Name), 34 | code, 35 | ) 36 | } 37 | 38 | func TestSetInts(t *testing.T) { 39 | nv := NewSetValidator() 40 | e := SetTestStruct{} 41 | et := reflect.TypeOf(e) 42 | 43 | field, ok := et.FieldByName("SetInt") 44 | require.True(t, ok) 45 | code, err := nv.Generate(et, field, []string{"3"}) 46 | require.NoError(t, err) 47 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 48 | require.Equal( 49 | t, 50 | fmt.Sprintf("if s.%[1]s != 0 && !(s.%[1]s == 3) {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal 3\"))\n}", field.Name), 51 | code, 52 | ) 53 | 54 | code, err = nv.Generate(et, field, []string{"1", "3", "7"}) 55 | require.NoError(t, err) 56 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 57 | require.Equal( 58 | t, 59 | fmt.Sprintf("if s.%[1]s != 0 && !(s.%[1]s == 1 || s.%[1]s == 3 || s.%[1]s == 7) {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal 1 or 3 or 7\"))\n}", field.Name), 60 | code, 61 | ) 62 | } 63 | 64 | func TestSetPointer(t *testing.T) { 65 | nv := NewSetValidator() 66 | e := SetTestStruct{} 67 | et := reflect.TypeOf(e) 68 | 69 | field, ok := et.FieldByName("SetStringPtr") 70 | require.True(t, ok) 71 | code, err := nv.Generate(et, field, []string{"cat"}) 72 | require.NoError(t, err) 73 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 74 | require.Equal( 75 | t, 76 | fmt.Sprintf("if s.%[1]s != nil && !(*s.%[1]s == \"cat\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat\"))\n}", field.Name), 77 | code, 78 | ) 79 | 80 | code, err = nv.Generate(et, field, []string{"cat", "dog", "mouse"}) 81 | require.NoError(t, err) 82 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 83 | require.Equal( 84 | t, 85 | fmt.Sprintf("if s.%[1]s != nil && !(*s.%[1]s == \"cat\" || *s.%[1]s == \"dog\" || *s.%[1]s == \"mouse\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat or dog or mouse\"))\n}", field.Name), 86 | code, 87 | ) 88 | } 89 | 90 | func TestSetIntPointer(t *testing.T) { 91 | nv := NewSetValidator() 92 | e := SetTestStruct{} 93 | et := reflect.TypeOf(e) 94 | 95 | field, ok := et.FieldByName("SetIntPtr") 96 | require.True(t, ok) 97 | code, err := nv.Generate(et, field, []string{"1"}) 98 | require.NoError(t, err) 99 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 100 | require.Equal( 101 | t, 102 | fmt.Sprintf("if s.%[1]s != nil && !(*s.%[1]s == 1) {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal 1\"))\n}", field.Name), 103 | code, 104 | ) 105 | 106 | code, err = nv.Generate(et, field, []string{"1", "3", "7"}) 107 | require.NoError(t, err) 108 | code = strings.Replace(strings.TrimSpace(code), "\t", "", -1) 109 | require.Equal( 110 | t, 111 | fmt.Sprintf("if s.%[1]s != nil && !(*s.%[1]s == 1 || *s.%[1]s == 3 || *s.%[1]s == 7) {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal 1 or 3 or 7\"))\n}", field.Name), 112 | code, 113 | ) 114 | } 115 | 116 | func TestSetInvalidTypes(t *testing.T) { 117 | nv := NewSetValidator() 118 | e := NotNilTestStruct{} 119 | et := reflect.TypeOf(e) 120 | 121 | field, _ := et.FieldByName("NotNilMap") 122 | _, err := nv.Generate(et, field, []string{"0"}) 123 | require.Error(t, err) 124 | require.Equal( 125 | t, 126 | "Set does not work on type 'map'", 127 | err.Error(), 128 | ) 129 | 130 | field, _ = et.FieldByName("NotNilSlice") 131 | _, err = nv.Generate(et, field, []string{"test"}) 132 | require.Error(t, err) 133 | require.Equal( 134 | t, 135 | "Set does not work on type 'slice'", 136 | err.Error(), 137 | ) 138 | } 139 | 140 | func TestSetInvalidParameters(t *testing.T) { 141 | nv := NewSetValidator() 142 | e := SetTestStruct{} 143 | et := reflect.TypeOf(e) 144 | 145 | field, _ := et.FieldByName("SetString") 146 | _, err := nv.Generate(et, field, []string{}) 147 | require.Error(t, err) 148 | require.Equal( 149 | t, 150 | "Set validation requires at least 1 parameter", 151 | err.Error(), 152 | ) 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gokay [![CircleCI](https://circleci.com/gh/zencoder/gokay.svg?style=svg&circle-token=90f42bc5cbb6fe74834f7649d67298130431d88d)](https://circleci.com/gh/zencoder/gokay) [![Coverage Status](https://coveralls.io/repos/github/zencoder/gokay/badge.svg?t=A2kWWv)](https://coveralls.io/github/zencoder/gokay) 2 | Codegenerated struct validation tool for go. 3 | 4 | ## How it works 5 | gokay parses a struct and generates a `Validate` method so that the struct implements the `Validateable` interface. It does so by parsing the `valid` tags in that struct's fields. 6 | 7 | gokay generated Validate methods will return an ErrorMap that implements the Error interface. The ErrorMap is a `map[string]error` containing failed validations for each invalid field in a struct. 8 | 9 | ### Code Documentation 10 | `godoc -http=:6060` 11 | 12 | ## Installing gokay 13 | 14 | This project uses [Glide](https://github.com/Masterminds/glide) to manage it's dependencies. Please refer to the glide docs to see how to install and use glide. 15 | 16 | This project is tested on go 1.9.1 and glide 0.12.3 17 | 18 | ```sh 19 | mkdir -p $GOPATH/github.com/zencoder 20 | cd $GOPATH/src/github.com/zencoder 21 | git clone https://github.com/zencoder/gokay 22 | cd gokay 23 | glide install 24 | go install ./... 25 | ``` 26 | 27 | ## Running gokay 28 | ### Usage 29 | ``` sh 30 | gokay [custom-generator-package custom-generator-contructor] 31 | ``` 32 | 33 | ### Examples 34 | Generate validation methods for types in a single file 35 | ```sh 36 | gokay file.go 37 | ``` 38 | 39 | Generate validation methods with a custom package and constructor 40 | ```sh 41 | gokay file.go gkcustom NewCustomGKGenerator 42 | ``` 43 | 44 | gokay relies on the goimports tool to resolve import path of custom generator. 45 | 46 | ## Using gokay 47 | - Add validations to `valid` tag in struct def: 48 | 49 | ```go 50 | type ExampleStruct struct { 51 | HexStringPtr *string `valid:"Length=(16),NotNil,Hex"` 52 | HexString string `valid:"Length=(12),Hex"` 53 | CanBeNilWithConstraints *string `valid:"Length=(12)"` 54 | } 55 | ``` 56 | 57 | - Run gokay command 58 | 59 | ### Tag syntax 60 | Validation tags are comma separated, with Validation parameters delimited by open and closed parentheses. 61 | 62 | `valid:"ValidationName1,ValidationName2=(vn2paramA)(vn2paramB)"` 63 | 64 | In the above example, the `Hex` and `NotNil` Validations are parameterless, whereas length requires 1 parameter. 65 | 66 | ### Built-in Validations 67 | Name | Params | Allowed Field Types | Description 68 | ---- | ------------------- | ------ | ----------- 69 | Hex | N/A | `(*)string` | Checks if a string field is a valid hexadecimal format number (0x prefix optional) 70 | NotNil | N/A | pointers | Checks and fails if a pointer is nil 71 | Length | 1 | `(*)string` | Checks if a string's length matches the tag's parameter 72 | UUID | N/A | `(*)string` | Checks and fails if a string is not a valid UUID 73 | NotEqual | 1 | `(*)string` or `(*)number` | Checks and fails if field is equal to specific value 74 | Set | 1+ | `(*)string` | Checks and fails if field is not in a specific set of values 75 | 76 | ### Implicitly generated validations 77 | These sections of code will be added to the generated `Validate()` method regardless of a field's `valid` tag's contents. 78 | If a struct does not have any `valid` tags and no fields with implicit validation rules, then no Validate method will be generated. 79 | 80 | - Struct fields: generated code will call static Validate method on any field that implements Validateable interface 81 | - Slice/Map fields: Static Validate will be called on each element of a slice or map of structs or struct pointers (one level of indirection). Only supports maps with string indices. 82 | 83 | 84 | *Note* on built-in and implicit validations: With the obvious exception of NotNil, nil pointers fields are considered to be valid in order to allow support for optional fields. 85 | 86 | ### Writing your own Validations 87 | gokay was built to allow developers to write and attach their own Validations to the Validate generator. 88 | 89 | 1. Write a function that validates a field. E.g: 90 | 91 | ```go 92 | // LengthString checks if the value of a string pointer has a length of exactly 'expected' 93 | func LengthString(expected int64, str *string) error { 94 | if str == nil { 95 | return nil // Covers the case where a value can be nil OR has a length constraint 96 | } 97 | 98 | if expected != int64(len(*str)) { 99 | return fmt.Errorf("Length was '%d', needs to be '%d'", len(*str), expected) 100 | } 101 | return nil 102 | } 103 | ``` 104 | 105 | 1. Write a struct that implements the `Generater` interface 106 | 107 | ```go 108 | // Generater defines the behavior of types that generate validation code 109 | type Generater interface { 110 | Generate(reflect.Type, reflect.StructField, []string) (string, error) 111 | Name() string 112 | } 113 | ``` 114 | - Name returns the string that will be used as a validation tag 115 | 116 | 1. GenerateValidationCode should generate a block will leverage the function defined in step 1. This block will be inserted into the generated `Validate` function. GenerateValidationCode output example: 117 | 118 | ```go 119 | // ValidationName 120 | if err := somepackage.ValidationFunction(someparamA, someparamB, s.Field); err != nil { 121 | errorsField = append(errorsField, err) 122 | } 123 | ``` 124 | 125 | 1. Write a function that constructs a `ValidateGenerator`. This function should live in a different package from your data model. Example: 126 | 127 | ```go 128 | package gkcustom 129 | 130 | // To run: `gokay gkexample NewCustomValidator` 131 | func NewCustomGKGenerator() *gkgen.ValidateGenerator { 132 | v := gkgen.NewValidateGenerator() 133 | v.AddValidation(NewCustomValidator()) 134 | return v 135 | } 136 | ``` 137 | 1. Write tests for your struct's constraints 138 | 1. Add `valid` tags to your struct fields 139 | 1. Run gokay: `gokay file.go gkcustom NewCustomGKGenerator` 140 | 141 | [More Examples](internal/gkexample/) 142 | 143 | ## Development 144 | 145 | ### Dependencies 146 | 147 | Tested on go 1.7.1. 148 | 149 | ### Build and run unit tests 150 | 151 | make test 152 | 153 | ### CI 154 | 155 | [This library builds on Circle CI, here.](https://circleci.com/gh/zencoder/gokay/) 156 | 157 | ## License 158 | 159 | [Apache License Version 2.0](LICENSE) 160 | 161 | -------------------------------------------------------------------------------- /internal/gkexample/example_test.go: -------------------------------------------------------------------------------- 1 | package gkexample 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/davecgh/go-spew/spew" 9 | "github.com/stretchr/testify/require" 10 | "github.com/zencoder/gokay/gokay" 11 | ) 12 | 13 | func TestNotAValidateable(t *testing.T) { 14 | underTest := NoImplicitValidate{} 15 | 16 | tut := reflect.TypeOf(underTest) 17 | validateable := reflect.TypeOf((*gokay.Validateable)(nil)) 18 | 19 | require.False(t, tut.Implements(validateable.Elem())) 20 | } 21 | 22 | func TestHasValidateImplicit(t *testing.T) { 23 | underTest := HasValidateImplicit{} 24 | 25 | tut := reflect.TypeOf(underTest) 26 | validateable := reflect.TypeOf((*gokay.Validateable)(nil)) 27 | 28 | require.True(t, tut.Implements(validateable.Elem())) 29 | } 30 | 31 | func TestHasValidateImplicit_Validate(t *testing.T) { 32 | underTest := HasValidateImplicit{ 33 | InvalidStruct: &TestValidate{}, 34 | } 35 | 36 | err := underTest.Validate() 37 | 38 | em, ok := err.(gokay.ErrorMap) 39 | require.True(t, ok) 40 | require.Equal(t, 1, len(em)) 41 | _, ok = em["ValidStruct"] 42 | require.False(t, ok) 43 | 44 | ea, ok := em["InvalidStruct"].(gokay.ErrorSlice) 45 | require.True(t, ok) 46 | require.Equal(t, gokay.ErrorSlice{ 47 | gokay.ErrorMap{ 48 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 49 | }, 50 | }, ea) 51 | } 52 | 53 | func TestHasValidateImplicit_NilInvalidStruct(t *testing.T) { 54 | underTest := HasValidateImplicit{ 55 | InvalidStruct: nil, 56 | } 57 | 58 | err := underTest.Validate() 59 | require.NoError(t, err) 60 | } 61 | 62 | func TestValidateImplicit_MapOfStruct(t *testing.T) { 63 | underTest := HasValidateImplicit{ 64 | InvalidStruct: nil, 65 | MapOfStruct: map[string]TestValidate{ 66 | "a": {}, 67 | "b": {}, 68 | }, 69 | } 70 | 71 | err := underTest.Validate() 72 | expected := gokay.ErrorMap{ 73 | "MapOfStruct": gokay.ErrorSlice{ 74 | gokay.ErrorMap{ 75 | "a": gokay.ErrorMap{ 76 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 77 | }, 78 | "b": gokay.ErrorMap{ 79 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 80 | }, 81 | }, 82 | }, 83 | } 84 | require.Equal(t, expected, err) 85 | } 86 | 87 | func TestValidateImplicit_MapOfStructPtrs(t *testing.T) { 88 | underTest := HasValidateImplicit{ 89 | InvalidStruct: nil, 90 | MapOfStructPtrs: map[string]*TestValidate{ 91 | "a": {}, 92 | "b": (*TestValidate)(nil), 93 | "c": {}, 94 | "d": {true}, 95 | }, 96 | } 97 | 98 | err := underTest.Validate() 99 | expected := gokay.ErrorMap{ 100 | "MapOfStructPtrs": gokay.ErrorSlice{ 101 | gokay.ErrorMap{ 102 | "a": gokay.ErrorMap{ 103 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 104 | }, 105 | "c": gokay.ErrorMap{ 106 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 107 | }, 108 | }, 109 | }, 110 | } 111 | require.Equal(t, expected, err) 112 | } 113 | 114 | func TestValidateImplicit_MapOfMapsOfStructs(t *testing.T) { 115 | underTest := HasValidateImplicit{ 116 | InvalidStruct: nil, 117 | MapOfMaps: map[string]map[string]*TestValidate{ 118 | "invalid": { 119 | "invalidA": {}, 120 | "invalidB": (*TestValidate)(nil), 121 | "invalidC": {}, 122 | "invalidD": {true}, 123 | }, 124 | "valid": { 125 | "validA": {true}, 126 | "validB": (*TestValidate)(nil), 127 | "validC": {true}, 128 | "validD": {true}, 129 | }, 130 | }, 131 | } 132 | 133 | err := underTest.Validate() 134 | expected := gokay.ErrorMap{ 135 | "MapOfMaps": gokay.ErrorSlice{ 136 | gokay.ErrorMap{ 137 | "invalid": gokay.ErrorMap{ 138 | "invalidA": gokay.ErrorMap{ 139 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 140 | }, 141 | "invalidC": gokay.ErrorMap{ 142 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 143 | }, 144 | }, 145 | }, 146 | }, 147 | } 148 | require.Equal(t, expected, err) 149 | } 150 | 151 | func TestValidateImplicit_MapOfMapsOfSlices(t *testing.T) { 152 | vErr := gokay.ErrorMap{ 153 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 154 | } 155 | 156 | underTest := HasValidateImplicit{ 157 | InvalidStruct: nil, 158 | MapMapsOfSlices: map[string]map[string][]*TestValidate{ 159 | "a": { 160 | "aa": { 161 | {}, 162 | (*TestValidate)(nil), 163 | {}, 164 | {true}, 165 | }, 166 | "ab": { 167 | {true}, 168 | {}, 169 | (*TestValidate)(nil), 170 | {true}, 171 | }, 172 | }, 173 | "b": { 174 | "ba": { 175 | {}, 176 | {}, 177 | {}, 178 | {}, 179 | }, 180 | "bb": { 181 | {true}, 182 | {true}, 183 | {true}, 184 | {true}, 185 | }, 186 | }, 187 | "c": { 188 | "ca": {}, 189 | }, 190 | "d": {}, 191 | }, 192 | } 193 | 194 | err := underTest.Validate() 195 | 196 | expected := gokay.ErrorMap{ 197 | "MapMapsOfSlices": gokay.ErrorSlice{ 198 | gokay.ErrorMap{ 199 | "a": gokay.ErrorMap{ 200 | "aa": gokay.ErrorMap{ 201 | "0": vErr, 202 | "2": vErr, 203 | }, 204 | "ab": gokay.ErrorMap{ 205 | "1": vErr, 206 | }, 207 | }, 208 | "b": gokay.ErrorMap{ 209 | "ba": gokay.ErrorMap{ 210 | "0": vErr, 211 | "1": vErr, 212 | "2": vErr, 213 | "3": vErr, 214 | }, 215 | }, 216 | }, 217 | }, 218 | } 219 | 220 | require.Equal(t, expected, err) 221 | } 222 | 223 | func TestValidateImplicit_MapOfSlicesOfMaps(t *testing.T) { 224 | vErr := gokay.ErrorMap{ 225 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 226 | } 227 | 228 | underTest := HasValidateImplicit{ 229 | InvalidStruct: nil, 230 | MapOfSlicesOfMaps: map[string][]map[string]*TestValidate{ 231 | "a": { 232 | { 233 | "a0a": {}, 234 | "a0b": (*TestValidate)(nil), 235 | }, 236 | { 237 | "a1a": {true}, 238 | "a1b": (*TestValidate)(nil), 239 | "a1c": {}, 240 | }, 241 | {}, 242 | }, 243 | "b": { 244 | { 245 | "b0a": {true}, 246 | "b0b": (*TestValidate)(nil), 247 | "b0c": (*TestValidate)(nil), 248 | }, 249 | {}, 250 | { 251 | "b2a": {}, 252 | "b2b": {}, 253 | "b2c": {}, 254 | }, 255 | }, 256 | "c": {}, 257 | }, 258 | } 259 | 260 | err := underTest.Validate() 261 | 262 | expected := gokay.ErrorMap{ 263 | "MapOfSlicesOfMaps": gokay.ErrorSlice{ 264 | gokay.ErrorMap{ 265 | "a": gokay.ErrorMap{ 266 | "0": gokay.ErrorMap{ 267 | "a0a": vErr, 268 | }, 269 | "1": gokay.ErrorMap{ 270 | "a1c": vErr, 271 | }, 272 | }, 273 | "b": gokay.ErrorMap{ 274 | "2": gokay.ErrorMap{ 275 | "b2a": vErr, 276 | "b2b": vErr, 277 | "b2c": vErr, 278 | }, 279 | }, 280 | }, 281 | }, 282 | } 283 | 284 | require.Equal(t, expected, err) 285 | } 286 | 287 | func TestValidateImplicit_MapOfInterfaces(t *testing.T) { 288 | vErr := gokay.ErrorMap{ 289 | "Field": gokay.ErrorSlice{errors.New("invalid when false")}, 290 | } 291 | 292 | underTest := HasValidateImplicit{ 293 | InvalidStruct: nil, 294 | MapOfInterfaces: map[string]interface{}{ 295 | "a": []map[string]*TestValidate{ 296 | { 297 | "a0a": {}, 298 | "a0b": (*TestValidate)(nil), 299 | }, 300 | { 301 | "a1a": {true}, 302 | "a1b": (*TestValidate)(nil), 303 | "a1c": {}, 304 | }, 305 | {}, 306 | }, 307 | "b": []map[string]*TestValidate{ 308 | { 309 | "b0a": {true}, 310 | "b0b": (*TestValidate)(nil), 311 | "b0c": (*TestValidate)(nil), 312 | }, 313 | {}, 314 | { 315 | "b2a": {}, 316 | "b2b": {}, 317 | "b2c": {}, 318 | }, 319 | }, 320 | "c": &TestValidate{Valid: false}, 321 | "d": &TestValidate{Valid: true}, 322 | }, 323 | } 324 | 325 | err := underTest.Validate() 326 | spew.Dump(err) 327 | 328 | expected := gokay.ErrorMap{ 329 | "MapOfInterfaces": gokay.ErrorSlice{ 330 | gokay.ErrorMap{ 331 | "c": vErr, 332 | }, 333 | }, 334 | } 335 | 336 | require.Equal(t, expected, err) 337 | } 338 | 339 | func TestValidateNotNil_Slice(t *testing.T) { 340 | expected := gokay.ErrorMap{ 341 | "NotNilSlice": gokay.ErrorSlice{errors.New("is Nil")}, 342 | } 343 | 344 | underTest := NotNilTestStruct{ 345 | NotNilMap: map[string]interface{}{}, 346 | } 347 | 348 | err := underTest.Validate() 349 | require.Equal(t, expected, err) 350 | } 351 | 352 | func TestValidateNotNil_Map(t *testing.T) { 353 | expected := gokay.ErrorMap{ 354 | "NotNilMap": gokay.ErrorSlice{errors.New("is Nil")}, 355 | } 356 | 357 | underTest := NotNilTestStruct{ 358 | NotNilSlice: []string{}, 359 | } 360 | 361 | err := underTest.Validate() 362 | require.Equal(t, expected, err) 363 | } 364 | 365 | func TestValidateNotEqual_Valid(t *testing.T) { 366 | zero := int64(1) 367 | empty := "" 368 | underTest := NotEqualTestStruct{ 369 | NotEqualInt64: 1, 370 | NotEqualInt64Ptr: &zero, 371 | NotEqualString: "2", 372 | NotEqualStringPtr: &empty, 373 | } 374 | 375 | err := underTest.Validate() 376 | require.Nil(t, err) 377 | } 378 | func TestValidateNotEqual_NilValid(t *testing.T) { 379 | underTest := NotEqualTestStruct{ 380 | NotEqualInt64: 1, 381 | NotEqualString: "2", 382 | } 383 | 384 | err := underTest.Validate() 385 | require.Nil(t, err) 386 | } 387 | 388 | func TestValidateNotEqual_Inalid(t *testing.T) { 389 | expected := gokay.ErrorMap{ 390 | "NotEqualInt64": gokay.ErrorSlice{errors.New("NotEqualInt64 cannot equal '0'")}, 391 | "NotEqualInt64Ptr": gokay.ErrorSlice{errors.New("NotEqualInt64Ptr cannot equal '7'")}, 392 | "NotEqualString": gokay.ErrorSlice{errors.New("NotEqualString cannot equal ''")}, 393 | "NotEqualStringPtr": gokay.ErrorSlice{errors.New("NotEqualStringPtr cannot equal 'gokay'")}, 394 | } 395 | 396 | seven := int64(7) 397 | gokay := "gokay" 398 | underTest := NotEqualTestStruct{ 399 | NotEqualInt64: 0, 400 | NotEqualInt64Ptr: &seven, 401 | NotEqualString: "", 402 | NotEqualStringPtr: &gokay, 403 | } 404 | 405 | err := underTest.Validate() 406 | require.Equal(t, expected, err) 407 | } 408 | 409 | func TestValidateSet_Valid(t *testing.T) { 410 | validCases := []string{"cat", "dog", "mouse"} 411 | 412 | for _, tc := range validCases { 413 | underTest := SetTestStruct{ 414 | SetString: tc, 415 | SetStringPtr: &tc, 416 | } 417 | 418 | err := underTest.Validate() 419 | require.Nil(t, err) 420 | } 421 | } 422 | 423 | func TestValidateSet_NilValid(t *testing.T) { 424 | underTest := SetTestStruct{} 425 | 426 | err := underTest.Validate() 427 | require.Nil(t, err) 428 | } 429 | 430 | func TestValidateSet_Inalid(t *testing.T) { 431 | expected := gokay.ErrorMap{ 432 | "SetString": gokay.ErrorSlice{errors.New("SetString must equal cat or dog or mouse")}, 433 | "SetStringPtr": gokay.ErrorSlice{errors.New("SetStringPtr must equal cat or dog or mouse")}, 434 | } 435 | 436 | gokay := "gokay" 437 | underTest := SetTestStruct{ 438 | SetString: "Cat", 439 | SetStringPtr: &gokay, 440 | } 441 | 442 | err := underTest.Validate() 443 | require.Equal(t, expected, err) 444 | } 445 | -------------------------------------------------------------------------------- /gkgen/generator.go: -------------------------------------------------------------------------------- 1 | package gkgen 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "reflect" 10 | ) 11 | 12 | // Generater defines the behavior of types that generate validation code 13 | type Generater interface { 14 | Generate(reflect.Type, reflect.StructField, []string) (string, error) 15 | Name() string 16 | } 17 | 18 | // ValidateGenerator holds a map of identifiers and Generator's 19 | type ValidateGenerator struct { 20 | Generators map[string]Generater 21 | } 22 | 23 | // NewValidateGenerator creates a new pointer value of type ValidateGenerator 24 | // - Hex: checks if a string is a valid hexadecimal format number 25 | // - Length: takes 1 integer argument and compares the length of a string field against that 26 | // - NotNil: Validate fails if field is nil 27 | // - Set: Validate fails if field is not in a specific set of values 28 | // - NotEqual: Validate fails if field is equal to specific value 29 | // - UUID: Checks and fails if a string is not a valid UUID 30 | func NewValidateGenerator() *ValidateGenerator { 31 | v := &ValidateGenerator{make(map[string]Generater)} 32 | v.AddValidation(NewNotNilValidator()) 33 | v.AddValidation(NewSetValidator()) 34 | v.AddValidation(NewNotEqualValidator()) 35 | v.AddValidation(NewLengthValidator()) 36 | v.AddValidation(NewHexValidator()) 37 | v.AddValidation(NewUUIDValidator()) 38 | v.AddValidation(NewBCP47Validator()) 39 | v.AddValidation(NewMinLengthValidator()) 40 | return v 41 | } 42 | 43 | // AddValidation adds a Validation to a ValidateGenerator, that Validation can be applied to a struct 44 | // field using the string returned by 45 | // validator.Name() 46 | func (s *ValidateGenerator) AddValidation(g Generater) error { 47 | s.Generators[g.Name()] = g 48 | return nil 49 | } 50 | 51 | // Generate generates Validate method for a structure 52 | // Implicitly generates code that validates Structs, Slices and Maps which can be nested. Null pointer fields are considered valid by default 53 | // Return value of generated function is an ErrorMap of ErrorSlices, where each element of an ErrorSlice represents a failed validation 54 | func (s *ValidateGenerator) Generate(out io.Writer, i interface{}) error { 55 | structValue := reflect.ValueOf(i) 56 | structType := reflect.TypeOf(i) 57 | 58 | hasValidation := false 59 | svBuf := &bytes.Buffer{} 60 | 61 | fmt.Fprintf(svBuf, "func(s %s) Validate() error {\n", structType.Name()) 62 | fmt.Fprint(svBuf, "\tem := make(gokay.ErrorMap)\n\n") 63 | 64 | // Loop through fields 65 | for j := 0; j < structValue.NumField(); j++ { 66 | fieldIsValidated := false 67 | 68 | field := structType.Field(j) 69 | tag := field.Tag.Get("valid") // Everything under the 'valid' tag 70 | 71 | fvBuf := &bytes.Buffer{} 72 | 73 | fmt.Fprintf(fvBuf, ` 74 | // BEGIN %[1]s field Validations 75 | errors%[1]s := make(gokay.ErrorSlice, 0, 0) 76 | `, field.Name) 77 | if tag != "" && tag != "-" { 78 | field := structType.Field(j) 79 | 80 | vcs, err := ParseTag(i, tag) 81 | if err != nil { 82 | return fmt.Errorf("Unable to parse tag: '%v'. Error: '%v'", tag, err) 83 | } 84 | 85 | for _, vc := range vcs { 86 | if _, ok := s.Generators[vc.name]; !ok { 87 | return fmt.Errorf("Unknown validation generator name: '%s'", vc.name) 88 | } 89 | code, err := s.Generators[vc.name].Generate(structType, field, vc.Params) 90 | fmt.Fprintf(fvBuf, "// %s", vc.name) 91 | fmt.Fprintln(fvBuf, code) 92 | if err != nil { 93 | return err 94 | } 95 | } 96 | fieldIsValidated = true 97 | } 98 | 99 | isPtr := field.Type.Kind() == reflect.Ptr 100 | 101 | var fieldType reflect.Type 102 | 103 | if isPtr { 104 | fieldType = field.Type.Elem() 105 | } else { 106 | fieldType = field.Type 107 | } 108 | 109 | recursiveValidate := false 110 | switch fieldType.Kind() { 111 | case reflect.Struct: 112 | recursiveValidate = true 113 | fieldIsValidated = true 114 | case reflect.Ptr: 115 | return errors.New("Nested pointers are not currently supported by gokay") 116 | } 117 | 118 | if isPtr && recursiveValidate { 119 | fmt.Fprintf(fvBuf, ` 120 | if s.%s != nil { 121 | `, field.Name) 122 | } 123 | 124 | switch fieldType.Kind() { 125 | case reflect.Struct: 126 | fmt.Fprintf(fvBuf, `if err := gokay.Validate(s.%[1]s); err != nil { 127 | errors%[1]s = append(errors%[1]s, err) 128 | } 129 | `, field.Name) 130 | case reflect.Map: 131 | // TODO: Support non-string keys 132 | mapBuf := &bytes.Buffer{} 133 | err := generateMapValidationCode(mapBuf, fieldType, field.Name, 0) 134 | if err != nil { 135 | log.Printf("WARNING: Cannot generate recursive validation of map %q, %s", field.Name, err.Error()) 136 | } else { 137 | io.Copy(fvBuf, mapBuf) 138 | recursiveValidate = true 139 | fieldIsValidated = true 140 | } 141 | case reflect.Slice, reflect.Array: 142 | slBuf := &bytes.Buffer{} 143 | err := generateSliceValidationCode(slBuf, fieldType, field.Name, 0) 144 | if err != nil { 145 | log.Printf("WARNING: Cannot generate recursive validation of slice %q, %s", field.Name, err.Error()) 146 | } else { 147 | io.Copy(fvBuf, slBuf) 148 | recursiveValidate = true 149 | fieldIsValidated = true 150 | } 151 | } 152 | 153 | if isPtr && recursiveValidate { 154 | fmt.Fprintln(fvBuf, "}") 155 | } 156 | 157 | fmt.Fprintf(fvBuf, ` 158 | if len(errors%[1]s) > 0 { 159 | em["%[1]s"] = errors%[1]s 160 | } 161 | `, field.Name) 162 | 163 | fmt.Fprintf(fvBuf, "// END %s field Validations\n", field.Name) 164 | 165 | if fieldIsValidated { 166 | svBuf.Write(fvBuf.Bytes()) 167 | hasValidation = true 168 | } 169 | } 170 | fmt.Fprintln(svBuf, ` 171 | if len(em) > 0 { 172 | return em 173 | } else { 174 | return nil 175 | } 176 | `) 177 | 178 | fmt.Fprintln(svBuf, "}") 179 | 180 | if hasValidation { 181 | out.Write(svBuf.Bytes()) 182 | } 183 | 184 | return nil 185 | } 186 | 187 | // generateMapValidationCode generates validation code used with maps 188 | func generateMapValidationCode(out io.Writer, fieldType reflect.Type, fieldName string, depth int64) error { 189 | if fieldType.Kind() != reflect.Map { 190 | return fmt.Errorf("Cannot call `generateMapValidationCode` on non-map type '%v'", fieldType) 191 | } 192 | 193 | if fieldType.Key().Kind() != reflect.String { 194 | return fmt.Errorf("Unsupported map Key type at depth %d of Field %s. Key Type: '%v'.", depth, fieldName, fieldType.Key().Kind()) 195 | } 196 | 197 | if depth == 0 { 198 | fmt.Fprintf(out, "em%s := make(gokay.ErrorMap)\n", fieldName) 199 | fmt.Fprintf(out, "for k%d, v%d := range s.%s {\n", depth, depth, fieldName) 200 | } else { 201 | fmt.Fprintf(out, "for k%d, v%d := range v%d {\n", depth, depth, depth-1) 202 | } 203 | 204 | isPtr := fieldType.Elem().Kind() == reflect.Ptr 205 | if isPtr { 206 | fieldType = fieldType.Elem().Elem() 207 | fmt.Fprintf(out, "if v%d != nil {\n", depth) 208 | } else { 209 | fieldType = fieldType.Elem() 210 | } 211 | 212 | if fieldType.Kind() == reflect.Ptr { 213 | return fmt.Errorf("Recursive validation of nested pointers not yet supported. Field '%s' at depth '%d'", fieldName, depth) 214 | } 215 | 216 | switch fieldType.Kind() { 217 | case reflect.Map: 218 | fmt.Fprintf(out, "emv%d := make(gokay.ErrorMap)\n", depth) 219 | err := generateMapValidationCode(out, fieldType, fieldName, depth+1) 220 | if err != nil { 221 | return err 222 | } 223 | case reflect.Slice, reflect.Array: 224 | fmt.Fprintf(out, "emv%d := make(gokay.ErrorMap)\n", depth) 225 | err := generateSliceValidationCode(out, fieldType, fieldName, depth+1) 226 | if err != nil { 227 | return err 228 | } 229 | case reflect.Struct, reflect.Interface: 230 | if depth > 0 { 231 | fmt.Fprintf(out, 232 | `if err := gokay.Validate(v%d); err != nil { 233 | emv%d[fmt.Sprintf("%%v", k%d)] = err 234 | } 235 | `, depth, depth-1, depth) 236 | } else { 237 | fmt.Fprintf(out, 238 | `if err := gokay.Validate(v%d); err != nil { 239 | em%s[fmt.Sprintf("%%v", k%d)] = err 240 | } 241 | `, depth, fieldName, depth) 242 | } 243 | default: 244 | return fmt.Errorf("Unsupported map Value type at depth %d. Value Type: '%s'", depth, fieldType.Kind()) 245 | } 246 | 247 | if isPtr { 248 | fmt.Fprint(out, "}\n") 249 | } 250 | 251 | fmt.Fprint(out, "}\n") 252 | 253 | if depth == 0 { 254 | fmt.Fprintf(out, ` 255 | if len(em%[1]s) > 0 { 256 | errors%[1]s = append(errors%[1]s, em%[1]s) 257 | } 258 | `, fieldName) 259 | } else if depth == 1 { 260 | fmt.Fprintf(out, ` 261 | if len(emv%d) > 0 { 262 | em%s[fmt.Sprintf("%%v", k%d)] = emv%d 263 | } 264 | `, depth-1, fieldName, depth-1, depth-1) 265 | } else { 266 | fmt.Fprintf(out, ` 267 | if len(emv%d) > 0 { 268 | emv%d[fmt.Sprintf("%%v", k%d)] = emv%d 269 | } 270 | `, depth-1, depth-2, depth-1, depth-1) 271 | } 272 | return nil 273 | } 274 | 275 | // generateSliceValidationCode generates validation code used with slice 276 | func generateSliceValidationCode(out io.Writer, fieldType reflect.Type, fieldName string, depth int64) error { 277 | if fieldType.Kind() != reflect.Slice { 278 | return fmt.Errorf("`generateSliceValidationCode` only supports slices and arrays. Not: '%s'", fieldType.Kind()) 279 | } 280 | 281 | isPtr := fieldType.Elem().Kind() == reflect.Ptr 282 | // Slice of Ptrs 283 | if isPtr { 284 | fieldType = fieldType.Elem().Elem() 285 | } else { 286 | fieldType = fieldType.Elem() 287 | } 288 | 289 | if depth == 0 { 290 | fmt.Fprintf(out, "em%s := make(gokay.ErrorMap)\n", fieldName) 291 | fmt.Fprintf(out, "for k%d, v%d := range s.%s {\n", depth, depth, fieldName) 292 | } else { 293 | fmt.Fprintf(out, "for k%d, v%d := range v%d {\n", depth, depth, depth-1) 294 | } 295 | 296 | if isPtr { 297 | fmt.Fprintf(out, "if v%d != nil {\n", depth) 298 | } 299 | 300 | switch fieldType.Kind() { 301 | case reflect.Slice, reflect.Array: 302 | fmt.Fprintf(out, "emv%d := make(gokay.ErrorMap)\n", depth) 303 | err := generateSliceValidationCode(out, fieldType, fieldName, depth+1) 304 | if err != nil { 305 | return err 306 | } 307 | 308 | case reflect.Map: 309 | fmt.Fprintf(out, "emv%d := make(gokay.ErrorMap)\n", depth) 310 | err := generateMapValidationCode(out, fieldType, fieldName, depth+1) 311 | if err != nil { 312 | return err 313 | } 314 | 315 | case reflect.Struct: 316 | if depth > 0 { 317 | fmt.Fprintf(out, 318 | `if err := gokay.Validate(v%d); err != nil { 319 | emv%d[fmt.Sprintf("%%v", k%d)] = err 320 | } 321 | `, depth, depth-1, depth) 322 | } else { 323 | fmt.Fprintf(out, 324 | `if err := gokay.Validate(v%d); err != nil { 325 | em%s[fmt.Sprintf("%%v", k%d)] = err 326 | } 327 | `, depth, fieldName, depth) 328 | } 329 | default: 330 | return fmt.Errorf("`generateSliceValidationCode` cannot generate code to recursively validate slices of (pointers to) '%s'", fieldType.Kind()) 331 | } 332 | 333 | if isPtr { 334 | fmt.Fprint(out, "}\n") 335 | } 336 | 337 | fmt.Fprint(out, "}\n") 338 | 339 | if depth == 0 { 340 | fmt.Fprintf(out, ` 341 | if len(em%[1]s) > 0 { 342 | errors%[1]s = append(errors%[1]s, em%[1]s) 343 | } 344 | `, fieldName) 345 | } else if depth == 1 { 346 | fmt.Fprintf(out, ` 347 | if len(emv%d) > 0 { 348 | em%s[fmt.Sprintf("%%v", k%d)] = emv%d 349 | } 350 | `, depth-1, fieldName, depth-1, depth-1) 351 | } else { 352 | fmt.Fprintf(out, ` 353 | if len(emv%d) > 0 { 354 | emv%d[fmt.Sprintf("%%v", k%d)] = emv%d 355 | } 356 | `, depth-1, depth-2, depth-1, depth-1) 357 | } 358 | 359 | return nil 360 | } 361 | -------------------------------------------------------------------------------- /internal/gkexample/example_validators.go: -------------------------------------------------------------------------------- 1 | // Code in this file generated by gokay: github.com/zencoder/gokay 2 | package gkexample 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/zencoder/gokay/gokay" 9 | ) 10 | 11 | func (s Example) Validate() error { 12 | em := make(gokay.ErrorMap) 13 | 14 | // BEGIN MapOfInterfaces field Validations 15 | errorsMapOfInterfaces := make(gokay.ErrorSlice, 0, 0) 16 | // NotNil 17 | if s.MapOfInterfaces == nil { 18 | errorsMapOfInterfaces = append(errorsMapOfInterfaces, errors.New("is Nil")) 19 | } 20 | emMapOfInterfaces := make(gokay.ErrorMap) 21 | for k0, v0 := range s.MapOfInterfaces { 22 | if err := gokay.Validate(v0); err != nil { 23 | emMapOfInterfaces[fmt.Sprintf("%v", k0)] = err 24 | } 25 | } 26 | 27 | if len(emMapOfInterfaces) > 0 { 28 | errorsMapOfInterfaces = append(errorsMapOfInterfaces, emMapOfInterfaces) 29 | } 30 | 31 | if len(errorsMapOfInterfaces) > 0 { 32 | em["MapOfInterfaces"] = errorsMapOfInterfaces 33 | } 34 | // END MapOfInterfaces field Validations 35 | 36 | if len(em) > 0 { 37 | return em 38 | } else { 39 | return nil 40 | } 41 | 42 | } 43 | func (s ExampleStruct) Validate() error { 44 | em := make(gokay.ErrorMap) 45 | 46 | // BEGIN HexStringPtr field Validations 47 | errorsHexStringPtr := make(gokay.ErrorSlice, 0, 0) 48 | // Length 49 | if err := gokay.LengthString(16, s.HexStringPtr); err != nil { 50 | errorsHexStringPtr = append(errorsHexStringPtr, err) 51 | } 52 | 53 | // NotNil 54 | if s.HexStringPtr == nil { 55 | errorsHexStringPtr = append(errorsHexStringPtr, errors.New("is Nil")) 56 | } 57 | // Hex 58 | if err := gokay.IsHex(s.HexStringPtr); err != nil { 59 | errorsHexStringPtr = append(errorsHexStringPtr, err) 60 | } 61 | 62 | if len(errorsHexStringPtr) > 0 { 63 | em["HexStringPtr"] = errorsHexStringPtr 64 | } 65 | // END HexStringPtr field Validations 66 | 67 | // BEGIN HexString field Validations 68 | errorsHexString := make(gokay.ErrorSlice, 0, 0) 69 | // Length 70 | if err := gokay.LengthString(12, &s.HexString); err != nil { 71 | errorsHexString = append(errorsHexString, err) 72 | } 73 | 74 | // Hex 75 | if err := gokay.IsHex(&s.HexString); err != nil { 76 | errorsHexString = append(errorsHexString, err) 77 | } 78 | 79 | if len(errorsHexString) > 0 { 80 | em["HexString"] = errorsHexString 81 | } 82 | // END HexString field Validations 83 | 84 | // BEGIN BCP47StringPtr field Validations 85 | errorsBCP47StringPtr := make(gokay.ErrorSlice, 0, 0) 86 | // NotNil 87 | if s.BCP47StringPtr == nil { 88 | errorsBCP47StringPtr = append(errorsBCP47StringPtr, errors.New("is Nil")) 89 | } 90 | // BCP47 91 | if err := gokay.IsBCP47(s.BCP47StringPtr); err != nil { 92 | errorsBCP47StringPtr = append(errorsBCP47StringPtr, err) 93 | } 94 | 95 | if len(errorsBCP47StringPtr) > 0 { 96 | em["BCP47StringPtr"] = errorsBCP47StringPtr 97 | } 98 | // END BCP47StringPtr field Validations 99 | 100 | // BEGIN BCP47String field Validations 101 | errorsBCP47String := make(gokay.ErrorSlice, 0, 0) 102 | // BCP47 103 | if err := gokay.IsBCP47(&s.BCP47String); err != nil { 104 | errorsBCP47String = append(errorsBCP47String, err) 105 | } 106 | 107 | if len(errorsBCP47String) > 0 { 108 | em["BCP47String"] = errorsBCP47String 109 | } 110 | // END BCP47String field Validations 111 | 112 | // BEGIN CanBeNilWithConstraints field Validations 113 | errorsCanBeNilWithConstraints := make(gokay.ErrorSlice, 0, 0) 114 | // Length 115 | if err := gokay.LengthString(12, s.CanBeNilWithConstraints); err != nil { 116 | errorsCanBeNilWithConstraints = append(errorsCanBeNilWithConstraints, err) 117 | } 118 | 119 | if len(errorsCanBeNilWithConstraints) > 0 { 120 | em["CanBeNilWithConstraints"] = errorsCanBeNilWithConstraints 121 | } 122 | // END CanBeNilWithConstraints field Validations 123 | 124 | if len(em) > 0 { 125 | return em 126 | } else { 127 | return nil 128 | } 129 | 130 | } 131 | func (s HasValidateImplicit) Validate() error { 132 | em := make(gokay.ErrorMap) 133 | 134 | // BEGIN InvalidStruct field Validations 135 | errorsInvalidStruct := make(gokay.ErrorSlice, 0, 0) 136 | 137 | if s.InvalidStruct != nil { 138 | if err := gokay.Validate(s.InvalidStruct); err != nil { 139 | errorsInvalidStruct = append(errorsInvalidStruct, err) 140 | } 141 | } 142 | 143 | if len(errorsInvalidStruct) > 0 { 144 | em["InvalidStruct"] = errorsInvalidStruct 145 | } 146 | // END InvalidStruct field Validations 147 | 148 | // BEGIN ValidStruct field Validations 149 | errorsValidStruct := make(gokay.ErrorSlice, 0, 0) 150 | if err := gokay.Validate(s.ValidStruct); err != nil { 151 | errorsValidStruct = append(errorsValidStruct, err) 152 | } 153 | 154 | if len(errorsValidStruct) > 0 { 155 | em["ValidStruct"] = errorsValidStruct 156 | } 157 | // END ValidStruct field Validations 158 | 159 | // BEGIN MapOfStruct field Validations 160 | errorsMapOfStruct := make(gokay.ErrorSlice, 0, 0) 161 | emMapOfStruct := make(gokay.ErrorMap) 162 | for k0, v0 := range s.MapOfStruct { 163 | if err := gokay.Validate(v0); err != nil { 164 | emMapOfStruct[fmt.Sprintf("%v", k0)] = err 165 | } 166 | } 167 | 168 | if len(emMapOfStruct) > 0 { 169 | errorsMapOfStruct = append(errorsMapOfStruct, emMapOfStruct) 170 | } 171 | 172 | if len(errorsMapOfStruct) > 0 { 173 | em["MapOfStruct"] = errorsMapOfStruct 174 | } 175 | // END MapOfStruct field Validations 176 | 177 | // BEGIN MapOfStructPtrs field Validations 178 | errorsMapOfStructPtrs := make(gokay.ErrorSlice, 0, 0) 179 | emMapOfStructPtrs := make(gokay.ErrorMap) 180 | for k0, v0 := range s.MapOfStructPtrs { 181 | if v0 != nil { 182 | if err := gokay.Validate(v0); err != nil { 183 | emMapOfStructPtrs[fmt.Sprintf("%v", k0)] = err 184 | } 185 | } 186 | } 187 | 188 | if len(emMapOfStructPtrs) > 0 { 189 | errorsMapOfStructPtrs = append(errorsMapOfStructPtrs, emMapOfStructPtrs) 190 | } 191 | 192 | if len(errorsMapOfStructPtrs) > 0 { 193 | em["MapOfStructPtrs"] = errorsMapOfStructPtrs 194 | } 195 | // END MapOfStructPtrs field Validations 196 | 197 | // BEGIN MapOfMaps field Validations 198 | errorsMapOfMaps := make(gokay.ErrorSlice, 0, 0) 199 | emMapOfMaps := make(gokay.ErrorMap) 200 | for k0, v0 := range s.MapOfMaps { 201 | emv0 := make(gokay.ErrorMap) 202 | for k1, v1 := range v0 { 203 | if v1 != nil { 204 | if err := gokay.Validate(v1); err != nil { 205 | emv0[fmt.Sprintf("%v", k1)] = err 206 | } 207 | } 208 | } 209 | 210 | if len(emv0) > 0 { 211 | emMapOfMaps[fmt.Sprintf("%v", k0)] = emv0 212 | } 213 | } 214 | 215 | if len(emMapOfMaps) > 0 { 216 | errorsMapOfMaps = append(errorsMapOfMaps, emMapOfMaps) 217 | } 218 | 219 | if len(errorsMapOfMaps) > 0 { 220 | em["MapOfMaps"] = errorsMapOfMaps 221 | } 222 | // END MapOfMaps field Validations 223 | 224 | // BEGIN MapMapsOfSlices field Validations 225 | errorsMapMapsOfSlices := make(gokay.ErrorSlice, 0, 0) 226 | emMapMapsOfSlices := make(gokay.ErrorMap) 227 | for k0, v0 := range s.MapMapsOfSlices { 228 | emv0 := make(gokay.ErrorMap) 229 | for k1, v1 := range v0 { 230 | emv1 := make(gokay.ErrorMap) 231 | for k2, v2 := range v1 { 232 | if v2 != nil { 233 | if err := gokay.Validate(v2); err != nil { 234 | emv1[fmt.Sprintf("%v", k2)] = err 235 | } 236 | } 237 | } 238 | 239 | if len(emv1) > 0 { 240 | emv0[fmt.Sprintf("%v", k1)] = emv1 241 | } 242 | } 243 | 244 | if len(emv0) > 0 { 245 | emMapMapsOfSlices[fmt.Sprintf("%v", k0)] = emv0 246 | } 247 | } 248 | 249 | if len(emMapMapsOfSlices) > 0 { 250 | errorsMapMapsOfSlices = append(errorsMapMapsOfSlices, emMapMapsOfSlices) 251 | } 252 | 253 | if len(errorsMapMapsOfSlices) > 0 { 254 | em["MapMapsOfSlices"] = errorsMapMapsOfSlices 255 | } 256 | // END MapMapsOfSlices field Validations 257 | 258 | // BEGIN MapOfInterfaces field Validations 259 | errorsMapOfInterfaces := make(gokay.ErrorSlice, 0, 0) 260 | emMapOfInterfaces := make(gokay.ErrorMap) 261 | for k0, v0 := range s.MapOfInterfaces { 262 | if err := gokay.Validate(v0); err != nil { 263 | emMapOfInterfaces[fmt.Sprintf("%v", k0)] = err 264 | } 265 | } 266 | 267 | if len(emMapOfInterfaces) > 0 { 268 | errorsMapOfInterfaces = append(errorsMapOfInterfaces, emMapOfInterfaces) 269 | } 270 | 271 | if len(errorsMapOfInterfaces) > 0 { 272 | em["MapOfInterfaces"] = errorsMapOfInterfaces 273 | } 274 | // END MapOfInterfaces field Validations 275 | 276 | // BEGIN SimpleSlice field Validations 277 | errorsSimpleSlice := make(gokay.ErrorSlice, 0, 0) 278 | emSimpleSlice := make(gokay.ErrorMap) 279 | for k0, v0 := range s.SimpleSlice { 280 | if v0 != nil { 281 | if err := gokay.Validate(v0); err != nil { 282 | emSimpleSlice[fmt.Sprintf("%v", k0)] = err 283 | } 284 | } 285 | } 286 | 287 | if len(emSimpleSlice) > 0 { 288 | errorsSimpleSlice = append(errorsSimpleSlice, emSimpleSlice) 289 | } 290 | 291 | if len(errorsSimpleSlice) > 0 { 292 | em["SimpleSlice"] = errorsSimpleSlice 293 | } 294 | // END SimpleSlice field Validations 295 | 296 | // BEGIN SliceOfSlicesOfSlices field Validations 297 | errorsSliceOfSlicesOfSlices := make(gokay.ErrorSlice, 0, 0) 298 | emSliceOfSlicesOfSlices := make(gokay.ErrorMap) 299 | for k0, v0 := range s.SliceOfSlicesOfSlices { 300 | emv0 := make(gokay.ErrorMap) 301 | for k1, v1 := range v0 { 302 | emv1 := make(gokay.ErrorMap) 303 | for k2, v2 := range v1 { 304 | if v2 != nil { 305 | if err := gokay.Validate(v2); err != nil { 306 | emv1[fmt.Sprintf("%v", k2)] = err 307 | } 308 | } 309 | } 310 | 311 | if len(emv1) > 0 { 312 | emv0[fmt.Sprintf("%v", k1)] = emv1 313 | } 314 | } 315 | 316 | if len(emv0) > 0 { 317 | emSliceOfSlicesOfSlices[fmt.Sprintf("%v", k0)] = emv0 318 | } 319 | } 320 | 321 | if len(emSliceOfSlicesOfSlices) > 0 { 322 | errorsSliceOfSlicesOfSlices = append(errorsSliceOfSlicesOfSlices, emSliceOfSlicesOfSlices) 323 | } 324 | 325 | if len(errorsSliceOfSlicesOfSlices) > 0 { 326 | em["SliceOfSlicesOfSlices"] = errorsSliceOfSlicesOfSlices 327 | } 328 | // END SliceOfSlicesOfSlices field Validations 329 | 330 | // BEGIN MapOfSlicesOfMaps field Validations 331 | errorsMapOfSlicesOfMaps := make(gokay.ErrorSlice, 0, 0) 332 | emMapOfSlicesOfMaps := make(gokay.ErrorMap) 333 | for k0, v0 := range s.MapOfSlicesOfMaps { 334 | emv0 := make(gokay.ErrorMap) 335 | for k1, v1 := range v0 { 336 | emv1 := make(gokay.ErrorMap) 337 | for k2, v2 := range v1 { 338 | if v2 != nil { 339 | if err := gokay.Validate(v2); err != nil { 340 | emv1[fmt.Sprintf("%v", k2)] = err 341 | } 342 | } 343 | } 344 | 345 | if len(emv1) > 0 { 346 | emv0[fmt.Sprintf("%v", k1)] = emv1 347 | } 348 | } 349 | 350 | if len(emv0) > 0 { 351 | emMapOfSlicesOfMaps[fmt.Sprintf("%v", k0)] = emv0 352 | } 353 | } 354 | 355 | if len(emMapOfSlicesOfMaps) > 0 { 356 | errorsMapOfSlicesOfMaps = append(errorsMapOfSlicesOfMaps, emMapOfSlicesOfMaps) 357 | } 358 | 359 | if len(errorsMapOfSlicesOfMaps) > 0 { 360 | em["MapOfSlicesOfMaps"] = errorsMapOfSlicesOfMaps 361 | } 362 | // END MapOfSlicesOfMaps field Validations 363 | 364 | if len(em) > 0 { 365 | return em 366 | } else { 367 | return nil 368 | } 369 | 370 | } 371 | func (s NotEqualTestStruct) Validate() error { 372 | em := make(gokay.ErrorMap) 373 | 374 | // BEGIN NotEqualString field Validations 375 | errorsNotEqualString := make(gokay.ErrorSlice, 0, 0) 376 | // NotEqual 377 | if s.NotEqualString == "" { 378 | errorsNotEqualString = append(errorsNotEqualString, errors.New("NotEqualString cannot equal ''")) 379 | } 380 | 381 | if len(errorsNotEqualString) > 0 { 382 | em["NotEqualString"] = errorsNotEqualString 383 | } 384 | // END NotEqualString field Validations 385 | 386 | // BEGIN NotEqualStringPtr field Validations 387 | errorsNotEqualStringPtr := make(gokay.ErrorSlice, 0, 0) 388 | // NotEqual 389 | if s.NotEqualStringPtr != nil && *s.NotEqualStringPtr == "gokay" { 390 | errorsNotEqualStringPtr = append(errorsNotEqualStringPtr, errors.New("NotEqualStringPtr cannot equal 'gokay'")) 391 | } 392 | 393 | if len(errorsNotEqualStringPtr) > 0 { 394 | em["NotEqualStringPtr"] = errorsNotEqualStringPtr 395 | } 396 | // END NotEqualStringPtr field Validations 397 | 398 | // BEGIN NotEqualInt64 field Validations 399 | errorsNotEqualInt64 := make(gokay.ErrorSlice, 0, 0) 400 | // NotEqual 401 | if s.NotEqualInt64 == 0 { 402 | errorsNotEqualInt64 = append(errorsNotEqualInt64, errors.New("NotEqualInt64 cannot equal '0'")) 403 | } 404 | 405 | if len(errorsNotEqualInt64) > 0 { 406 | em["NotEqualInt64"] = errorsNotEqualInt64 407 | } 408 | // END NotEqualInt64 field Validations 409 | 410 | // BEGIN NotEqualInt64Ptr field Validations 411 | errorsNotEqualInt64Ptr := make(gokay.ErrorSlice, 0, 0) 412 | // NotEqual 413 | if s.NotEqualInt64Ptr != nil && *s.NotEqualInt64Ptr == 7 { 414 | errorsNotEqualInt64Ptr = append(errorsNotEqualInt64Ptr, errors.New("NotEqualInt64Ptr cannot equal '7'")) 415 | } 416 | 417 | if len(errorsNotEqualInt64Ptr) > 0 { 418 | em["NotEqualInt64Ptr"] = errorsNotEqualInt64Ptr 419 | } 420 | // END NotEqualInt64Ptr field Validations 421 | 422 | if len(em) > 0 { 423 | return em 424 | } else { 425 | return nil 426 | } 427 | 428 | } 429 | func (s NotNilTestStruct) Validate() error { 430 | em := make(gokay.ErrorMap) 431 | 432 | // BEGIN NotNilMap field Validations 433 | errorsNotNilMap := make(gokay.ErrorSlice, 0, 0) 434 | // NotNil 435 | if s.NotNilMap == nil { 436 | errorsNotNilMap = append(errorsNotNilMap, errors.New("is Nil")) 437 | } 438 | emNotNilMap := make(gokay.ErrorMap) 439 | for k0, v0 := range s.NotNilMap { 440 | if err := gokay.Validate(v0); err != nil { 441 | emNotNilMap[fmt.Sprintf("%v", k0)] = err 442 | } 443 | } 444 | 445 | if len(emNotNilMap) > 0 { 446 | errorsNotNilMap = append(errorsNotNilMap, emNotNilMap) 447 | } 448 | 449 | if len(errorsNotNilMap) > 0 { 450 | em["NotNilMap"] = errorsNotNilMap 451 | } 452 | // END NotNilMap field Validations 453 | 454 | // BEGIN NotNilSlice field Validations 455 | errorsNotNilSlice := make(gokay.ErrorSlice, 0, 0) 456 | // NotNil 457 | if s.NotNilSlice == nil { 458 | errorsNotNilSlice = append(errorsNotNilSlice, errors.New("is Nil")) 459 | } 460 | 461 | if len(errorsNotNilSlice) > 0 { 462 | em["NotNilSlice"] = errorsNotNilSlice 463 | } 464 | // END NotNilSlice field Validations 465 | 466 | if len(em) > 0 { 467 | return em 468 | } else { 469 | return nil 470 | } 471 | 472 | } 473 | func (s SetTestStruct) Validate() error { 474 | em := make(gokay.ErrorMap) 475 | 476 | // BEGIN SetString field Validations 477 | errorsSetString := make(gokay.ErrorSlice, 0, 0) 478 | // Set 479 | if s.SetString != "" && !(s.SetString == "cat" || s.SetString == "dog" || s.SetString == "mouse") { 480 | errorsSetString = append(errorsSetString, errors.New("SetString must equal cat or dog or mouse")) 481 | } 482 | 483 | if len(errorsSetString) > 0 { 484 | em["SetString"] = errorsSetString 485 | } 486 | // END SetString field Validations 487 | 488 | // BEGIN SetStringPtr field Validations 489 | errorsSetStringPtr := make(gokay.ErrorSlice, 0, 0) 490 | // Set 491 | if s.SetStringPtr != nil && !(*s.SetStringPtr == "cat" || *s.SetStringPtr == "dog" || *s.SetStringPtr == "mouse") { 492 | errorsSetStringPtr = append(errorsSetStringPtr, errors.New("SetStringPtr must equal cat or dog or mouse")) 493 | } 494 | 495 | if len(errorsSetStringPtr) > 0 { 496 | em["SetStringPtr"] = errorsSetStringPtr 497 | } 498 | // END SetStringPtr field Validations 499 | 500 | if len(em) > 0 { 501 | return em 502 | } else { 503 | return nil 504 | } 505 | 506 | } 507 | --------------------------------------------------------------------------------