├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── README.md ├── TODO.txt ├── UNLICENSE.txt └── v1 ├── and.go ├── array.go ├── boolean.go ├── doc.go ├── function.go ├── internal └── reflectutil.go ├── jsonv.go ├── nil.go ├── number.go ├── object.go ├── optional.go ├── or.go ├── string.go └── tests ├── and_test.go ├── array_test.go ├── boolean_test.go ├── common_test.go ├── example_test.go ├── function_test.go ├── nil_test.go ├── number_test.go ├── object_test.go ├── optional_test.go ├── or_test.go ├── performance_test.go ├── reflectutil_test.go └── string_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.out 3 | 4 | # Created by https://www.gitignore.io 5 | 6 | ### Go ### 7 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 8 | *.o 9 | *.a 10 | *.so 11 | 12 | # Folders 13 | _obj 14 | _test 15 | 16 | # Architecture specific extensions/prefixes 17 | *.[568vq] 18 | [568vq].out 19 | 20 | *.cgo1.go 21 | *.cgo2.c 22 | _cgo_defun.c 23 | _cgo_gotypes.go 24 | _cgo_export.* 25 | 26 | _testmain.go 27 | 28 | *.exe 29 | *.test 30 | *.prof 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.4 5 | install: 6 | - go get -v -d -t ./v1/... 7 | - go get golang.org/x/tools/cmd/cover 8 | - go get github.com/mattn/goveralls 9 | script: 10 | - go test -v -covermode=count -coverprofile=cover.prof -cover -coverpkg ./v1 ./v1/tests 11 | - cat cover.prof 12 | - goveralls -coverprofile=cover.prof -repotoken=$COVERALLS_TOKEN 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | File an issue or use pull requests, but relay your intent for the action. 4 | 5 | Try to avoid mixing different concerns in one commit. *(Look who's talking.)* Same applies to issues and pull requests. 6 | 7 | *By contributing to this project, you agree to place (and you agree that you have the right to place) your contribution under the same license as this project is.* 8 | 9 | 10 | 11 | #### Code formatting 12 | 13 | Run `go fmt` before committing. 14 | 15 | #### Run tests 16 | 17 | Test all reasonable code paths. 18 | 19 | go test ./tests/ 20 | 21 | #### Run code coverage 22 | 23 | Try to cover the necessary cases. 24 | 25 | go test -cover -coverpkg . -coverprofile cover.prof ./tests 26 | go tool cover -html=cover.prof -o coverage.html 27 | 28 | --- 29 | 30 | *This contribution readme was shamelessly modelled after: 31 | https://opencomparison.readthedocs.org/en/latest/contributing.html* 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Govalid [![godoc](https://godoc.org/github.com/gima/govalid/v1?status.png)](https://godoc.org/github.com/gima/govalid/v1) [![Build Status](https://travis-ci.org/gima/govalid.svg?branch=master)](https://travis-ci.org/gima/govalid) [![Coverage Status](https://coveralls.io/repos/github/gima/govalid/badge.svg?branch=master)](https://coveralls.io/github/gima/govalid?branch=master) [![License: Unlicense](https://img.shields.io/badge/%E2%9C%93-unlicense-4cc61e.svg?style=flat)](http://unlicense.org) 4 | 5 | Govalid is a data validation library that can validate [most data types](https://godoc.org/github.com/gima/govalid/v1) supported by golang. Custom validators can be used where the supplied ones are not enough. 6 | 7 | ```go 8 | import v "github.com/gima/govalid/v1" 9 | ``` 10 | 11 | 12 | ## Example 13 | 14 | Create a validator: 15 | 16 | ```go 17 | schema := v.Object( 18 | v.ObjKV("status", v.Boolean()), 19 | v.ObjKV("data", v.Object( 20 | v.ObjKV("token", v.Function(myValidatorFunc)), 21 | v.ObjKV("debug", v.Number(v.NumMin(1), v.NumMax(99999))), 22 | v.ObjKV("items", v.Array(v.ArrEach(v.Object( 23 | v.ObjKV("url", v.String(v.StrMin(1))), 24 | v.ObjKV("comment", v.Optional(v.String())), 25 | )))), 26 | v.ObjKV("ghost", v.Optional(v.String())), 27 | v.ObjKV("ghost2", v.Optional(v.String())), 28 | v.ObjKV("meta", v.Object( 29 | v.ObjKeys(v.String()), 30 | v.ObjValues(v.Or(v.Number(v.NumMin(.01), v.NumMax(1.1)), v.String())), 31 | )), 32 | )), 33 | ) 34 | ``` 35 | 36 | Validate some data using the created validator: 37 | 38 | ```go 39 | if path, err := schema.Validate(data); err == nil { 40 | t.Log("Validation passed.") 41 | } else { 42 | t.Fatalf("Validation failed at %s. Error (%s)", path, err) 43 | } 44 | ``` 45 | 46 | ```go 47 | // Example of failed validation: 48 | 49 | // Validation failed at Object->Key[data].Value->Object->Key[debug].Value->Number. 50 | // Error (expected (*)data convertible to float64, got bool) 51 | ``` 52 | 53 | You can also take a look at the "[tests/](https://github.com/gima/govalid/tree/master/v1/tests)" folder. (Sorry, but if you feel more documentation is needed, please open an issue.) 54 | 55 | 56 | 57 | ## Similar libraries 58 | 59 | `Go` [check](https://github.com/pengux/check) 60 | `Javascript` [js-schema](https://github.com/molnarg/js-schema), [jsonvalidator](https://code.google.com/p/jsonvalidator/) 61 | `Python` [voluptuous](https://pypi.python.org/pypi/voluptuous), [json_schema](https://pypi.python.org/pypi/json_schema) 62 | `Ruby` [json-schema](https://rubygems.org/gems/json-schema) 63 | 64 | Original idea for jsonv (version 0 of this library, before rename) loosely based on [js-schema](https://github.com/molnarg/js-schema), thank you. 65 | 66 | 67 | ## License 68 | 69 | http://unlicense.org 70 | Authoritative: UNLICENSE.txt 71 | Mention of origin would be appreciated. 72 | 73 | *jsonv, jsonv2, json validator, json validation, alternative, go, golang* 74 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | -- 2015/01/09 2 | # support data type: interface 3 | 4 | DOCUMENT VERY CAREFULLY HOW VALIDATION WORKS! "implicit diving into pointers". then user has validated struct ok, 5 | what happend when they try to type assert hmm? do [plan] and make it clear and that they need to use some other 6 | library to reach the data maybe or something 7 | 8 | BLABLABLA WTF 9 | reasoning: 10 | passing interface{} value copies the data, and that needs to be avoidable. 11 | (for example `json.Unmarshal` needs a pointer to an interface{} as well) 12 | plan: 13 | generic function that uses reflection to reach the first non-pointer & 14 | non-interface value and then returns that reflection Value. 15 | (this function would then be used in the beginning of every validator 16 | workhorse) 17 | 18 | # [MAYBE] validation functions should be more generic 19 | reasoning AGAINST: 20 | it could also be reasoned to make the supplied validators fixed in a way 21 | that they cannot be extended, and if someone wanted more functionality, 22 | they'd write their own Validator. 23 | reasoning FOR: 24 | _validation functions_ are exported and meant to be used from outside. 25 | currently each validator needs it's own type-specific validator function. 26 | this does not scale, because if someone wanted to do something more fancy 27 | with them, they cannot. for example, the following isn't currently possible: 28 | "to relay a path with an error from `type BooleanOpt func(bool) error`" 29 | plan: 30 | to-be-figured-out. 31 | 32 | # tests should be more generic / follow a pattern 33 | resoning: 34 | it's easy to forget (as I did at first) to test for example non-nil and nil 35 | values of different type or other such corner cases. this applies to each 36 | validator that handles types and hence should be generic/follow a pattern 37 | plan: 38 | maybe a function doing the "generic" tests using reflection, or a function 39 | taking functions for doing the "generic" tests. 40 | to-be-figured-out. 41 | -------------------------------------------------------------------------------- /UNLICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to [http://unlicense.org] 25 | -------------------------------------------------------------------------------- /v1/and.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ----------------------------------------------------------------------------- 8 | 9 | // Construct a logical-and validator using the specified validators. 10 | // Given no validators, this validator passes always. 11 | func And(validators ...Validator) Validator { 12 | return &andValidator{validators} 13 | } 14 | 15 | // ----------------------------------------------------------------------------- 16 | 17 | // validator for logical-and 18 | type andValidator struct { 19 | validators []Validator 20 | } 21 | 22 | // ----------------------------------------------------------------------------- 23 | 24 | // the actual workhorse for logical-and validator 25 | func (r *andValidator) Validate(data interface{}) (string, error) { 26 | 27 | for i, v := range r.validators { 28 | if path, err := v.Validate(data); err != nil { 29 | return fmt.Sprintf("And(idx: %d)->%s", i+1, path), err 30 | } 31 | } 32 | 33 | return "", nil 34 | } 35 | 36 | // ----------------------------------------------------------------------------- 37 | -------------------------------------------------------------------------------- /v1/array.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ----------------------------------------------------------------------------- 9 | 10 | // Array validation function. Parameter is reflection to array or slice. 11 | type ArrayOpt func(slice *reflect.Value) (path string, err error) 12 | 13 | // ----------------------------------------------------------------------------- 14 | 15 | // Construct an array validator using the specified validation functions. 16 | // Validates data type to be either an array or a slice, or a pointer to such. 17 | func Array(opts ...ArrayOpt) Validator { 18 | return &arrayValidator{opts} 19 | } 20 | 21 | // ----------------------------------------------------------------------------- 22 | 23 | // Array validator function for checking minimum number of entries. 24 | func ArrMin(min int) ArrayOpt { 25 | return func(slice *reflect.Value) (path string, err error) { 26 | if slice.Len() < min { 27 | return "", fmt.Errorf("number of entries %d should >= %d", slice.Len(), min) 28 | } 29 | return "", nil 30 | } 31 | } 32 | 33 | // ----------------------------------------------------------------------------- 34 | 35 | // Array validator function for checking maximum number of entries. 36 | func ArrMax(max int) ArrayOpt { 37 | return func(slice *reflect.Value) (path string, err error) { 38 | if slice.Len() > max { 39 | return "", fmt.Errorf("number of entries %d should <= %d", slice.Len(), max) 40 | } 41 | return "", nil 42 | } 43 | } 44 | 45 | // ----------------------------------------------------------------------------- 46 | 47 | // Array validator function for checking each entry with the specified validator. 48 | func ArrEach(validator Validator) ArrayOpt { 49 | return func(slice *reflect.Value) (path string, err error) { 50 | for i := 0; i < slice.Len(); i++ { 51 | path, err := validator.Validate(slice.Index(i).Interface()) 52 | if err != nil { 53 | return path, fmt.Errorf("idx %d: %s", i, err.Error()) 54 | } 55 | } 56 | return "", nil 57 | } 58 | } 59 | 60 | // ----------------------------------------------------------------------------- 61 | 62 | // validator for array 63 | type arrayValidator struct { 64 | opts []ArrayOpt 65 | } 66 | 67 | // ----------------------------------------------------------------------------- 68 | 69 | // the actual workhorse for array validator 70 | func (r *arrayValidator) Validate(data interface{}) (string, error) { 71 | value := reflect.ValueOf(data) 72 | switch value.Kind() { 73 | case reflect.Invalid: 74 | return "Array", fmt.Errorf("expected (*)array/slice, got ") 75 | 76 | case reflect.Ptr: 77 | if value.IsNil() { 78 | return "Array", fmt.Errorf("expected (*)array/slice, got %s", value.Kind()) 79 | } 80 | value = value.Elem() 81 | 82 | case reflect.Array: 83 | // pass through 84 | 85 | case reflect.Slice: 86 | // pass through 87 | 88 | default: 89 | return "Array", fmt.Errorf("expected (*)array/slice, got %s", value.Kind()) 90 | } 91 | 92 | for _, o := range r.opts { 93 | if path, err := o(&value); err != nil { 94 | if path == "" { 95 | return "Array", err 96 | } 97 | return "Array->" + path, err 98 | } 99 | } 100 | 101 | return "", nil 102 | } 103 | 104 | // ----------------------------------------------------------------------------- 105 | -------------------------------------------------------------------------------- /v1/boolean.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ----------------------------------------------------------------------------- 9 | 10 | // Boolean validation function. 11 | type BooleanOpt func(bool) error 12 | 13 | // ----------------------------------------------------------------------------- 14 | 15 | // Construct a boolean validator using the specified validation functions. 16 | // Validates data type to be either a bool or a pointer to such. 17 | func Boolean(opts ...BooleanOpt) Validator { 18 | return &boolValidator{opts} 19 | } 20 | 21 | // ----------------------------------------------------------------------------- 22 | 23 | // validator for boolean 24 | type boolValidator struct { 25 | opts []BooleanOpt 26 | } 27 | 28 | // ----------------------------------------------------------------------------- 29 | 30 | // Validation function for checking boolean value. 31 | func BoolIs(expected bool) BooleanOpt { 32 | return func(got bool) error { 33 | if got != expected { 34 | return fmt.Errorf("expected %t, got %t", expected, got) 35 | } 36 | return nil 37 | } 38 | } 39 | 40 | // ----------------------------------------------------------------------------- 41 | 42 | // the actual workhorse for boolean validator 43 | func (r *boolValidator) Validate(data interface{}) (string, error) { 44 | var v bool 45 | 46 | switch tmp := data.(type) { 47 | case bool: 48 | v = tmp 49 | case *bool: 50 | if tmp == nil { 51 | return "Boolean", fmt.Errorf("expected (*)bool, got ") 52 | } 53 | v = *tmp 54 | default: 55 | return "Boolean", fmt.Errorf("expected (*)bool, got %v", reflect.TypeOf(data)) 56 | } 57 | 58 | for _, o := range r.opts { 59 | if err := o(v); err != nil { 60 | return "Boolean", err 61 | } 62 | } 63 | 64 | return "", nil 65 | } 66 | 67 | // ----------------------------------------------------------------------------- 68 | -------------------------------------------------------------------------------- /v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Data validation library. For introduction, visit https://github.com/gima/govalid/blob/master/README.md 3 | 4 | Data type verification 5 | 6 | The supplied Validators verify data's type implicitly; they don't need extra 7 | parameters during their construction to do so. 8 | // This means, for example, that the following code verifies that "asd" is of 9 | // string type, even though no parameters are given to the String validator. 10 | path, err := govalid.String().Validate("asd") 11 | 12 | Supported data types 13 | 14 | The supplied validators understand the following data types: 15 | bool, array, map, ptr, slice, string and any numerical type except complex 16 | This leaves out 17 | complex, chan, func, interface, struct and unsafepointer 18 | 19 | (Don't be fooled by missing interface{} support, as this library uses reflection 20 | to reach values, and reflection goes from interface value to reflection object.) 21 | 22 | Regarding pointers 23 | 24 | It is recommended to pass pointers to the Validators, as this avoids making 25 | copies of data and thus avoids unnecessary garbage collection. 26 | Make no mistake, non-pointers work perfectly fine as well. 27 | */ 28 | package govalid 29 | -------------------------------------------------------------------------------- /v1/function.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | // ----------------------------------------------------------------------------- 4 | 5 | // Arbitrary validation function. 6 | type ValidatorFunc func(data interface{}) (path string, err error) 7 | 8 | // ----------------------------------------------------------------------------- 9 | 10 | // Construct a validator using a validation function. 11 | func Function(validatorfunc ValidatorFunc) Validator { 12 | return &functionValidator{validatorfunc} 13 | } 14 | 15 | // ----------------------------------------------------------------------------- 16 | 17 | // validator for using an arbitrary function 18 | type functionValidator struct { 19 | validatorfunc ValidatorFunc 20 | } 21 | 22 | // ----------------------------------------------------------------------------- 23 | 24 | // the actual workhorse for arbitrary function validator 25 | func (r *functionValidator) Validate(data interface{}) (string, error) { 26 | return r.validatorfunc(data) 27 | } 28 | 29 | // ----------------------------------------------------------------------------- 30 | -------------------------------------------------------------------------------- /v1/internal/reflectutil.go: -------------------------------------------------------------------------------- 1 | // This package is internal, do not use externally. 2 | package internal 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | ) 9 | 10 | // Get reflection to i. If i is a pointer, indirect it. 11 | // Fails if i is nil or i is a pointer to nil. 12 | func ReflectOrIndirect(i interface{}) (*reflect.Value, error) { 13 | value := reflect.ValueOf(i) 14 | switch value.Kind() { 15 | 16 | case reflect.Invalid: 17 | // ValueOf(nil) returns the zero Value 18 | // , its Kind method returns Invalid 19 | return nil, errors.New("") 20 | 21 | case reflect.Ptr: 22 | if value.IsNil() { 23 | // ensure not nil pointer 24 | return nil, fmt.Errorf(" %s", value.Type()) 25 | } 26 | 27 | // reflection to indirection of i 28 | value = value.Elem() 29 | return &value, nil 30 | 31 | default: 32 | // reflection to i 33 | return &value, nil 34 | } 35 | } 36 | 37 | // Get reflection to pointer i. If i is not a pointer, fabricate one. 38 | // Fails if i is nil or i is a pointer to nil. 39 | func ReflectPtrOrFabricate(i interface{}) (*reflect.Value, error) { 40 | value := reflect.ValueOf(i) 41 | switch value.Kind() { 42 | case reflect.Invalid: 43 | // ValueOf(nil) returns the zero Value 44 | // , its Kind method returns Invalid 45 | return nil, errors.New("") 46 | 47 | case reflect.Ptr: 48 | if value.IsNil() { 49 | // ensure not nil pointer 50 | return nil, fmt.Errorf("nil pointer %s", value.Type()) 51 | } 52 | 53 | // reflection to i 54 | return &value, nil 55 | 56 | default: 57 | // reflection to address of a copy of i 58 | ptr := reflect.New(value.Type()) 59 | ptr.Elem().Set(value) 60 | return &ptr, nil 61 | } 62 | } 63 | 64 | // Returns `reflect.Value` of `data` or the value it points to if it's a pointer. 65 | // Check `reflect.isValid()` to see if the value is `nil`. 66 | func DigValue(data interface{}) reflect.Value { 67 | value := reflect.ValueOf(data) 68 | if value.IsValid() { 69 | if value.Kind() == reflect.Ptr { 70 | return value.Elem() 71 | } 72 | } 73 | return value 74 | } 75 | -------------------------------------------------------------------------------- /v1/jsonv.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | // Validator validates any data type that the implementing validator supports. 4 | type Validator interface { 5 | // Validate the given data. 6 | // In the case of a failed validation, path indicates which validator failed. 7 | Validate(interface{}) (path string, err error) 8 | } 9 | -------------------------------------------------------------------------------- /v1/nil.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gima/govalid/v1/internal" 7 | ) 8 | 9 | // Constructs a validator which validates data type to be a `nil`. 10 | func Nil() Validator { 11 | return &nilValidator{} 12 | } 13 | 14 | type nilValidator struct { 15 | } 16 | 17 | func (r *nilValidator) Validate(data interface{}) (string, error) { 18 | if value := internal.DigValue(data); !value.IsValid() { 19 | return "", nil 20 | } else { 21 | return "Nil", fmt.Errorf("expected , was %v", value.Type()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /v1/number.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | ) 8 | 9 | // ----------------------------------------------------------------------------- 10 | 11 | // Number validation function. 12 | type NumberOpt func(float64) error 13 | 14 | // ----------------------------------------------------------------------------- 15 | 16 | // Construct a number validator using the specified validation functions. 17 | // Validates data type to be either any numerical type or a pointer to such 18 | // (except a complex number). 19 | func Number(opts ...NumberOpt) Validator { 20 | return &numberValidator{opts} 21 | } 22 | 23 | // ----------------------------------------------------------------------------- 24 | 25 | // Number validator function for checking minimum value. 26 | func NumMin(min float64) NumberOpt { 27 | return func(f float64) error { 28 | if f < min { 29 | fs, mins := shortFloatStr(f, min) 30 | return fmt.Errorf("number %s should >= %s", fs, mins) 31 | } 32 | return nil 33 | } 34 | } 35 | 36 | // ----------------------------------------------------------------------------- 37 | 38 | // Number validator function for checking maximum value. 39 | func NumMax(max float64) NumberOpt { 40 | return func(f float64) error { 41 | if f > max { 42 | fs, maxs := shortFloatStr(f, max) 43 | return fmt.Errorf("number %s should <= %s", fs, maxs) 44 | } 45 | return nil 46 | } 47 | } 48 | 49 | // ----------------------------------------------------------------------------- 50 | 51 | // Number validator function for checking value. 52 | func NumIs(expected float64) NumberOpt { 53 | return func(f float64) error { 54 | if f != expected { 55 | expecteds, fs := shortFloatStr(expected, f) 56 | return fmt.Errorf("expected %s, got %s", expecteds, fs) 57 | } 58 | return nil 59 | } 60 | } 61 | 62 | // ----------------------------------------------------------------------------- 63 | 64 | // validator for a number 65 | type numberValidator struct { 66 | opts []NumberOpt 67 | } 68 | 69 | // ----------------------------------------------------------------------------- 70 | 71 | var float64type = reflect.TypeOf(float64(0)) 72 | 73 | // the actual workhorse for number validator 74 | func (r *numberValidator) Validate(data interface{}) (string, error) { 75 | value := reflect.ValueOf(data) 76 | 77 | switch value.Kind() { 78 | case reflect.Invalid: 79 | return "Number", fmt.Errorf("expected (*)data convertible to float64, got ") 80 | 81 | case reflect.Ptr: 82 | if value.IsNil() { 83 | return "Number", fmt.Errorf("expected (*)data convertible to float64, got %s", value.Type()) 84 | } 85 | value = value.Elem() 86 | } 87 | 88 | if value.Type().ConvertibleTo(float64type) { 89 | value = value.Convert(float64type) 90 | } else { 91 | return "Number", fmt.Errorf("expected (*)data convertible to float64, got %s", value.Type()) 92 | } 93 | 94 | v := value.Interface().(float64) 95 | 96 | for _, o := range r.opts { 97 | if err := o(v); err != nil { 98 | return "Number", err 99 | } 100 | } 101 | 102 | return "", nil 103 | } 104 | 105 | // ----------------------------------------------------------------------------- 106 | 107 | func shortFloatStr(a, b float64) (string, string) { 108 | return strconv.FormatFloat(a, 'f', -1, 64), 109 | strconv.FormatFloat(b, 'f', -1, 64) 110 | } 111 | 112 | // ----------------------------------------------------------------------------- 113 | -------------------------------------------------------------------------------- /v1/object.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gima/govalid/v1/internal" 6 | "reflect" 7 | ) 8 | 9 | // ----------------------------------------------------------------------------- 10 | 11 | // Object validation function. Parameter is reflection to a map. 12 | type ObjectOpt func(m *reflect.Value) (path string, err error) 13 | 14 | // ----------------------------------------------------------------------------- 15 | 16 | // Construct an object validator using the specified validation functions. 17 | // Validates data type to be either a map or a pointer to such 18 | // 19 | // Currently this validator supports only map data type. 20 | func Object(opts ...ObjectOpt) Validator { 21 | return &objectValidator{opts} 22 | } 23 | 24 | // ----------------------------------------------------------------------------- 25 | 26 | // Array validator function for validating every key (of key->value pair) with the specified validator. 27 | func ObjKeys(v Validator) ObjectOpt { 28 | return func(m *reflect.Value) (path string, _ error) { 29 | for _, key := range m.MapKeys() { 30 | k := key.Interface() 31 | if path, err := v.Validate(k); err != nil { 32 | return fmt.Sprintf("Key(%v)->%s", k, path), err 33 | } 34 | } 35 | return "", nil 36 | } 37 | } 38 | 39 | // ----------------------------------------------------------------------------- 40 | 41 | // Array validator function for validating every value (of key->value pair) with the specified validator. 42 | func ObjValues(v Validator) ObjectOpt { 43 | return func(m *reflect.Value) (path string, _ error) { 44 | for _, key := range m.MapKeys() { 45 | value := m.MapIndex(key).Interface() 46 | if path, err := v.Validate(value); err != nil { 47 | return fmt.Sprintf("Key[%v].Value->%s", key.Interface(), path), err 48 | } 49 | } 50 | return "", nil 51 | } 52 | } 53 | 54 | // ----------------------------------------------------------------------------- 55 | 56 | // Array validator function for validating a specific key's value (of key->value pair) with the specified validator. 57 | // If the map under validation doesn't have such key, nil is passed to the Validator (hint: Optional Validator). 58 | // 59 | // keys keys keys 60 | func ObjKV(key interface{}, v Validator) ObjectOpt { 61 | return func(m *reflect.Value) (path string, _ error) { 62 | var refkey reflect.Value 63 | 64 | if key == nil { 65 | refkey = reflect.Zero(m.Type().Key()) 66 | } else { 67 | refkey = reflect.ValueOf(key) 68 | } 69 | 70 | var value interface{} 71 | 72 | refval := m.MapIndex(refkey) 73 | if !refval.IsValid() { 74 | value = nil 75 | } else { 76 | value = refval.Interface() 77 | } 78 | 79 | if path, err := v.Validate(value); err != nil { 80 | return fmt.Sprintf("Key[%v].Value->%s", key, path), err 81 | } 82 | 83 | return "", nil 84 | } 85 | } 86 | 87 | // ----------------------------------------------------------------------------- 88 | 89 | // validator for an object 90 | type objectValidator struct { 91 | opts []ObjectOpt 92 | } 93 | 94 | // ----------------------------------------------------------------------------- 95 | 96 | // the actual workhorse for object validator 97 | func (r *objectValidator) Validate(data interface{}) (string, error) { 98 | 99 | value, err := internal.ReflectOrIndirect(data) 100 | if err != nil { 101 | return "Object", fmt.Errorf("expected (*)map, got %s", err) 102 | } 103 | 104 | if value.Kind() != reflect.Map { 105 | return "Object", fmt.Errorf("expected (*)map, got %s", value.Type()) 106 | } 107 | 108 | for _, o := range r.opts { 109 | if path, err := o(value); err != nil { 110 | return "Object->" + path, err 111 | } 112 | } 113 | 114 | return "", nil 115 | } 116 | -------------------------------------------------------------------------------- /v1/optional.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ----------------------------------------------------------------------------- 9 | 10 | // Construct an optional validator using the specified validator. 11 | // Validation succeeds when given nil or a pointer to nil. 12 | // 13 | // Note: json.Unmarshal() converts JSON null to Go's nil. 14 | func Optional(validator Validator) Validator { 15 | return &optionalValidator{validator} 16 | } 17 | 18 | // ----------------------------------------------------------------------------- 19 | 20 | // validator for optional 21 | type optionalValidator struct { 22 | validator Validator 23 | } 24 | 25 | // ----------------------------------------------------------------------------- 26 | 27 | // the actual workhorse for optional validator 28 | func (r *optionalValidator) Validate(data interface{}) (string, error) { 29 | 30 | if data == nil { 31 | return "", nil 32 | } 33 | 34 | typ := reflect.TypeOf(data) 35 | if typ.Kind() == reflect.Ptr && reflect.ValueOf(data).IsNil() { 36 | return "", nil 37 | } 38 | 39 | if path, err := r.validator.Validate(data); err != nil { 40 | return fmt.Sprintf("Optional->%s", path), err 41 | } 42 | 43 | return "", nil 44 | } 45 | 46 | // ----------------------------------------------------------------------------- 47 | -------------------------------------------------------------------------------- /v1/or.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ----------------------------------------------------------------------------- 8 | 9 | // Construct a logical-or validator using the specified validators. 10 | // Given no validators, this validator passes always. 11 | func Or(validators ...Validator) Validator { 12 | return &orValidator{validators} 13 | } 14 | 15 | // ----------------------------------------------------------------------------- 16 | 17 | // validator for logical-or 18 | type orValidator struct { 19 | validators []Validator 20 | } 21 | 22 | // ----------------------------------------------------------------------------- 23 | 24 | // the actual workhorse for logical-or validator 25 | func (r *orValidator) Validate(data interface{}) (string, error) { 26 | 27 | numvalidators := len(r.validators) 28 | 29 | for i, v := range r.validators { 30 | path, err := v.Validate(data) 31 | if err == nil { 32 | return "", nil 33 | } 34 | 35 | if i == numvalidators-1 { 36 | return fmt.Sprintf("Or(%d)->%s", i+1, path), err 37 | } 38 | } 39 | 40 | // never should reach this 41 | return "", nil 42 | } 43 | 44 | // ----------------------------------------------------------------------------- 45 | -------------------------------------------------------------------------------- /v1/string.go: -------------------------------------------------------------------------------- 1 | package govalid 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | ) 8 | 9 | // ----------------------------------------------------------------------------- 10 | 11 | // String validator function. 12 | type StringOpt func(*string) error 13 | 14 | // ----------------------------------------------------------------------------- 15 | 16 | // Construct a string validator using the specified validator functions. 17 | // Validates data type to be either a string or a pointer to such. 18 | func String(opts ...StringOpt) Validator { 19 | return &stringValidator{opts} 20 | } 21 | 22 | // ----------------------------------------------------------------------------- 23 | 24 | // String validator function for checking minimum string length. 25 | func StrMin(minlen int) StringOpt { 26 | return func(s *string) error { 27 | if len(*s) < minlen { 28 | return fmt.Errorf("length should >=%d, was %d", minlen, len(*s)) 29 | } 30 | return nil 31 | } 32 | } 33 | 34 | // ----------------------------------------------------------------------------- 35 | 36 | // String validator function for checking maximum string length. 37 | func StrMax(maxlen int) StringOpt { 38 | return func(s *string) error { 39 | if len(*s) > maxlen { 40 | return fmt.Errorf("length should <=%d, was %d", maxlen, len(*s)) 41 | } 42 | return nil 43 | } 44 | } 45 | 46 | // ----------------------------------------------------------------------------- 47 | 48 | // String validator function for checking string equality. 49 | func StrIs(expected string) StringOpt { 50 | return func(s *string) error { 51 | if *s != expected { 52 | return fmt.Errorf("expected %s, got %s", expected, *s) 53 | } 54 | return nil 55 | } 56 | } 57 | 58 | // ----------------------------------------------------------------------------- 59 | 60 | // String validator function for checking regular expression match of the string. 61 | // If regular expression has error, the error is returned when validation is performed. 62 | func StrRegExp(expr string) StringOpt { 63 | re, err := regexp.Compile(expr) 64 | return func(s *string) error { 65 | if err != nil { 66 | return fmt.Errorf("regexp compile error: %s", err) 67 | } 68 | 69 | if !re.MatchString(*s) { 70 | return fmt.Errorf("regexp doesn't match") 71 | } 72 | return nil 73 | } 74 | } 75 | 76 | // ----------------------------------------------------------------------------- 77 | 78 | // validator for a string 79 | type stringValidator struct { 80 | opts []StringOpt 81 | } 82 | 83 | // ----------------------------------------------------------------------------- 84 | 85 | // the actual workhorse for string validator 86 | func (r *stringValidator) Validate(data interface{}) (string, error) { 87 | var v *string 88 | 89 | switch tmp := data.(type) { 90 | case string: 91 | v = &tmp 92 | case *string: 93 | if tmp == nil { 94 | return "String", fmt.Errorf("expected (*)string, got ") 95 | } 96 | v = tmp 97 | default: 98 | return "String", fmt.Errorf("expected (*)string, was %v", reflect.TypeOf(data)) 99 | } 100 | 101 | for _, o := range r.opts { 102 | if err := o(v); err != nil { 103 | return "String", err 104 | } 105 | } 106 | 107 | return "", nil 108 | } 109 | 110 | // ----------------------------------------------------------------------------- 111 | -------------------------------------------------------------------------------- /v1/tests/and_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func TestAnd(t *testing.T) { 9 | test(t, "combination1", true, v.And(), nil) 10 | 11 | test(t, "combination2", false, v.And(v.String(v.StrMin(3)), v.String(v.StrMax(3))), "aa") 12 | test(t, "combination3", true, v.And(v.String(v.StrMin(3)), v.String(v.StrMax(3))), "aaa") 13 | test(t, "combination4", false, v.And(v.String(v.StrMin(3)), v.String(v.StrMax(3))), "aaaa") 14 | 15 | test(t, "combination5", false, v.And(v.String(v.StrMin(3)), v.String(v.StrMax(4))), "bb") 16 | test(t, "combination6", true, v.And(v.String(v.StrMin(3)), v.String(v.StrMax(4))), "bbb") 17 | test(t, "combination7", true, v.And(v.String(v.StrMin(3)), v.String(v.StrMax(4))), "bbbb") 18 | test(t, "combination8", false, v.And(v.String(v.StrMin(3)), v.String(v.StrMax(4))), "bbbbb") 19 | } 20 | -------------------------------------------------------------------------------- /v1/tests/array_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func TestArray(t *testing.T) { 9 | var np *[]interface{} 10 | var nnsp = []interface{}{1, 2, 3} 11 | var nnap = [3]interface{}{1, 2, 3} 12 | 13 | test(t, "basic slice", true, v.Array(), []interface{}{1, 2, 3}) 14 | test(t, "nil", false, v.Array(), nil) 15 | test(t, "nil slice pointer", false, v.Array(), np) 16 | test(t, "slice pointer", true, v.Array(), &nnsp) 17 | test(t, "non-array/slice", false, v.Array(), 3) 18 | 19 | test(t, "basic array ", true, v.Array(), [3]interface{}{1, 2, 3}) 20 | test(t, "nil array pointer", true, v.Array(), [3]interface{}{1, 2, 3}) 21 | test(t, "array pointer", true, v.Array(), &nnap) 22 | 23 | test(t, "int slice", true, v.Array(), []int{1, 2, 3}) 24 | 25 | test(t, "minlen1", false, v.Array(v.ArrMin(3)), []interface{}{1, 2}) 26 | test(t, "minlen2", true, v.Array(v.ArrMin(3)), []interface{}{1, 2, 3}) 27 | test(t, "minlen3", true, v.Array(v.ArrMin(3)), []interface{}{1, 2, 3, 4}) 28 | 29 | test(t, "maxlen", true, v.Array(v.ArrMax(3)), []interface{}{1, 2}) 30 | test(t, "maxlen2", true, v.Array(v.ArrMax(3)), []interface{}{1, 2, 3}) 31 | test(t, "maxlen3", false, v.Array(v.ArrMax(3)), []interface{}{1, 2, 3, 4}) 32 | 33 | test(t, "combination1", false, v.Array(v.ArrMin(3), v.ArrMax(3)), []interface{}{1, 2}) 34 | test(t, "combination2", true, v.Array(v.ArrMin(3), v.ArrMax(3)), []interface{}{1, 2, 3}) 35 | test(t, "combination3", false, v.Array(v.ArrMin(3), v.ArrMax(3)), []interface{}{1, 2, 3, 4}) 36 | 37 | test(t, "each1", true, v.Array(v.ArrEach(v.Number(v.NumMin(3)))), []interface{}{}) 38 | test(t, "each2", false, v.Array(v.ArrEach(v.Number(v.NumMin(3)))), []interface{}{2, 3}) 39 | test(t, "each3", true, v.Array(v.ArrEach(v.Number(v.NumMin(3)))), []interface{}{3, 4, 5}) 40 | } 41 | -------------------------------------------------------------------------------- /v1/tests/boolean_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func TestBoolean(t *testing.T) { 9 | var np *bool 10 | var nnp bool = true 11 | 12 | test(t, "type check: true", true, v.Boolean(), true) 13 | test(t, "type check: false", true, v.Boolean(), false) 14 | test(t, "type check: non-boolean", false, v.Boolean(), nil) 15 | 16 | test(t, "nil bool pointer", false, v.Boolean(), np) 17 | test(t, "non-nil bool pointer", true, v.Boolean(), &nnp) 18 | 19 | test(t, "should true value", true, v.Boolean(v.BoolIs(true)), true) 20 | test(t, "!should true value", false, v.Boolean(v.BoolIs(true)), false) 21 | 22 | test(t, "should false value", true, v.Boolean(v.BoolIs(false)), false) 23 | test(t, "!should false value", false, v.Boolean(v.BoolIs(false)), true) 24 | } 25 | -------------------------------------------------------------------------------- /v1/tests/common_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func test(t *testing.T, title string, expectPass bool, validator v.Validator, data interface{}) { 9 | path, err := validator.Validate(data) 10 | 11 | if err == nil { 12 | 13 | if expectPass { 14 | return 15 | } else { 16 | t.Fatalf("'%s' failed (%s). Path: %s", title, "nil error, but error expected", path) 17 | } 18 | 19 | } else if err != nil { 20 | 21 | if !expectPass { 22 | return 23 | } else { 24 | t.Fatalf("'%s' failed (%s). Path: %s", title, err, path) 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /v1/tests/example_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | v "github.com/gima/govalid/v1" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestExample(t *testing.T) { 12 | 13 | // set up raw json data 14 | rawJson := []byte(` 15 | { 16 | "status": true, 17 | "data": { 18 | "token": "CAFED00D", 19 | "debug": 69306, 20 | "items": [ 21 | { "url": "https://news.ycombinator.com/", "comment": "clickbaits" }, 22 | { "url": "http://golang.org/", "comment": "some darn gophers" }, 23 | { "url": "http://www.kickstarter.com/", "comment": "opensource projects. yeah.." } 24 | ], 25 | "ghost2": null, 26 | "meta": { 27 | "g": 1, 28 | "xyzzy": 0.25, 29 | "blöö": 0.5 30 | } 31 | } 32 | }`) 33 | 34 | // decode json 35 | var data interface{} 36 | if err := json.Unmarshal(rawJson, &data); err != nil { 37 | t.Fatal("JSON parsing failed. Err =", err) 38 | } 39 | 40 | // set up a custom validator function 41 | myValidatorFunc := func(data interface{}) (path string, err error) { 42 | path = "myValidatorFunc" 43 | 44 | validate, ok := data.(string) 45 | if !ok { 46 | return path, fmt.Errorf("expected string, got %v", reflect.TypeOf(data)) 47 | } 48 | 49 | if validate != "CAFED00D" { 50 | return path, fmt.Errorf("expected CAFED00D, got %s", validate) 51 | } 52 | 53 | return "", nil 54 | } 55 | 56 | // construct the schema which is used to validate data 57 | schema := v.Object( 58 | v.ObjKV("status", v.Boolean()), 59 | v.ObjKV("data", v.Object( 60 | v.ObjKV("token", v.Function(myValidatorFunc)), 61 | v.ObjKV("debug", v.Number(v.NumMin(1), v.NumMax(99999))), 62 | v.ObjKV("items", v.Array(v.ArrEach(v.Object( 63 | v.ObjKV("url", v.String(v.StrMin(1))), 64 | v.ObjKV("comment", v.Optional(v.String())), 65 | )))), 66 | v.ObjKV("ghost", v.Optional(v.String())), 67 | v.ObjKV("ghost2", v.Optional(v.String())), 68 | v.ObjKV("meta", v.Object( 69 | v.ObjKeys(v.String()), 70 | v.ObjValues(v.Or(v.Number(v.NumMin(.01), v.NumMax(1.1)), v.String())), 71 | )), 72 | )), 73 | ) 74 | 75 | // do the actual validation 76 | if path, err := schema.Validate(data); err == nil { 77 | t.Log("Validation passed.") 78 | } else { 79 | t.Fatalf("Validation failed at %s. Error (%s)", path, err) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /v1/tests/function_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | "fmt" 5 | v "github.com/gima/govalid/v1" 6 | "reflect" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestFunction(t *testing.T) { 12 | counter, countingValidator := createCountingValidator() 13 | 14 | test(t, "combination1", false, v.Function(dogeValidator), "cat") 15 | test(t, "combination2", true, v.Function(dogeValidator), "cate") 16 | test(t, "combination3", true, v.Function(countingValidator), "doge") 17 | test(t, "combination4", true, v.Function(countingValidator), "doge") 18 | 19 | if *counter != 2 { 20 | t.Fatalf("counting validator count should be 2, is %d", *counter) 21 | } 22 | } 23 | 24 | func dogeValidator(data interface{}) (path string, err error) { 25 | s, ok := data.(string) 26 | if !ok { 27 | return "doge-validator", fmt.Errorf("expected string, got %s", reflect.TypeOf(data)) 28 | } 29 | 30 | if !strings.HasSuffix(strings.ToLower(s), "e") { 31 | return "doge-validator", fmt.Errorf("expected string to end in 'e'") 32 | } 33 | 34 | return "", nil 35 | } 36 | 37 | func createCountingValidator() (counter *int, _ v.ValidatorFunc) { 38 | counter = new(int) 39 | return counter, func(data interface{}) (path string, err error) { 40 | *counter++ 41 | return "", nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /v1/tests/nil_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | "testing" 5 | 6 | v "github.com/gima/govalid/v1" 7 | ) 8 | 9 | func TestNil(t *testing.T) { 10 | test(t, "nil", true, v.Nil(), nil) 11 | test(t, "non-nil", false, v.Nil(), "") 12 | } 13 | -------------------------------------------------------------------------------- /v1/tests/number_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func TestNumber(t *testing.T) { 9 | var np *int 10 | var nnp int = 3 11 | 12 | test(t, "basic number", true, v.Number(), 3) 13 | test(t, "non-number", false, v.Number(), "") 14 | test(t, "nil", false, v.Number(), nil) 15 | test(t, "nil int pointer", false, v.Number(), np) 16 | test(t, "int pointer", true, v.Number(), &nnp) 17 | 18 | test(t, "numis", true, v.Number(v.NumIs(3)), 3) 19 | test(t, "!numis", false, v.Number(v.NumIs(3)), 2) 20 | 21 | test(t, "minlen1", false, v.Number(v.NumMin(3)), 2) 22 | test(t, "minlen2", true, v.Number(v.NumMin(3)), 3) 23 | test(t, "minlen2", true, v.Number(v.NumMin(3)), 4) 24 | 25 | test(t, "maxlen1", true, v.Number(v.NumMax(3)), 2) 26 | test(t, "maxlen2", true, v.Number(v.NumMax(3)), 3) 27 | test(t, "maxlen3", false, v.Number(v.NumMax(3)), 4) 28 | 29 | test(t, "combination1", false, v.Number(v.NumMin(3), v.NumMax(3)), 2) 30 | test(t, "combination2", true, v.Number(v.NumMin(3), v.NumMax(3)), 3) 31 | test(t, "combination3", false, v.Number(v.NumMin(3), v.NumMax(3)), 4) 32 | } 33 | -------------------------------------------------------------------------------- /v1/tests/object_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | type maep map[interface{}]interface{} 9 | 10 | func TestObject(t *testing.T) { 11 | var np *map[interface{}]interface{} 12 | 13 | test(t, "basic object", true, v.Object(), maep{}) 14 | test(t, "nil", false, v.Object(), nil) 15 | test(t, "nil map ptr", false, v.Object(), np) 16 | test(t, "non-object", false, v.Object(), 3) 17 | 18 | testObjKeys(t) 19 | testObjValues(t) 20 | testObjKVs(t) 21 | } 22 | 23 | func testObjKeys(t *testing.T) { 24 | counter, countingValidator := createCountingValidator() 25 | 26 | sch := v.Object( 27 | v.ObjKeys(v.String()), 28 | v.ObjKeys(v.Function(countingValidator)), 29 | ) 30 | m := maep{ 31 | "a": nil, 32 | "b": 1, 33 | "c": true, 34 | } 35 | test(t, "only string objkeys", true, sch, m) 36 | if *counter != 3 { 37 | t.Fatalf("key counter should be 3, got %d", *counter) 38 | } 39 | 40 | m = maep{ 41 | "a": nil, 42 | 1: 1, 43 | } 44 | test(t, "!only string objkeys", false, sch, m) 45 | } 46 | 47 | func testObjValues(t *testing.T) { 48 | counter, countingValidator := createCountingValidator() 49 | 50 | sch := v.Object( 51 | v.ObjValues(v.String()), 52 | v.ObjValues(v.Function(countingValidator)), 53 | ) 54 | m := maep{ 55 | nil: "1", 56 | 1: "b", 57 | true: "c", 58 | } 59 | test(t, "only string objvalues", true, sch, m) 60 | if *counter != 3 { 61 | t.Fatalf("value counter should be 3, got %d", *counter) 62 | } 63 | 64 | m = maep{ 65 | nil: "1", 66 | 1: 1, 67 | } 68 | test(t, "!only string objvalues", false, sch, m) 69 | } 70 | 71 | func testObjKVs(t *testing.T) { 72 | counter, countingValidator := createCountingValidator() 73 | 74 | sch := v.Object( 75 | v.ObjKV(nil, v.And(v.String(v.StrIs("1")), v.Function(countingValidator))), 76 | v.ObjKV("1", v.And(v.String(v.StrIs("b")), v.Function(countingValidator))), 77 | v.ObjKV(true, v.And(v.Number(v.NumIs(3)), v.Function(countingValidator))), 78 | ) 79 | m := maep{ 80 | nil: "1", 81 | "1": "b", 82 | true: 3, 83 | } 84 | test(t, "mixed objkvs", true, sch, m) 85 | if *counter != 3 { 86 | t.Fatalf("value counter should be 3, got %d", *counter) 87 | } 88 | 89 | m = maep{ 90 | nil: "1", 91 | "1": 2, 92 | true: 3, 93 | } 94 | test(t, "!mixed objkvs", false, sch, m) 95 | 96 | m = maep{ 97 | nil: "1", 98 | "1": nil, 99 | true: 3, 100 | } 101 | test(t, "!mixed objkvs (nil)", false, sch, m) 102 | } 103 | -------------------------------------------------------------------------------- /v1/tests/optional_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func TestOptional(t *testing.T) { 9 | var np *bool 10 | test(t, "string", true, v.Optional(v.String(v.StrIs("a"))), "a") 11 | test(t, "wrong string", false, v.Optional(v.String(v.StrIs("a"))), "b") 12 | 13 | test(t, "nil ptr", true, v.Optional(v.String(v.StrIs("a"))), np) 14 | } 15 | -------------------------------------------------------------------------------- /v1/tests/or_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func TestOr(t *testing.T) { 9 | test(t, "combination1", true, v.Or(v.String(v.StrIs("a")), v.String(v.StrIs("b"))), "a") 10 | test(t, "combination2", true, v.Or(v.String(v.StrIs("a")), v.String(v.StrIs("b"))), "b") 11 | test(t, "combination3", false, v.Or(v.String(v.StrIs("a")), v.String(v.StrIs("b"))), 3) 12 | 13 | test(t, "combination4", true, v.Or(v.String(v.StrIs("a")), v.String(v.StrIs("b")), v.String(v.StrIs("c"))), "b") 14 | 15 | test(t, "combination5", true, v.Or(v.String(v.StrIs("a"))), "a") 16 | test(t, "combination6", false, v.Or(v.String(v.StrIs("a"))), "b") 17 | 18 | test(t, "combination7", true, v.Or(), nil) 19 | } 20 | -------------------------------------------------------------------------------- /v1/tests/performance_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | v "github.com/gima/govalid/v1" 7 | "log" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | var ( 13 | decoded interface{} 14 | schema v.Validator 15 | ) 16 | 17 | func init() { 18 | // set up raw json data 19 | rawJson := []byte(` 20 | { 21 | "status": true, 22 | "data": { 23 | "token": "CAFED00D", 24 | "debug": 69306, 25 | "items": [ 26 | { "url": "https://news.ycombinator.com/", "comment": "clickbaits" }, 27 | { "url": "http://golang.org/", "comment": "some darn gophers" }, 28 | { "url": "http://www.kickstarter.com/", "comment": "opensource projects. yeah.." } 29 | ], 30 | "ghost2": null, 31 | "meta": { 32 | "g": 1, 33 | "xyzzy": 0.25, 34 | "blöö": 0.5 35 | } 36 | } 37 | }`) 38 | 39 | // decode json 40 | if err := json.Unmarshal(rawJson, &decoded); err != nil { 41 | log.Panic("JSON parsing failed. Err =", err) 42 | } 43 | 44 | // set up a custom validator function 45 | myValidatorFunc := func(data interface{}) (path string, err error) { 46 | path = "myValidatorFunc" 47 | 48 | validate, ok := data.(string) 49 | if !ok { 50 | return path, fmt.Errorf("expected string, got %v", reflect.TypeOf(data)) 51 | } 52 | 53 | if validate != "CAFED00D" { 54 | return path, fmt.Errorf("expected CAFED00D, got %s", validate) 55 | } 56 | 57 | return "", nil 58 | } 59 | 60 | // construct the schema which is used to validate data 61 | schema = v.Object( 62 | v.ObjKV("status", v.Boolean()), 63 | v.ObjKV("data", v.Object( 64 | v.ObjKV("token", v.Function(myValidatorFunc)), 65 | v.ObjKV("debug", v.Number(v.NumMin(1), v.NumMax(99999))), 66 | v.ObjKV("items", v.Array(v.ArrEach(v.Object( 67 | v.ObjKV("url", v.String(v.StrMin(1))), 68 | v.ObjKV("comment", v.Optional(v.String())), 69 | )))), 70 | v.ObjKV("ghost", v.Optional(v.String())), 71 | v.ObjKV("ghost2", v.Optional(v.String())), 72 | v.ObjKV("meta", v.Object( 73 | v.ObjKeys(v.String()), 74 | v.ObjValues(v.Or(v.Number(v.NumMin(.01), v.NumMax(1.1)), v.String())), 75 | )), 76 | )), 77 | ) 78 | } 79 | 80 | func BenchmarkObject(b *testing.B) { 81 | for i := 0; i < b.N; i++ { 82 | // do the actual validation 83 | if path, err := schema.Validate(decoded); err != nil { 84 | b.Fatalf("Benchmark N = %d. Failed (%s). Path: %s", b.N, err, path) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /v1/tests/reflectutil_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | "github.com/gima/govalid/v1/internal" 5 | "github.com/stretchr/testify/require" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | var ( 11 | s = "asd" 12 | np *string 13 | ) 14 | 15 | func TestReflectOrIndirect(t *testing.T) { 16 | _, err := internal.ReflectOrIndirect(nil) 17 | require.Error(t, err, "@nil") 18 | 19 | v, err := internal.ReflectOrIndirect(s) 20 | require.NoError(t, err, "@value") 21 | require.Equal(t, reflect.String, v.Kind(), "value kind") 22 | require.Equal(t, s, v.Interface(), "value") 23 | 24 | v, err = internal.ReflectOrIndirect(&s) 25 | require.NoError(t, err, "@ptr") 26 | require.Equal(t, reflect.String, v.Kind(), "ptr kind") 27 | require.Equal(t, s, v.Interface(), "ptr") 28 | 29 | _, err = internal.ReflectOrIndirect(np) 30 | require.Error(t, err, "@nil ptr") 31 | } 32 | 33 | func TestReflectPtrOrFabricate(t *testing.T) { 34 | _, err := internal.ReflectPtrOrFabricate(nil) 35 | require.Error(t, err, "@nil") 36 | 37 | v, err := internal.ReflectPtrOrFabricate(s) 38 | require.NoError(t, err, "@value") 39 | require.Equal(t, reflect.Ptr, v.Kind(), "value kind: "+v.Kind().String()) 40 | require.Equal(t, s, v.Elem().Interface(), "value") 41 | 42 | v, err = internal.ReflectPtrOrFabricate(np) 43 | require.Error(t, err, "@nil ptr") 44 | } 45 | 46 | func TestDigValue(t *testing.T) { 47 | func() { 48 | const id = "nil" 49 | v := internal.DigValue(nil) 50 | require.NotNil(t, v, id) 51 | require.False(t, v.IsValid(), id+" -> isvalid") 52 | }() 53 | 54 | func() { 55 | const id = "string" 56 | var s string = "asd" 57 | v := internal.DigValue(s) 58 | require.NotNil(t, v, id) 59 | require.True(t, v.IsValid(), id+" -> isvalid") 60 | require.Equal(t, reflect.String, v.Kind(), id+" -> kind==string") 61 | require.Equal(t, s, v.Interface(), id+" -> value==given") 62 | }() 63 | 64 | func() { 65 | const id = "nil *string" 66 | var ps *string = nil 67 | v := internal.DigValue(ps) 68 | require.NotNil(t, v, id) 69 | require.False(t, v.IsValid(), id+" -> isvalid") 70 | }() 71 | 72 | func() { 73 | const id = "non-nil **string to nil *string" 74 | var ( 75 | ps *string = nil 76 | pps **string = &ps 77 | ) 78 | v := internal.DigValue(pps) 79 | require.NotNil(t, v, id) 80 | require.True(t, v.IsValid(), id+" -> isvalid") 81 | require.Equal(t, reflect.Ptr, v.Type().Kind(), id+" -> type -> kind==ptr") 82 | require.Equal(t, reflect.String, v.Type().Elem().Kind(), id+" -> type -> kind==string") 83 | require.False(t, v.Elem().IsValid(), id+" -> elem -> isvalid") 84 | }() 85 | 86 | func() { 87 | const id = "non-nil **string to non-nil *string" 88 | var ( 89 | s = "asd" 90 | ps *string = &s 91 | pps **string = &ps 92 | ) 93 | v := internal.DigValue(pps) 94 | require.NotNil(t, v, id) 95 | require.True(t, v.IsValid(), id+" -> isvalid") 96 | require.Equal(t, reflect.Ptr, v.Type().Kind(), id+" -> type -> kind==ptr") 97 | require.Equal(t, reflect.String, v.Type().Elem().Kind(), id+" -> type -> kind==string") 98 | require.True(t, v.Elem().IsValid(), id+" -> elem -> isvalid") 99 | require.Equal(t, s, v.Elem().Interface(), id+" -> elem -> value==given") 100 | }() 101 | } 102 | -------------------------------------------------------------------------------- /v1/tests/string_test.go: -------------------------------------------------------------------------------- 1 | package govalid_test 2 | 3 | import ( 4 | v "github.com/gima/govalid/v1" 5 | "testing" 6 | ) 7 | 8 | func TestString(t *testing.T) { 9 | var np *string 10 | var nnp string = "a" 11 | 12 | test(t, "non-string", false, v.String(), 3) 13 | test(t, "basic string", true, v.String(), "") 14 | test(t, "nil", false, v.String(), nil) 15 | test(t, "nil string pointer", false, v.String(), np) 16 | test(t, "string pointer", true, v.String(), &nnp) 17 | 18 | test(t, "equals", true, v.String(v.StrIs("abc")), "abc") 19 | test(t, "!equals", false, v.String(v.StrIs("abc")), "abd") 20 | 21 | test(t, "minlen1", false, v.String(v.StrMin(3)), "aa") 22 | test(t, "minlen2", true, v.String(v.StrMin(3)), "aaa") 23 | test(t, "minlen3", true, v.String(v.StrMin(3)), "aaaa") 24 | 25 | test(t, "maxlen1", true, v.String(v.StrMax(4)), "aaa") 26 | test(t, "maxlen2", true, v.String(v.StrMax(4)), "aaaa") 27 | test(t, "maxlen3", false, v.String(v.StrMax(4)), "aaaaa") 28 | 29 | test(t, "regexp1", true, v.String(v.StrRegExp("^.{3}$")), "bbb") 30 | test(t, "regexp2", false, v.String(v.StrRegExp("^.{3}$")), "bbbb") 31 | test(t, "regexp3", false, v.String(v.StrRegExp("[")), "c") 32 | 33 | test(t, "combination1", false, v.String(v.StrMin(3), v.StrMax(3)), "cc") 34 | test(t, "combination2", true, v.String(v.StrMin(3), v.StrMax(3)), "ccc") 35 | test(t, "combination1", false, v.String(v.StrMin(3), v.StrMax(3)), "cccc") 36 | 37 | } 38 | --------------------------------------------------------------------------------