├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── builtins.go ├── doc.go ├── examplevalidate_test.go ├── go.mod ├── go.sum ├── validator.go └── validator_test.go /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.10" 4 | - 1.11 5 | - 1.12 6 | - tip 7 | go_import_path: gopkg.in/validator.v2 8 | script: 9 | - go test -race -v -bench=. 10 | notifications: 11 | email: false 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Package validator 2 | 3 | Package validator implements variable validations 4 | 5 | # Installation 6 | 7 | Just use go get. 8 | 9 | ```bash 10 | go get gopkg.in/validator.v2 11 | ``` 12 | 13 | And then just import the package into your own code. 14 | 15 | ```go 16 | import ( 17 | "gopkg.in/validator.v2" 18 | ) 19 | ``` 20 | 21 | # Usage 22 | 23 | Please see http://godoc.org/gopkg.in/validator.v2 for detailed usage docs. 24 | A simple example would be. 25 | 26 | ```go 27 | type NewUserRequest struct { 28 | Username string `validate:"min=3,max=40,regexp=^[a-zA-Z]*$"` 29 | Name string `validate:"nonzero"` 30 | Age int `validate:"min=21"` 31 | Password string `validate:"min=8"` 32 | } 33 | 34 | nur := NewUserRequest{Username: "something", Age: 20} 35 | if errs := validator.Validate(nur); errs != nil { 36 | // values not valid, deal with errors here 37 | } 38 | ``` 39 | 40 | Builtin validators 41 | 42 | Here is the list of validators buildin in the package. Validators buildin 43 | will check the element pointed to if the value to check is a pointer. 44 | The `nil` pointer is treated as a valid value by validators buildin other 45 | than `nonzero`, so you should to use `nonzero` if you don't want to 46 | accept a `nil` pointer. 47 | 48 | ``` 49 | len 50 | For numeric numbers, len will simply make sure that the 51 | value is equal to the parameter given. For strings, it 52 | checks that the string length is exactly that number of 53 | characters. For slices, arrays, and maps, validates the 54 | number of items. (Usage: len=10) 55 | 56 | max 57 | For numeric numbers, max will simply make sure that the 58 | value is lesser or equal to the parameter given. For strings, 59 | it checks that the string length is at most that number of 60 | characters. For slices, arrays, and maps, validates the 61 | number of items. (Usage: max=10) 62 | 63 | min 64 | For numeric numbers, min will simply make sure that the value 65 | is greater or equal to the parameter given. For strings, it 66 | checks that the string length is at least that number of 67 | characters. For slices, arrays, and maps, validates the 68 | number of items. (Usage: min=10) 69 | 70 | nonzero 71 | This validates that the value is not zero. The appropriate 72 | zero value is given by the Go spec (e.g. for int it's 0, for 73 | string it's "", for pointers is nil, etc.) For structs, it 74 | will not check to see if the struct itself has all zero 75 | values, instead use a pointer or put nonzero on the struct's 76 | keys that you care about. For pointers, the pointer's value 77 | is used to test for nonzero in addition to the pointer itself 78 | not being nil. To just check for not being nil, use `nonnil`. 79 | (Usage: nonzero) 80 | 81 | regexp 82 | Only valid for string types, it will validate that the 83 | value matches the regular expression provided as parameter. 84 | Commas need to be escaped with 2 backslashes `\\`. 85 | (Usage: regexp=^a.*b$) 86 | 87 | nonnil 88 | Validates that the given value is not nil. (Usage: nonnil) 89 | ``` 90 | 91 | Custom validators 92 | 93 | It is possible to define custom validators by using SetValidationFunc. 94 | First, one needs to create a validation function. 95 | 96 | ```go 97 | // Very simple validator 98 | func notZZ(v interface{}, param string) error { 99 | st := reflect.ValueOf(v) 100 | if st.Kind() != reflect.String { 101 | return errors.New("notZZ only validates strings") 102 | } 103 | if st.String() == "ZZ" { 104 | return errors.New("value cannot be ZZ") 105 | } 106 | return nil 107 | } 108 | ``` 109 | 110 | Then one needs to add it to the list of validators and give it a "tag" 111 | name. 112 | 113 | ```go 114 | validator.SetValidationFunc("notzz", notZZ) 115 | ``` 116 | 117 | Then it is possible to use the notzz validation tag. This will print 118 | "Field A error: value cannot be ZZ" 119 | 120 | ```go 121 | type T struct { 122 | A string `validate:"nonzero,notzz"` 123 | } 124 | t := T{"ZZ"} 125 | if errs := validator.Validate(t); errs != nil { 126 | fmt.Printf("Field A error: %s\n", errs["A"][0]) 127 | } 128 | ``` 129 | 130 | You can also have multiple sets of validator rules with SetTag(). 131 | 132 | ```go 133 | type T struct { 134 | A int `foo:"nonzero" bar:"min=10"` 135 | } 136 | t := T{5} 137 | SetTag("foo") 138 | validator.Validate(t) // valid as it's nonzero 139 | SetTag("bar") 140 | validator.Validate(t) // invalid as it's less than 10 141 | ``` 142 | 143 | SetTag is probably better used with multiple validators. 144 | 145 | ```go 146 | fooValidator := validator.NewValidator() 147 | fooValidator.SetTag("foo") 148 | barValidator := validator.NewValidator() 149 | barValidator.SetTag("bar") 150 | fooValidator.Validate(t) 151 | barValidator.Validate(t) 152 | ``` 153 | 154 | This keeps the default validator's tag clean. Again, please refer to 155 | godocs for a lot of more examples and different uses. 156 | 157 | # Pull requests policy 158 | 159 | tl;dr. Contributions are welcome. 160 | 161 | The repository is organized in version branches. Pull requests to, say, the 162 | `v2` branch that break API compatibility will not be accepted. It is okay to 163 | break the API in master, _not in the branches_. 164 | 165 | As for validation functions, the preference is to keep the main code simple 166 | and add most new functions to the validator-contrib repository. 167 | 168 | https://github.com/go-validator/validator-contrib 169 | 170 | For improvements and/or fixes to the builtin validation functions, please 171 | make sure the behaviour will not break existing functionality in the branches. 172 | If you see a case where the functionality of the builtin will change 173 | significantly, please send a pull request against `master`. We can discuss then 174 | whether the changes should be incorporated in the version branches as well. 175 | 176 | # License 177 | 178 | Copyright 2014 Roberto Teixeira 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /builtins.go: -------------------------------------------------------------------------------- 1 | // Package validator implements value validations 2 | // 3 | // Copyright 2014 Roberto Teixeira 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package validator 18 | 19 | import ( 20 | "reflect" 21 | "regexp" 22 | "strconv" 23 | "unicode/utf8" 24 | ) 25 | 26 | // nonzero tests whether a variable value non-zero 27 | // as defined by the golang spec. 28 | func nonzero(v interface{}, param string) error { 29 | st := reflect.ValueOf(v) 30 | var valid bool 31 | switch st.Kind() { 32 | case reflect.String: 33 | valid = utf8.RuneCountInString(st.String()) != 0 34 | case reflect.Ptr, reflect.Interface: 35 | valid = !st.IsNil() 36 | case reflect.Slice, reflect.Map, reflect.Array: 37 | valid = st.Len() != 0 38 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 39 | valid = st.Int() != 0 40 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 41 | valid = st.Uint() != 0 42 | case reflect.Float32, reflect.Float64: 43 | valid = st.Float() != 0 44 | case reflect.Bool: 45 | valid = st.Bool() 46 | case reflect.Invalid: 47 | valid = false // always invalid 48 | case reflect.Struct: 49 | valid = true // always valid since only nil pointers are empty 50 | default: 51 | return ErrUnsupported 52 | } 53 | 54 | if !valid { 55 | return ErrZeroValue 56 | } 57 | return nil 58 | } 59 | 60 | // length tests whether a variable's length is equal to a given 61 | // value. For strings it tests the number of characters whereas 62 | // for maps and slices it tests the number of items. 63 | func length(v interface{}, param string) error { 64 | st := reflect.ValueOf(v) 65 | var valid bool 66 | if st.Kind() == reflect.Ptr { 67 | if st.IsNil() { 68 | return nil 69 | } 70 | st = st.Elem() 71 | } 72 | switch st.Kind() { 73 | case reflect.String: 74 | p, err := asInt(param) 75 | if err != nil { 76 | return ErrBadParameter 77 | } 78 | valid = int64(utf8.RuneCountInString(st.String())) == p 79 | case reflect.Slice, reflect.Map, reflect.Array: 80 | p, err := asInt(param) 81 | if err != nil { 82 | return ErrBadParameter 83 | } 84 | valid = int64(st.Len()) == p 85 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 86 | p, err := asInt(param) 87 | if err != nil { 88 | return ErrBadParameter 89 | } 90 | valid = st.Int() == p 91 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 92 | p, err := asUint(param) 93 | if err != nil { 94 | return ErrBadParameter 95 | } 96 | valid = st.Uint() == p 97 | case reflect.Float32, reflect.Float64: 98 | p, err := asFloat(param) 99 | if err != nil { 100 | return ErrBadParameter 101 | } 102 | valid = st.Float() == p 103 | default: 104 | return ErrUnsupported 105 | } 106 | if !valid { 107 | return ErrLen 108 | } 109 | return nil 110 | } 111 | 112 | // min tests whether a variable value is larger or equal to a given 113 | // number. For number types, it's a simple lesser-than test; for 114 | // strings it tests the number of characters whereas for maps 115 | // and slices it tests the number of items. 116 | func min(v interface{}, param string) error { 117 | st := reflect.ValueOf(v) 118 | invalid := false 119 | if st.Kind() == reflect.Ptr { 120 | if st.IsNil() { 121 | return nil 122 | } 123 | st = st.Elem() 124 | } 125 | switch st.Kind() { 126 | case reflect.String: 127 | p, err := asInt(param) 128 | if err != nil { 129 | return ErrBadParameter 130 | } 131 | invalid = int64(utf8.RuneCountInString(st.String())) < p 132 | case reflect.Slice, reflect.Map, reflect.Array: 133 | p, err := asInt(param) 134 | if err != nil { 135 | return ErrBadParameter 136 | } 137 | invalid = int64(st.Len()) < p 138 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 139 | p, err := asInt(param) 140 | if err != nil { 141 | return ErrBadParameter 142 | } 143 | invalid = st.Int() < p 144 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 145 | p, err := asUint(param) 146 | if err != nil { 147 | return ErrBadParameter 148 | } 149 | invalid = st.Uint() < p 150 | case reflect.Float32, reflect.Float64: 151 | p, err := asFloat(param) 152 | if err != nil { 153 | return ErrBadParameter 154 | } 155 | invalid = st.Float() < p 156 | default: 157 | return ErrUnsupported 158 | } 159 | if invalid { 160 | return ErrMin 161 | } 162 | return nil 163 | } 164 | 165 | // max tests whether a variable value is lesser than a given 166 | // value. For numbers, it's a simple lesser-than test; for 167 | // strings it tests the number of characters whereas for maps 168 | // and slices it tests the number of items. 169 | func max(v interface{}, param string) error { 170 | st := reflect.ValueOf(v) 171 | var invalid bool 172 | if st.Kind() == reflect.Ptr { 173 | if st.IsNil() { 174 | return nil 175 | } 176 | st = st.Elem() 177 | } 178 | switch st.Kind() { 179 | case reflect.String: 180 | p, err := asInt(param) 181 | if err != nil { 182 | return ErrBadParameter 183 | } 184 | invalid = int64(utf8.RuneCountInString(st.String())) > p 185 | case reflect.Slice, reflect.Map, reflect.Array: 186 | p, err := asInt(param) 187 | if err != nil { 188 | return ErrBadParameter 189 | } 190 | invalid = int64(st.Len()) > p 191 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 192 | p, err := asInt(param) 193 | if err != nil { 194 | return ErrBadParameter 195 | } 196 | invalid = st.Int() > p 197 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 198 | p, err := asUint(param) 199 | if err != nil { 200 | return ErrBadParameter 201 | } 202 | invalid = st.Uint() > p 203 | case reflect.Float32, reflect.Float64: 204 | p, err := asFloat(param) 205 | if err != nil { 206 | return ErrBadParameter 207 | } 208 | invalid = st.Float() > p 209 | default: 210 | return ErrUnsupported 211 | } 212 | if invalid { 213 | return ErrMax 214 | } 215 | return nil 216 | } 217 | 218 | // regex is the builtin validation function that checks 219 | // whether the string variable matches a regular expression 220 | func regex(v interface{}, param string) error { 221 | rv := reflect.ValueOf(v) 222 | if rv.Kind() == reflect.Ptr { 223 | if rv.IsNil() { 224 | return nil 225 | } 226 | rv = rv.Elem() 227 | } 228 | if rv.Kind() != reflect.String { 229 | return ErrUnsupported 230 | } 231 | s := rv.String() 232 | re, err := regexp.Compile(param) 233 | if err != nil { 234 | return ErrBadParameter 235 | } 236 | 237 | if !re.MatchString(s) { 238 | return ErrRegexp 239 | } 240 | return nil 241 | } 242 | 243 | // asInt returns the parameter as a int64 244 | // or panics if it can't convert 245 | func asInt(param string) (int64, error) { 246 | i, err := strconv.ParseInt(param, 0, 64) 247 | if err != nil { 248 | return 0, ErrBadParameter 249 | } 250 | return i, nil 251 | } 252 | 253 | // asUint returns the parameter as a uint64 254 | // or panics if it can't convert 255 | func asUint(param string) (uint64, error) { 256 | i, err := strconv.ParseUint(param, 0, 64) 257 | if err != nil { 258 | return 0, ErrBadParameter 259 | } 260 | return i, nil 261 | } 262 | 263 | // asFloat returns the parameter as a float64 264 | // or panics if it can't convert 265 | func asFloat(param string) (float64, error) { 266 | i, err := strconv.ParseFloat(param, 64) 267 | if err != nil { 268 | return 0.0, ErrBadParameter 269 | } 270 | return i, nil 271 | } 272 | 273 | // nonnil validates that the given pointer is not nil 274 | func nonnil(v interface{}, param string) error { 275 | st := reflect.ValueOf(v) 276 | // if we got a non-pointer then we most likely got 277 | // the value for a pointer field, either way, its not 278 | // nil 279 | switch st.Kind() { 280 | case reflect.Ptr, reflect.Interface, reflect.Func: 281 | if st.IsNil() { 282 | return ErrZeroValue 283 | } 284 | case reflect.Invalid: 285 | // the only way its invalid is if its an interface that's nil 286 | return ErrZeroValue 287 | } 288 | return nil 289 | } 290 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package validator implements value validations 2 | // 3 | // Copyright 2014 Roberto Teixeira 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | /* 18 | Package validator implements value validations based on struct tags. 19 | 20 | In code it is often necessary to validate that a given value is valid before 21 | using it for something. A typical example might be something like this. 22 | 23 | if age < 18 { 24 | return error.New("age cannot be under 18") 25 | } 26 | 27 | This is a simple enough example, but it can get significantly more complex, 28 | especially when dealing with structs. 29 | 30 | l := len(strings.Trim(s.Username)) 31 | if l < 3 || l > 40 || !regexp.MatchString("^[a-zA-Z]$", s.Username) || s.Age < 18 || s.Password { 32 | return errors.New("Invalid request") 33 | } 34 | 35 | You get the idea. Package validator allows one to define valid values as 36 | struct tags when defining a new struct type. 37 | 38 | type NewUserRequest struct { 39 | Username string `validate:"min=3,max=40,regexp=^[a-zA-Z]*$"` 40 | Name string `validate:"nonzero"` 41 | Age int `validate:"min=18"` 42 | Password string `validate:"min=8"` 43 | } 44 | 45 | Then validating a variable of type NewUserRequest becomes trivial. 46 | 47 | nur := NewUserRequest{Username: "something", ...} 48 | if errs := validator.Validate(nur); errs != nil { 49 | // do something 50 | } 51 | 52 | Builtin validator functions 53 | 54 | Here is the list of validator functions builtin in the package. 55 | 56 | len 57 | For numeric numbers, len will simply make sure that the value is 58 | equal to the parameter given. For strings, it checks that 59 | the string length is exactly that number of characters. For slices, 60 | arrays, and maps, validates the number of items. (Usage: len=10) 61 | 62 | max 63 | For numeric numbers, max will simply make sure that the value is 64 | lesser or equal to the parameter given. For strings, it checks that 65 | the string length is at most that number of characters. For slices, 66 | arrays, and maps, validates the number of items. (Usage: max=10) 67 | 68 | min 69 | For numeric numbers, min will simply make sure that the value is 70 | greater or equal to the parameter given. For strings, it checks that 71 | the string length is at least that number of characters. For slices, 72 | arrays, and maps, validates the number of items. (Usage: min=10) 73 | 74 | nonzero 75 | This validates that the value is not zero. The appropriate zero value 76 | is given by the Go spec (e.g. for int it's 0, for string it's "", for 77 | pointers is nil, etc.). For pointers, the pointer's value is used to 78 | test for nonzero in addition to the pointer itself not being nil. To 79 | just check for not being nil, use nonnil. Usage: nonzero 80 | 81 | regexp 82 | Only valid for string types, it will validate that the value matches 83 | the regular expression provided as parameter. (Usage: regexp=^a.*b$) 84 | 85 | nonnil 86 | Validates that the given value is not nil. Usage: nonnil 87 | 88 | Note that there are no tests to prevent conflicting validator parameters. For 89 | instance, these fields will never be valid. 90 | 91 | ... 92 | A int `validate:"max=0,min=1"` 93 | B string `validate:"len=10,regexp=^$" 94 | ... 95 | 96 | Custom validation functions 97 | 98 | It is possible to define custom validation functions by using SetValidationFunc. 99 | First, one needs to create a validation function. 100 | 101 | // Very simple validation func 102 | func notZZ(v interface{}, param string) error { 103 | st := reflect.ValueOf(v) 104 | if st.Kind() != reflect.String { 105 | return validate.ErrUnsupported 106 | } 107 | if st.String() == "ZZ" { 108 | return errors.New("value cannot be ZZ") 109 | } 110 | return nil 111 | } 112 | 113 | Then one needs to add it to the list of validation funcs and give it a "tag" name. 114 | 115 | validate.SetValidationFunc("notzz", notZZ) 116 | 117 | Then it is possible to use the notzz validation tag. This will print 118 | "Field A error: value cannot be ZZ" 119 | 120 | type T struct { 121 | A string `validate:"nonzero,notzz"` 122 | } 123 | t := T{"ZZ"} 124 | if errs := validator.Validate(t); errs != nil { 125 | fmt.Printf("Field A error: %s\n", errs["A"][0]) 126 | } 127 | 128 | To use parameters, it is very similar. 129 | 130 | // Very simple validator with parameter 131 | func notSomething(v interface{}, param string) error { 132 | st := reflect.ValueOf(v) 133 | if st.Kind() != reflect.String { 134 | return validate.ErrUnsupported 135 | } 136 | if st.String() == param { 137 | return errors.New("value cannot be " + param) 138 | } 139 | return nil 140 | } 141 | 142 | And then the code below should print "Field A error: value cannot be ABC". 143 | 144 | validator.SetValidationFunc("notsomething", notSomething) 145 | type T struct { 146 | A string `validate:"notsomething=ABC"` 147 | } 148 | t := T{"ABC"} 149 | if errs := validator.Validate(t); errs != nil { 150 | fmt.Printf("Field A error: %s\n", errs["A"][0]) 151 | } 152 | 153 | As well, it is possible to overwrite builtin validation functions. 154 | 155 | validate.SetValidationFunc("min", myMinFunc) 156 | 157 | And you can delete a validation function by setting it to nil. 158 | 159 | validate.SetValidationFunc("notzz", nil) 160 | validate.SetValidationFunc("nonzero", nil) 161 | 162 | Using a non-existing validation func in a field tag will always return 163 | false and with error validate.ErrUnknownTag. 164 | 165 | Finally, package validator also provides a helper function that can be used 166 | to validate simple variables/values. 167 | 168 | // errs: nil 169 | errs = validator.Valid(42, "min=10, max=50") 170 | 171 | // errs: [validate.ErrZeroValue] 172 | errs = validator.Valid(nil, "nonzero") 173 | 174 | // errs: [validate.ErrMin,validate.ErrMax] 175 | errs = validator.Valid("hi", "nonzero,min=3,max=2") 176 | 177 | Custom tag name 178 | 179 | In case there is a reason why one would not wish to use tag 'validate' (maybe due to 180 | a conflict with a different package), it is possible to tell the package to use 181 | a different tag. 182 | 183 | validator.SetTag("valid") 184 | 185 | Then. 186 | 187 | Type T struct { 188 | A int `valid:"min=8, max=10"` 189 | B string `valid:"nonzero"` 190 | } 191 | 192 | SetTag is permanent. The new tag name will be used until it is again changed 193 | with a new call to SetTag. A way to temporarily use a different tag exists. 194 | 195 | validator.WithTag("foo").Validate(t) 196 | validator.WithTag("bar").Validate(t) 197 | // But this will go back to using 'validate' 198 | validator.Validate(t) 199 | 200 | Multiple validators 201 | 202 | You may often need to have a different set of validation 203 | rules for different situations. In all the examples above, 204 | we only used the default validator but you could create a 205 | new one and set specific rules for it. 206 | 207 | For instance, you might use the same struct to decode incoming JSON for a REST API 208 | but your needs will change when you're using it to, say, create a new instance 209 | in storage vs. when you need to change something. 210 | 211 | type User struct { 212 | Username string `validate:"nonzero"` 213 | Name string `validate:"nonzero"` 214 | Age int `validate:"nonzero"` 215 | Password string `validate:"nonzero"` 216 | } 217 | 218 | Maybe when creating a new user, you need to make sure all values in the struct are filled, 219 | but then you use the same struct to handle incoming requests to, say, change the password, 220 | in which case you only need the Username and the Password and don't care for the others. 221 | You might use two different validators. 222 | 223 | type User struct { 224 | Username string `creating:"nonzero" chgpw:"nonzero"` 225 | Name string `creating:"nonzero"` 226 | Age int `creating:"nonzero"` 227 | Password string `creating:"nonzero" chgpw:"nonzero"` 228 | } 229 | 230 | var ( 231 | creationValidator = validator.NewValidator() 232 | chgPwValidator = validator.NewValidator() 233 | ) 234 | 235 | func init() { 236 | creationValidator.SetTag("creating") 237 | chgPwValidator.SetTag("chgpw") 238 | } 239 | 240 | ... 241 | 242 | func CreateUserHandler(w http.ResponseWriter, r *http.Request) { 243 | var u User 244 | json.NewDecoder(r.Body).Decode(&user) 245 | if errs := creationValidator.Validate(user); errs != nil { 246 | // the request did not include all of the User 247 | // struct fields, so send a http.StatusBadRequest 248 | // back or something 249 | } 250 | // create the new user 251 | } 252 | 253 | func SetNewUserPasswordHandler(w http.ResponseWriter, r *http.Request) { 254 | var u User 255 | json.NewDecoder(r.Body).Decode(&user) 256 | if errs := chgPwValidator.Validate(user); errs != nil { 257 | // the request did not Username and Password, 258 | // so send a http.StatusBadRequest 259 | // back or something 260 | } 261 | // save the new password 262 | } 263 | 264 | It is also possible to do all of that using only the default validator as long 265 | as SetTag is always called before calling validator.Validate() or you chain the 266 | with WithTag(). 267 | 268 | */ 269 | package validator 270 | -------------------------------------------------------------------------------- /examplevalidate_test.go: -------------------------------------------------------------------------------- 1 | // Package validator implements value validations 2 | // 3 | // Copyright 2014 Roberto Teixeira 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package validator_test 18 | 19 | import ( 20 | "fmt" 21 | "sort" 22 | 23 | "gopkg.in/validator.v2" 24 | ) 25 | 26 | // This example demonstrates a custom function to process template text. 27 | // It installs the strings.Title function and uses it to 28 | // Make Title Text Look Good In Our Template's Output. 29 | func ExampleValidate() { 30 | // First create a struct to be validated 31 | // according to the validator tags. 32 | type ValidateExample struct { 33 | Name string `validate:"nonzero"` 34 | Description string 35 | Age int `validate:"min=18"` 36 | Email string `validate:"regexp=^[0-9a-z]+@[0-9a-z]+(\\.[0-9a-z]+)+$"` 37 | Address struct { 38 | Street string `validate:"nonzero"` 39 | City string `validate:"nonzero"` 40 | } 41 | } 42 | 43 | // Fill in some values 44 | ve := ValidateExample{ 45 | Name: "Joe Doe", // valid as it's nonzero 46 | Description: "", // valid no validation tag exists 47 | Age: 17, // invalid as age is less than required 18 48 | } 49 | // invalid as Email won't match the regular expression 50 | ve.Email = "@not.a.valid.email" 51 | ve.Address.City = "Some City" // valid 52 | ve.Address.Street = "" // invalid 53 | 54 | err := validator.Validate(ve) 55 | if err == nil { 56 | fmt.Println("Values are valid.") 57 | } else { 58 | errs := err.(validator.ErrorMap) 59 | // See if Address was empty 60 | if errs["Address.Street"][0] == validator.ErrZeroValue { 61 | fmt.Println("Street cannot be empty.") 62 | } 63 | 64 | // Iterate through the list of fields and respective errors 65 | fmt.Println("Invalid due to fields:") 66 | 67 | // Here we have to sort the arrays to ensure map ordering does not 68 | // fail our example, typically it's ok to just range through the err 69 | // list when order is not important. 70 | var errOuts []string 71 | for f, e := range errs { 72 | errOuts = append(errOuts, fmt.Sprintf("\t - %s (%v)\n", f, e)) 73 | } 74 | 75 | // Again this part is extraneous and you should not need this in real 76 | // code. 77 | sort.Strings(errOuts) 78 | for _, str := range errOuts { 79 | fmt.Print(str) 80 | } 81 | } 82 | 83 | // Output: 84 | // Street cannot be empty. 85 | // Invalid due to fields: 86 | // - Address.Street (zero value) 87 | // - Age (less than min) 88 | // - Email (regular expression mismatch) 89 | } 90 | 91 | // This example shows how to use the Valid helper 92 | // function to validator any number of values 93 | func ExampleValid() { 94 | err := validator.Valid(42, "min=10,max=100,nonzero") 95 | fmt.Printf("42: valid=%v, errs=%v\n", err == nil, err) 96 | 97 | var ptr *int 98 | if err := validator.Valid(ptr, "nonzero"); err != nil { 99 | fmt.Println("ptr: Invalid nil pointer.") 100 | } 101 | 102 | err = validator.Valid("ABBA", "regexp=[ABC]*") 103 | fmt.Printf("ABBA: valid=%v\n", err == nil) 104 | 105 | // Output: 106 | // 42: valid=true, errs= 107 | // ptr: Invalid nil pointer. 108 | // ABBA: valid=true 109 | } 110 | 111 | // This example shows you how to change the tag name 112 | func ExampleSetTag() { 113 | type T struct { 114 | A int `foo:"nonzero" bar:"min=10"` 115 | } 116 | t := T{5} 117 | v := validator.NewValidator() 118 | v.SetTag("foo") 119 | err := v.Validate(t) 120 | fmt.Printf("foo --> valid: %v, errs: %v\n", err == nil, err) 121 | v.SetTag("bar") 122 | err = v.Validate(t) 123 | errs := err.(validator.ErrorMap) 124 | fmt.Printf("bar --> valid: %v, errs: %v\n", err == nil, errs) 125 | 126 | // Output: 127 | // foo --> valid: true, errs: 128 | // bar --> valid: false, errs: A: less than min 129 | } 130 | 131 | // This example shows you how to change the tag name 132 | func ExampleWithTag() { 133 | type T struct { 134 | A int `foo:"nonzero" bar:"min=10"` 135 | } 136 | t := T{5} 137 | err := validator.WithTag("foo").Validate(t) 138 | fmt.Printf("foo --> valid: %v, errs: %v\n", err == nil, err) 139 | err = validator.WithTag("bar").Validate(t) 140 | fmt.Printf("bar --> valid: %v, errs: %v\n", err == nil, err) 141 | 142 | // Output: 143 | // foo --> valid: true, errs: 144 | // bar --> valid: false, errs: A: less than min 145 | } 146 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gopkg.in/validator.v2 2 | 3 | go 1.18 4 | 5 | require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 6 | 7 | require ( 8 | github.com/kr/pretty v0.2.1 // indirect 9 | github.com/kr/text v0.1.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 2 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 3 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 4 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 6 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 7 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 8 | -------------------------------------------------------------------------------- /validator.go: -------------------------------------------------------------------------------- 1 | // Package validator implements value validations 2 | // 3 | // Copyright 2014 Roberto Teixeira 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package validator 18 | 19 | import ( 20 | "bytes" 21 | "errors" 22 | "fmt" 23 | "reflect" 24 | "regexp" 25 | "strings" 26 | ) 27 | 28 | // TextErr is an error that also implements the TextMarshaller interface for 29 | // serializing out to various plain text encodings. Packages creating their 30 | // own custom errors should use TextErr if they're intending to use serializing 31 | // formats like json, msgpack etc. 32 | type TextErr struct { 33 | Err error 34 | } 35 | 36 | // Error implements the error interface. 37 | func (t TextErr) Error() string { 38 | return t.Err.Error() 39 | } 40 | 41 | // MarshalText implements the TextMarshaller 42 | func (t TextErr) MarshalText() ([]byte, error) { 43 | return []byte(t.Err.Error()), nil 44 | } 45 | 46 | var ( 47 | // ErrZeroValue is the error returned when variable has zero value 48 | // and nonzero or nonnil was specified 49 | ErrZeroValue = TextErr{errors.New("zero value")} 50 | // ErrMin is the error returned when variable is less than mininum 51 | // value specified 52 | ErrMin = TextErr{errors.New("less than min")} 53 | // ErrMax is the error returned when variable is more than 54 | // maximum specified 55 | ErrMax = TextErr{errors.New("greater than max")} 56 | // ErrLen is the error returned when length is not equal to 57 | // param specified 58 | ErrLen = TextErr{errors.New("invalid length")} 59 | // ErrRegexp is the error returned when the value does not 60 | // match the provided regular expression parameter 61 | ErrRegexp = TextErr{errors.New("regular expression mismatch")} 62 | // ErrUnsupported is the error error returned when a validation rule 63 | // is used with an unsupported variable type 64 | ErrUnsupported = TextErr{errors.New("unsupported type")} 65 | // ErrBadParameter is the error returned when an invalid parameter 66 | // is provided to a validation rule (e.g. a string where an int was 67 | // expected (max=foo,len=bar) or missing a parameter when one is required (len=)) 68 | ErrBadParameter = TextErr{errors.New("bad parameter")} 69 | // ErrUnknownTag is the error returned when an unknown tag is found 70 | ErrUnknownTag = TextErr{errors.New("unknown tag")} 71 | // ErrInvalid is the error returned when variable is invalid 72 | // (normally a nil pointer) 73 | ErrInvalid = TextErr{errors.New("invalid value")} 74 | // ErrCannotValidate is the error returned when a struct is unexported 75 | ErrCannotValidate = TextErr{errors.New("cannot validate unexported struct")} 76 | ) 77 | 78 | // ErrorMap is a map which contains all errors from validating a struct. 79 | type ErrorMap map[string]ErrorArray 80 | 81 | // ErrorMap implements the Error interface so we can check error against nil. 82 | // The returned error is all existing errors with the map. 83 | func (err ErrorMap) Error() string { 84 | var b bytes.Buffer 85 | 86 | for k, errs := range err { 87 | if len(errs) > 0 { 88 | b.WriteString(fmt.Sprintf("%s: %s, ", k, errs.Error())) 89 | } 90 | } 91 | 92 | return strings.TrimSuffix(b.String(), ", ") 93 | } 94 | 95 | // ErrorArray is a slice of errors returned by the Validate function. 96 | type ErrorArray []error 97 | 98 | // ErrorArray implements the Error interface and returns all the errors comma seprated 99 | // if errors exist. 100 | func (err ErrorArray) Error() string { 101 | var b bytes.Buffer 102 | 103 | for _, errs := range err { 104 | b.WriteString(fmt.Sprintf("%s, ", errs.Error())) 105 | } 106 | 107 | errs := b.String() 108 | return strings.TrimSuffix(errs, ", ") 109 | } 110 | 111 | // ValidationFunc is a function that receives the value of a 112 | // field and a parameter used for the respective validation tag. 113 | type ValidationFunc func(v interface{}, param string) error 114 | 115 | // Validator implements a validator 116 | type Validator struct { 117 | // validationFuncs is a map of ValidationFuncs indexed 118 | // by their name. 119 | validationFuncs map[string]ValidationFunc 120 | // Tag name being used. 121 | tagName string 122 | // printJSON set to true will make errors print with the 123 | // name of their json field instead of their struct tag. 124 | // If no json tag is present the name of the struct field is used. 125 | printJSON bool 126 | } 127 | 128 | // Helper validator so users can use the 129 | // functions directly from the package 130 | var defaultValidator = NewValidator() 131 | 132 | // NewValidator creates a new Validator 133 | func NewValidator() *Validator { 134 | return &Validator{ 135 | tagName: "validate", 136 | validationFuncs: map[string]ValidationFunc{ 137 | "nonzero": nonzero, 138 | "len": length, 139 | "min": min, 140 | "max": max, 141 | "regexp": regex, 142 | "nonnil": nonnil, 143 | }, 144 | printJSON: false, 145 | } 146 | } 147 | 148 | // SetTag allows you to change the tag name used in structs 149 | func SetTag(tag string) { 150 | defaultValidator.SetTag(tag) 151 | } 152 | 153 | // SetTag allows you to change the tag name used in structs 154 | func (mv *Validator) SetTag(tag string) { 155 | mv.tagName = tag 156 | } 157 | 158 | // WithTag creates a new Validator with the new tag name. It is 159 | // useful to chain-call with Validate so we don't change the tag 160 | // name permanently: validator.WithTag("foo").Validate(t) 161 | func WithTag(tag string) *Validator { 162 | return defaultValidator.WithTag(tag) 163 | } 164 | 165 | // WithTag creates a new Validator with the new tag name. It is 166 | // useful to chain-call with Validate so we don't change the tag 167 | // name permanently: validator.WithTag("foo").Validate(t) 168 | func (mv *Validator) WithTag(tag string) *Validator { 169 | v := mv.copy() 170 | v.SetTag(tag) 171 | return v 172 | } 173 | 174 | // SetPrintJSON allows you to print errors with json tag names present in struct tags 175 | func SetPrintJSON(printJSON bool) { 176 | defaultValidator.SetPrintJSON(printJSON) 177 | } 178 | 179 | // SetPrintJSON allows you to print errors with json tag names present in struct tags 180 | func (mv *Validator) SetPrintJSON(printJSON bool) { 181 | mv.printJSON = printJSON 182 | } 183 | 184 | // WithPrintJSON creates a new Validator with printJSON set to new value. It is 185 | // useful to chain-call with Validate so we don't change the print option 186 | // permanently: validator.WithPrintJSON(true).Validate(t) 187 | func WithPrintJSON(printJSON bool) *Validator { 188 | return defaultValidator.WithPrintJSON(printJSON) 189 | } 190 | 191 | // WithPrintJSON creates a new Validator with printJSON set to new value. It is 192 | // useful to chain-call with Validate so we don't change the print option 193 | // permanently: validator.WithTag("foo").WithPrintJSON(true).Validate(t) 194 | func (mv *Validator) WithPrintJSON(printJSON bool) *Validator { 195 | v := mv.copy() 196 | v.SetPrintJSON(printJSON) 197 | return v 198 | } 199 | 200 | // Copy a validator 201 | func (mv *Validator) copy() *Validator { 202 | newFuncs := map[string]ValidationFunc{} 203 | for k, f := range mv.validationFuncs { 204 | newFuncs[k] = f 205 | } 206 | return &Validator{ 207 | tagName: mv.tagName, 208 | validationFuncs: newFuncs, 209 | printJSON: mv.printJSON, 210 | } 211 | } 212 | 213 | // SetValidationFunc sets the function to be used for a given 214 | // validation constraint. Calling this function with nil vf 215 | // is the same as removing the constraint function from the list. 216 | func SetValidationFunc(name string, vf ValidationFunc) error { 217 | return defaultValidator.SetValidationFunc(name, vf) 218 | } 219 | 220 | // SetValidationFunc sets the function to be used for a given 221 | // validation constraint. Calling this function with nil vf 222 | // is the same as removing the constraint function from the list. 223 | func (mv *Validator) SetValidationFunc(name string, vf ValidationFunc) error { 224 | if name == "" { 225 | return errors.New("name cannot be empty") 226 | } 227 | if vf == nil { 228 | delete(mv.validationFuncs, name) 229 | return nil 230 | } 231 | mv.validationFuncs[name] = vf 232 | return nil 233 | } 234 | 235 | // Validate calls the Validate method on the default validator. 236 | func Validate(v interface{}) error { 237 | return defaultValidator.Validate(v) 238 | } 239 | 240 | // Validate validates the fields of structs (included embedded structs) based on 241 | // 'validator' tags and returns errors found indexed by the field name. 242 | func (mv *Validator) Validate(v interface{}) error { 243 | m := make(ErrorMap) 244 | mv.deepValidateCollection(reflect.ValueOf(v), m, func() string { 245 | return "" 246 | }) 247 | if len(m) > 0 { 248 | return m 249 | } 250 | return nil 251 | } 252 | 253 | func (mv *Validator) validateStruct(sv reflect.Value, m ErrorMap) error { 254 | kind := sv.Kind() 255 | if (kind == reflect.Ptr || kind == reflect.Interface) && !sv.IsNil() { 256 | return mv.validateStruct(sv.Elem(), m) 257 | } 258 | if kind != reflect.Struct && kind != reflect.Interface { 259 | return ErrUnsupported 260 | } 261 | 262 | st := sv.Type() 263 | nfields := st.NumField() 264 | for i := 0; i < nfields; i++ { 265 | if err := mv.validateField(st.Field(i), sv.Field(i), m); err != nil { 266 | return err 267 | } 268 | } 269 | 270 | return nil 271 | } 272 | 273 | // validateField validates the field of fieldVal referred to by fieldDef. 274 | // If fieldDef refers to an anonymous/embedded field, 275 | // validateField will walk all of the embedded type's fields and validate them on sv. 276 | func (mv *Validator) validateField(fieldDef reflect.StructField, fieldVal reflect.Value, m ErrorMap) error { 277 | tag := fieldDef.Tag.Get(mv.tagName) 278 | if tag == "-" { 279 | return nil 280 | } 281 | // deal with pointers 282 | for (fieldVal.Kind() == reflect.Ptr || fieldVal.Kind() == reflect.Interface) && !fieldVal.IsNil() { 283 | fieldVal = fieldVal.Elem() 284 | } 285 | 286 | // ignore private structs unless Anonymous 287 | if !fieldDef.Anonymous && fieldDef.PkgPath != "" { 288 | return nil 289 | } 290 | 291 | var errs ErrorArray 292 | if tag != "" { 293 | var err error 294 | if fieldDef.PkgPath != "" { 295 | err = ErrCannotValidate 296 | } else { 297 | err = mv.validValue(fieldVal, tag) 298 | } 299 | if errarr, ok := err.(ErrorArray); ok { 300 | errs = errarr 301 | } else if err != nil { 302 | errs = ErrorArray{err} 303 | } 304 | } 305 | 306 | // no-op if field is not a struct, interface, array, slice or map 307 | fn := mv.fieldName(fieldDef) 308 | mv.deepValidateCollection(fieldVal, m, func() string { 309 | return fn 310 | }) 311 | 312 | if len(errs) > 0 { 313 | m[fn] = errs 314 | } 315 | return nil 316 | } 317 | 318 | func (mv *Validator) fieldName(fieldDef reflect.StructField) string { 319 | if mv.printJSON { 320 | if jsonTagValue, ok := fieldDef.Tag.Lookup("json"); ok { 321 | return parseName(jsonTagValue) 322 | } 323 | } 324 | return fieldDef.Name 325 | } 326 | 327 | func (mv *Validator) deepValidateCollection(f reflect.Value, m ErrorMap, fnameFn func() string) { 328 | switch f.Kind() { 329 | case reflect.Interface, reflect.Ptr: 330 | if f.IsNil() { 331 | return 332 | } 333 | mv.deepValidateCollection(f.Elem(), m, fnameFn) 334 | case reflect.Struct: 335 | subm := make(ErrorMap) 336 | err := mv.validateStruct(f, subm) 337 | parentName := fnameFn() 338 | if err != nil { 339 | m[parentName] = ErrorArray{err} 340 | } 341 | for j, k := range subm { 342 | keyName := j 343 | if parentName != "" { 344 | keyName = parentName + "." + keyName 345 | } 346 | m[keyName] = k 347 | } 348 | case reflect.Array, reflect.Slice: 349 | // we don't need to loop over every byte in a byte slice so we only end up 350 | // looping when the kind is something we care about 351 | switch f.Type().Elem().Kind() { 352 | case reflect.Struct, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Array, reflect.Slice: 353 | for i := 0; i < f.Len(); i++ { 354 | mv.deepValidateCollection(f.Index(i), m, func() string { 355 | return fmt.Sprintf("%s[%d]", fnameFn(), i) 356 | }) 357 | } 358 | } 359 | case reflect.Map: 360 | for _, key := range f.MapKeys() { 361 | mv.deepValidateCollection(key, m, func() string { 362 | return fmt.Sprintf("%s[%+v](key)", fnameFn(), key.Interface()) 363 | }) // validate the map key 364 | value := f.MapIndex(key) 365 | mv.deepValidateCollection(value, m, func() string { 366 | return fmt.Sprintf("%s[%+v](value)", fnameFn(), key.Interface()) 367 | }) 368 | } 369 | } 370 | } 371 | 372 | // Valid validates a value based on the provided 373 | // tags and returns errors found or nil. 374 | func Valid(val interface{}, tags string) error { 375 | return defaultValidator.Valid(val, tags) 376 | } 377 | 378 | // Valid validates a value based on the provided 379 | // tags and returns errors found or nil. 380 | func (mv *Validator) Valid(val interface{}, tags string) error { 381 | if tags == "-" { 382 | return nil 383 | } 384 | v := reflect.ValueOf(val) 385 | if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() { 386 | return mv.validValue(v.Elem(), tags) 387 | } 388 | if v.Kind() == reflect.Invalid { 389 | return mv.validateVar(nil, tags) 390 | } 391 | return mv.validateVar(val, tags) 392 | } 393 | 394 | // validValue is like Valid but takes a Value instead of an interface 395 | func (mv *Validator) validValue(v reflect.Value, tags string) error { 396 | if v.Kind() == reflect.Invalid { 397 | return mv.validateVar(nil, tags) 398 | } 399 | return mv.validateVar(v.Interface(), tags) 400 | } 401 | 402 | // validateVar validates one single variable 403 | func (mv *Validator) validateVar(v interface{}, tag string) error { 404 | tags, err := mv.parseTags(tag) 405 | if err != nil { 406 | // unknown tag found, give up. 407 | return err 408 | } 409 | errs := make(ErrorArray, 0, len(tags)) 410 | for _, t := range tags { 411 | if err := t.Fn(v, t.Param); err != nil { 412 | errs = append(errs, err) 413 | } 414 | } 415 | if len(errs) > 0 { 416 | return errs 417 | } 418 | return nil 419 | } 420 | 421 | // tag represents one of the tag items 422 | type tag struct { 423 | Name string // name of the tag 424 | Fn ValidationFunc // validation function to call 425 | Param string // parameter to send to the validation function 426 | } 427 | 428 | // separate by no escaped commas 429 | var sepPattern *regexp.Regexp = regexp.MustCompile(`((?:^|[^\\])(?:\\\\)*),`) 430 | 431 | func splitUnescapedComma(str string) []string { 432 | ret := []string{} 433 | indexes := sepPattern.FindAllStringIndex(str, -1) 434 | last := 0 435 | for _, is := range indexes { 436 | ret = append(ret, str[last:is[1]-1]) 437 | last = is[1] 438 | } 439 | ret = append(ret, str[last:]) 440 | return ret 441 | } 442 | 443 | // parseTags parses all individual tags found within a struct tag. 444 | func (mv *Validator) parseTags(t string) ([]tag, error) { 445 | tl := splitUnescapedComma(t) 446 | tags := make([]tag, 0, len(tl)) 447 | for _, i := range tl { 448 | i = strings.Replace(i, `\,`, ",", -1) 449 | tg := tag{} 450 | v := strings.SplitN(i, "=", 2) 451 | tg.Name = strings.Trim(v[0], " ") 452 | if tg.Name == "" { 453 | return []tag{}, ErrUnknownTag 454 | } 455 | if len(v) > 1 { 456 | tg.Param = strings.Trim(v[1], " ") 457 | } 458 | var found bool 459 | if tg.Fn, found = mv.validationFuncs[tg.Name]; !found { 460 | return []tag{}, ErrUnknownTag 461 | } 462 | tags = append(tags, tg) 463 | 464 | } 465 | return tags, nil 466 | } 467 | 468 | func parseName(tag string) string { 469 | if tag == "" { 470 | return "" 471 | } 472 | 473 | name := strings.SplitN(tag, ",", 2)[0] 474 | 475 | // if the field as be skipped in json, just return an empty string 476 | if name == "-" { 477 | return "" 478 | } 479 | return name 480 | } 481 | -------------------------------------------------------------------------------- /validator_test.go: -------------------------------------------------------------------------------- 1 | // Package validator implements value validations 2 | // 3 | // Copyright 2014 Roberto Teixeira 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package validator_test 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | "sort" 23 | "strings" 24 | "testing" 25 | 26 | . "gopkg.in/check.v1" 27 | "gopkg.in/validator.v2" 28 | ) 29 | 30 | func Test(t *testing.T) { 31 | TestingT(t) 32 | } 33 | 34 | type MySuite struct{} 35 | 36 | var _ = Suite(&MySuite{}) 37 | 38 | type Simple struct { 39 | A int `validate:"min=10"` 40 | } 41 | 42 | type I interface { 43 | Foo() string 44 | } 45 | 46 | type Impl struct { 47 | F string `validate:"len=3"` 48 | } 49 | 50 | func (i *Impl) Foo() string { 51 | return i.F 52 | } 53 | 54 | type Impl2 struct { 55 | F string `validate:"len=3"` 56 | } 57 | 58 | func (i Impl2) Foo() string { 59 | return i.F 60 | } 61 | 62 | type NestedStruct struct { 63 | A string `validate:"nonzero" json:"a"` 64 | } 65 | 66 | type TestStruct struct { 67 | A int `validate:"nonzero" json:"a"` 68 | B string `validate:"len=8,min=6,max=4"` 69 | Sub struct { 70 | A int `validate:"nonzero" json:"sub_a"` 71 | B string 72 | C float64 `validate:"nonzero,min=1" json:"c_is_a_float"` 73 | D *string `validate:"nonzero"` 74 | } 75 | D *Simple `validate:"nonzero"` 76 | E I `validate:nonzero` 77 | } 78 | 79 | type TestCompositedStruct struct { 80 | NestedStruct `json:""` 81 | OtherNested NestedStruct `json:"otherNested"` 82 | Items []NestedStruct `json:"nestedItems"` 83 | } 84 | 85 | func (ms *MySuite) TestValidate(c *C) { 86 | t := TestStruct{ 87 | A: 0, 88 | B: "12345", 89 | } 90 | t.Sub.A = 1 91 | t.Sub.B = "" 92 | t.Sub.C = 0.0 93 | t.D = &Simple{10} 94 | t.E = &Impl{"hello"} 95 | 96 | err := validator.Validate(t) 97 | c.Assert(err, NotNil) 98 | 99 | errs, ok := err.(validator.ErrorMap) 100 | c.Assert(ok, Equals, true) 101 | c.Assert(errs["A"], HasError, validator.ErrZeroValue) 102 | c.Assert(errs["B"], HasError, validator.ErrLen) 103 | c.Assert(errs["B"], HasError, validator.ErrMin) 104 | c.Assert(errs["B"], HasError, validator.ErrMax) 105 | c.Assert(errs["Sub.A"], HasLen, 0) 106 | c.Assert(errs["Sub.B"], HasLen, 0) 107 | c.Assert(errs["Sub.C"], HasLen, 2) 108 | c.Assert(errs["Sub.D"], HasError, validator.ErrZeroValue) 109 | c.Assert(errs["E.F"], HasError, validator.ErrLen) 110 | } 111 | 112 | func (ms *MySuite) TestValidSlice(c *C) { 113 | s := make([]int, 0, 10) 114 | err := validator.Valid(s, "nonzero") 115 | c.Assert(err, NotNil) 116 | errs, ok := err.(validator.ErrorArray) 117 | c.Assert(ok, Equals, true) 118 | c.Assert(errs, HasError, validator.ErrZeroValue) 119 | 120 | for i := 0; i < 10; i++ { 121 | s = append(s, i) 122 | } 123 | 124 | err = validator.Valid(s, "min=11,max=5,len=9,nonzero") 125 | c.Assert(err, NotNil) 126 | errs, ok = err.(validator.ErrorArray) 127 | c.Assert(ok, Equals, true) 128 | c.Assert(errs, HasError, validator.ErrMin) 129 | c.Assert(errs, HasError, validator.ErrMax) 130 | c.Assert(errs, HasError, validator.ErrLen) 131 | c.Assert(errs, Not(HasError), validator.ErrZeroValue) 132 | } 133 | 134 | func (ms *MySuite) TestValidMap(c *C) { 135 | m := make(map[string]string) 136 | err := validator.Valid(m, "nonzero") 137 | c.Assert(err, NotNil) 138 | errs, ok := err.(validator.ErrorArray) 139 | c.Assert(ok, Equals, true) 140 | c.Assert(errs, HasError, validator.ErrZeroValue) 141 | 142 | err = validator.Valid(m, "min=1") 143 | c.Assert(err, NotNil) 144 | errs, ok = err.(validator.ErrorArray) 145 | c.Assert(ok, Equals, true) 146 | c.Assert(errs, HasError, validator.ErrMin) 147 | 148 | m = map[string]string{"A": "a", "B": "a"} 149 | err = validator.Valid(m, "max=1") 150 | c.Assert(err, NotNil) 151 | errs, ok = err.(validator.ErrorArray) 152 | c.Assert(ok, Equals, true) 153 | c.Assert(errs, HasError, validator.ErrMax) 154 | 155 | err = validator.Valid(m, "min=2, max=5") 156 | c.Assert(err, IsNil) 157 | 158 | m = map[string]string{ 159 | "1": "a", 160 | "2": "b", 161 | "3": "c", 162 | "4": "d", 163 | "5": "e", 164 | } 165 | err = validator.Valid(m, "len=4,min=6,max=1,nonzero") 166 | c.Assert(err, NotNil) 167 | errs, ok = err.(validator.ErrorArray) 168 | c.Assert(ok, Equals, true) 169 | c.Assert(errs, HasError, validator.ErrLen) 170 | c.Assert(errs, HasError, validator.ErrMin) 171 | c.Assert(errs, HasError, validator.ErrMax) 172 | c.Assert(errs, Not(HasError), validator.ErrZeroValue) 173 | 174 | } 175 | 176 | func (ms *MySuite) TestValidFloat(c *C) { 177 | err := validator.Valid(12.34, "nonzero") 178 | c.Assert(err, IsNil) 179 | 180 | err = validator.Valid(0.0, "nonzero") 181 | c.Assert(err, NotNil) 182 | errs, ok := err.(validator.ErrorArray) 183 | c.Assert(ok, Equals, true) 184 | c.Assert(errs, HasError, validator.ErrZeroValue) 185 | } 186 | 187 | func (ms *MySuite) TestValidInt(c *C) { 188 | i := 123 189 | err := validator.Valid(i, "nonzero") 190 | c.Assert(err, IsNil) 191 | 192 | err = validator.Valid(i, "min=1") 193 | c.Assert(err, IsNil) 194 | 195 | err = validator.Valid(i, "min=124, max=122") 196 | c.Assert(err, NotNil) 197 | errs, ok := err.(validator.ErrorArray) 198 | c.Assert(ok, Equals, true) 199 | c.Assert(errs, HasError, validator.ErrMin) 200 | c.Assert(errs, HasError, validator.ErrMax) 201 | 202 | err = validator.Valid(i, "max=10") 203 | c.Assert(err, NotNil) 204 | errs, ok = err.(validator.ErrorArray) 205 | c.Assert(ok, Equals, true) 206 | c.Assert(errs, HasError, validator.ErrMax) 207 | } 208 | 209 | func (ms *MySuite) TestValidString(c *C) { 210 | s := "test1234" 211 | err := validator.Valid(s, "len=8") 212 | c.Assert(err, IsNil) 213 | 214 | err = validator.Valid(s, "len=0") 215 | c.Assert(err, NotNil) 216 | errs, ok := err.(validator.ErrorArray) 217 | c.Assert(ok, Equals, true) 218 | c.Assert(errs, HasError, validator.ErrLen) 219 | 220 | err = validator.Valid(s, "regexp=^[tes]{4}.*") 221 | c.Assert(err, IsNil) 222 | 223 | err = validator.Valid(s, "regexp=^.*[0-9]{5}$") 224 | c.Assert(err, NotNil) 225 | 226 | err = validator.Valid("", "nonzero,len=3,max=1") 227 | c.Assert(err, NotNil) 228 | errs, ok = err.(validator.ErrorArray) 229 | c.Assert(ok, Equals, true) 230 | c.Assert(errs, HasLen, 2) 231 | c.Assert(errs, HasError, validator.ErrZeroValue) 232 | c.Assert(errs, HasError, validator.ErrLen) 233 | c.Assert(errs, Not(HasError), validator.ErrMax) 234 | } 235 | 236 | func (ms *MySuite) TestValidateStructVar(c *C) { 237 | // just verifies that a the given val is a struct 238 | validator.SetValidationFunc("struct", func(val interface{}, _ string) error { 239 | v := reflect.ValueOf(val) 240 | if v.Kind() == reflect.Struct { 241 | return nil 242 | } 243 | return validator.ErrUnsupported 244 | }) 245 | 246 | type test struct { 247 | A int 248 | } 249 | err := validator.Valid(test{}, "struct") 250 | c.Assert(err, IsNil) 251 | 252 | type test2 struct { 253 | B int 254 | } 255 | type test1 struct { 256 | A test2 `validate:"struct"` 257 | } 258 | 259 | err = validator.Validate(test1{}) 260 | c.Assert(err, IsNil) 261 | 262 | type test4 struct { 263 | B int `validate:"foo"` 264 | } 265 | type test3 struct { 266 | A test4 267 | } 268 | err = validator.Validate(test3{}) 269 | errs, ok := err.(validator.ErrorMap) 270 | c.Assert(ok, Equals, true) 271 | c.Assert(errs["A.B"], HasError, validator.ErrUnknownTag) 272 | } 273 | 274 | func (ms *MySuite) TestValidatePointerVar(c *C) { 275 | // just verifies that a the given val is a struct 276 | validator.SetValidationFunc("struct", func(val interface{}, _ string) error { 277 | v := reflect.ValueOf(val) 278 | if v.Kind() == reflect.Struct { 279 | return nil 280 | } 281 | return validator.ErrUnsupported 282 | }) 283 | validator.SetValidationFunc("nil", func(val interface{}, _ string) error { 284 | v := reflect.ValueOf(val) 285 | if v.IsNil() { 286 | return nil 287 | } 288 | return validator.ErrUnsupported 289 | }) 290 | 291 | type test struct { 292 | A int 293 | } 294 | err := validator.Valid(&test{}, "struct") 295 | c.Assert(err, IsNil) 296 | 297 | type test2 struct { 298 | B int 299 | } 300 | type test1 struct { 301 | A *test2 `validate:"struct"` 302 | } 303 | 304 | err = validator.Validate(&test1{&test2{}}) 305 | c.Assert(err, IsNil) 306 | 307 | type test4 struct { 308 | B int `validate:"foo"` 309 | } 310 | type test3 struct { 311 | A test4 312 | } 313 | err = validator.Validate(&test3{}) 314 | errs, ok := err.(validator.ErrorMap) 315 | c.Assert(ok, Equals, true) 316 | c.Assert(errs["A.B"], HasError, validator.ErrUnknownTag) 317 | 318 | err = validator.Valid((*test)(nil), "nil") 319 | c.Assert(err, IsNil) 320 | 321 | type test5 struct { 322 | A *test2 `validate:"nil"` 323 | } 324 | err = validator.Validate(&test5{}) 325 | c.Assert(err, IsNil) 326 | 327 | type test6 struct { 328 | A *test2 `validate:"nonzero"` 329 | } 330 | err = validator.Validate(&test6{}) 331 | errs, ok = err.(validator.ErrorMap) 332 | c.Assert(ok, Equals, true) 333 | c.Assert(errs["A"], HasError, validator.ErrZeroValue) 334 | 335 | err = validator.Validate(&test6{&test2{}}) 336 | c.Assert(err, IsNil) 337 | 338 | type test7 struct { 339 | A *string `validate:"min=6"` 340 | B *int `validate:"len=7"` 341 | C *int `validate:"min=12"` 342 | D *int `validate:"nonzero"` 343 | E *int `validate:"nonzero"` 344 | F *int `validate:"nonnil"` 345 | G *int `validate:"nonnil"` 346 | } 347 | s := "aaa" 348 | b := 8 349 | e := 0 350 | err = validator.Validate(&test7{&s, &b, nil, nil, &e, &e, nil}) 351 | errs, ok = err.(validator.ErrorMap) 352 | c.Assert(ok, Equals, true) 353 | c.Assert(errs["A"], HasError, validator.ErrMin) 354 | c.Assert(errs["B"], HasError, validator.ErrLen) 355 | c.Assert(errs["C"], IsNil) 356 | c.Assert(errs["D"], HasError, validator.ErrZeroValue) 357 | c.Assert(errs["E"], HasError, validator.ErrZeroValue) 358 | c.Assert(errs["F"], IsNil) 359 | c.Assert(errs["G"], HasError, validator.ErrZeroValue) 360 | } 361 | 362 | func (ms *MySuite) TestValidateOmittedStructVar(c *C) { 363 | type test2 struct { 364 | B int `validate:"min=1"` 365 | } 366 | type test1 struct { 367 | A test2 `validate:"-"` 368 | } 369 | 370 | t := test1{} 371 | err := validator.Validate(t) 372 | c.Assert(err, IsNil) 373 | 374 | errs := validator.Valid(test2{}, "-") 375 | c.Assert(errs, IsNil) 376 | } 377 | 378 | func (ms *MySuite) TestUnknownTag(c *C) { 379 | type test struct { 380 | A int `validate:"foo"` 381 | } 382 | t := test{} 383 | err := validator.Validate(t) 384 | c.Assert(err, NotNil) 385 | errs, ok := err.(validator.ErrorMap) 386 | c.Assert(ok, Equals, true) 387 | c.Assert(errs, HasLen, 1) 388 | c.Assert(errs["A"], HasError, validator.ErrUnknownTag) 389 | } 390 | 391 | func (ms *MySuite) TestValidateStructWithSlice(c *C) { 392 | type test2 struct { 393 | Num int `validate:"max=2"` 394 | String string `validate:"nonzero"` 395 | } 396 | 397 | type test struct { 398 | Slices []test2 `validate:"len=1"` 399 | } 400 | 401 | t := test{ 402 | Slices: []test2{{ 403 | Num: 6, 404 | String: "foo", 405 | }}, 406 | } 407 | err := validator.Validate(t) 408 | c.Assert(err, NotNil) 409 | errs, ok := err.(validator.ErrorMap) 410 | c.Assert(ok, Equals, true) 411 | c.Assert(errs["Slices[0].Num"], HasError, validator.ErrMax) 412 | c.Assert(errs["Slices[0].String"], IsNil) // sanity check 413 | } 414 | 415 | func (ms *MySuite) TestValidateStructWithNestedSlice(c *C) { 416 | type test2 struct { 417 | Num int `validate:"max=2"` 418 | } 419 | 420 | type test struct { 421 | Slices [][]test2 422 | } 423 | 424 | t := test{ 425 | Slices: [][]test2{{{Num: 6}}}, 426 | } 427 | err := validator.Validate(t) 428 | c.Assert(err, NotNil) 429 | errs, ok := err.(validator.ErrorMap) 430 | c.Assert(ok, Equals, true) 431 | c.Assert(errs["Slices[0][0].Num"], HasError, validator.ErrMax) 432 | } 433 | 434 | func (ms *MySuite) TestValidateStructWithMap(c *C) { 435 | type test2 struct { 436 | Num int `validate:"max=2"` 437 | } 438 | 439 | type test struct { 440 | Map map[string]test2 441 | StructKeyMap map[test2]test2 442 | } 443 | 444 | t := test{ 445 | Map: map[string]test2{ 446 | "hello": {Num: 6}, 447 | }, 448 | StructKeyMap: map[test2]test2{ 449 | {Num: 3}: {Num: 1}, 450 | }, 451 | } 452 | err := validator.Validate(t) 453 | c.Assert(err, NotNil) 454 | errs, ok := err.(validator.ErrorMap) 455 | c.Assert(ok, Equals, true) 456 | 457 | c.Assert(errs["Map[hello](value).Num"], HasError, validator.ErrMax) 458 | c.Assert(errs["StructKeyMap[{Num:3}](key).Num"], HasError, validator.ErrMax) 459 | } 460 | 461 | func (ms *MySuite) TestUnsupported(c *C) { 462 | type test struct { 463 | A int `validate:"regexp=a.*b"` 464 | B float64 `validate:"regexp=.*"` 465 | } 466 | t := test{} 467 | err := validator.Validate(t) 468 | c.Assert(err, NotNil) 469 | errs, ok := err.(validator.ErrorMap) 470 | c.Assert(ok, Equals, true) 471 | c.Assert(errs, HasLen, 2) 472 | c.Assert(errs["A"], HasError, validator.ErrUnsupported) 473 | c.Assert(errs["B"], HasError, validator.ErrUnsupported) 474 | } 475 | 476 | func (ms *MySuite) TestBadParameter(c *C) { 477 | type test struct { 478 | A string `validate:"min="` 479 | B string `validate:"len=="` 480 | C string `validate:"max=foo"` 481 | } 482 | t := test{} 483 | err := validator.Validate(t) 484 | c.Assert(err, NotNil) 485 | errs, ok := err.(validator.ErrorMap) 486 | c.Assert(ok, Equals, true) 487 | c.Assert(errs, HasLen, 3) 488 | c.Assert(errs["A"], HasError, validator.ErrBadParameter) 489 | c.Assert(errs["B"], HasError, validator.ErrBadParameter) 490 | c.Assert(errs["C"], HasError, validator.ErrBadParameter) 491 | } 492 | 493 | func (ms *MySuite) TestCopy(c *C) { 494 | v := validator.NewValidator() 495 | // WithTag calls copy, so we just copy the validator with the same tag 496 | v2 := v.WithTag("validate") 497 | // now we add a custom func only to the second one, it shouldn't get added 498 | // to the first 499 | v2.SetValidationFunc("custom", func(_ interface{}, _ string) error { return nil }) 500 | type test struct { 501 | A string `validate:"custom"` 502 | } 503 | err := v2.Validate(test{}) 504 | c.Assert(err, IsNil) 505 | 506 | err = v.Validate(test{}) 507 | c.Assert(err, NotNil) 508 | errs, ok := err.(validator.ErrorMap) 509 | c.Assert(ok, Equals, true) 510 | c.Assert(errs, HasLen, 1) 511 | c.Assert(errs["A"], HasError, validator.ErrUnknownTag) 512 | } 513 | 514 | func (ms *MySuite) TestTagEscape(c *C) { 515 | type test struct { 516 | A string `validate:"min=0,regexp=^a{3\\,10}"` 517 | } 518 | t := test{"aaaa"} 519 | err := validator.Validate(t) 520 | c.Assert(err, IsNil) 521 | 522 | t2 := test{"aa"} 523 | err = validator.Validate(t2) 524 | c.Assert(err, NotNil) 525 | errs, ok := err.(validator.ErrorMap) 526 | c.Assert(ok, Equals, true) 527 | c.Assert(errs["A"], HasError, validator.ErrRegexp) 528 | } 529 | 530 | func (ms *MySuite) TestEmbeddedFields(c *C) { 531 | type baseTest struct { 532 | A string `validate:"min=1"` 533 | } 534 | type test struct { 535 | baseTest 536 | B string `validate:"min=1"` 537 | } 538 | 539 | err := validator.Validate(test{}) 540 | c.Assert(err, NotNil) 541 | errs, ok := err.(validator.ErrorMap) 542 | c.Assert(ok, Equals, true) 543 | c.Assert(errs, HasLen, 2) 544 | c.Assert(errs["baseTest.A"], HasError, validator.ErrMin) 545 | c.Assert(errs["B"], HasError, validator.ErrMin) 546 | 547 | type test2 struct { 548 | baseTest `validate:"-"` 549 | } 550 | err = validator.Validate(test2{}) 551 | c.Assert(err, IsNil) 552 | } 553 | 554 | func (ms *MySuite) TestEmbeddedPointerFields(c *C) { 555 | type baseTest struct { 556 | A string `validate:"min=1"` 557 | } 558 | type test struct { 559 | *baseTest 560 | B string `validate:"min=1"` 561 | } 562 | 563 | err := validator.Validate(test{baseTest: &baseTest{}}) 564 | c.Assert(err, NotNil) 565 | errs, ok := err.(validator.ErrorMap) 566 | c.Assert(ok, Equals, true) 567 | c.Assert(errs, HasLen, 2) 568 | c.Assert(errs["baseTest.A"], HasError, validator.ErrMin) 569 | c.Assert(errs["B"], HasError, validator.ErrMin) 570 | } 571 | 572 | func (ms *MySuite) TestEmbeddedNilPointerFields(c *C) { 573 | type baseTest struct { 574 | A string `validate:"min=1"` 575 | } 576 | type test struct { 577 | *baseTest 578 | } 579 | 580 | err := validator.Validate(test{}) 581 | c.Assert(err, IsNil) 582 | } 583 | 584 | func (ms *MySuite) TestPrivateFields(c *C) { 585 | type test struct { 586 | b string `validate:"min=1"` 587 | } 588 | 589 | err := validator.Validate(test{ 590 | b: "", 591 | }) 592 | c.Assert(err, IsNil) 593 | } 594 | 595 | func (ms *MySuite) TestEmbeddedUnexported(c *C) { 596 | type baseTest struct { 597 | A string `validate:"min=1"` 598 | } 599 | type test struct { 600 | baseTest `validate:"nonnil"` 601 | } 602 | 603 | err := validator.Validate(test{}) 604 | c.Assert(err, NotNil) 605 | errs, ok := err.(validator.ErrorMap) 606 | c.Assert(ok, Equals, true) 607 | c.Assert(errs, HasLen, 2) 608 | c.Assert(errs["baseTest"], HasError, validator.ErrCannotValidate) 609 | c.Assert(errs["baseTest.A"], HasError, validator.ErrMin) 610 | } 611 | 612 | func (ms *MySuite) TestValidateStructWithByteSliceSlice(c *C) { 613 | type test struct { 614 | Slices [][]byte `validate:"len=1"` 615 | } 616 | 617 | t := test{ 618 | Slices: [][]byte{[]byte(``)}, 619 | } 620 | err := validator.Validate(t) 621 | c.Assert(err, IsNil) 622 | } 623 | 624 | func (ms *MySuite) TestEmbeddedInterface(c *C) { 625 | type test struct { 626 | I 627 | } 628 | 629 | err := validator.Validate(test{Impl2{"hello"}}) 630 | c.Assert(err, NotNil) 631 | errs, ok := err.(validator.ErrorMap) 632 | c.Assert(ok, Equals, true) 633 | c.Assert(errs, HasLen, 1) 634 | c.Assert(errs["I.F"], HasError, validator.ErrLen) 635 | 636 | err = validator.Validate(test{&Impl{"hello"}}) 637 | c.Assert(err, NotNil) 638 | errs, ok = err.(validator.ErrorMap) 639 | c.Assert(ok, Equals, true) 640 | c.Assert(errs, HasLen, 1) 641 | c.Assert(errs["I.F"], HasError, validator.ErrLen) 642 | 643 | err = validator.Validate(test{}) 644 | c.Assert(err, IsNil) 645 | 646 | type test2 struct { 647 | I `validate:"nonnil"` 648 | } 649 | err = validator.Validate(test2{}) 650 | c.Assert(err, NotNil) 651 | errs, ok = err.(validator.ErrorMap) 652 | c.Assert(ok, Equals, true) 653 | c.Assert(errs, HasLen, 1) 654 | c.Assert(errs["I"], HasError, validator.ErrZeroValue) 655 | } 656 | 657 | func (ms *MySuite) TestErrors(c *C) { 658 | err := validator.ErrorMap{ 659 | "foo": validator.ErrorArray{ 660 | fmt.Errorf("bar"), 661 | }, 662 | "baz": validator.ErrorArray{ 663 | fmt.Errorf("qux"), 664 | }, 665 | } 666 | sep := ", " 667 | expected := "foo: bar, baz: qux" 668 | 669 | expectedParts := strings.Split(expected, sep) 670 | sort.Strings(expectedParts) 671 | 672 | errString := err.Error() 673 | errStringParts := strings.Split(errString, sep) 674 | sort.Strings(errStringParts) 675 | 676 | c.Assert(expectedParts, DeepEquals, errStringParts) 677 | } 678 | 679 | func (ms *MySuite) TestJSONPrint(c *C) { 680 | t := TestStruct{ 681 | A: 0, 682 | } 683 | err := validator.WithPrintJSON(true).Validate(t) 684 | c.Assert(err, NotNil) 685 | errs, ok := err.(validator.ErrorMap) 686 | c.Assert(ok, Equals, true) 687 | c.Assert(errs["A"], IsNil) 688 | c.Assert(errs["a"], HasError, validator.ErrZeroValue) 689 | } 690 | 691 | func (ms *MySuite) TestPrintNestedJson(c *C) { 692 | t := TestCompositedStruct{ 693 | Items: []NestedStruct{{}}, 694 | } 695 | err := validator.WithPrintJSON(true).Validate(t) 696 | c.Assert(err, NotNil) 697 | errs, ok := err.(validator.ErrorMap) 698 | c.Assert(ok, Equals, true) 699 | c.Assert(errs["a"], HasError, validator.ErrZeroValue) 700 | c.Assert(errs["otherNested.a"], HasError, validator.ErrZeroValue) 701 | c.Assert(errs["nestedItems[0].a"], HasError, validator.ErrZeroValue) 702 | } 703 | 704 | func (ms *MySuite) TestJSONPrintOff(c *C) { 705 | t := TestStruct{ 706 | A: 0, 707 | } 708 | err := validator.WithPrintJSON(false).Validate(t) 709 | c.Assert(err, NotNil) 710 | errs, ok := err.(validator.ErrorMap) 711 | c.Assert(ok, Equals, true) 712 | c.Assert(errs["A"], HasError, validator.ErrZeroValue) 713 | c.Assert(errs["a"], IsNil) 714 | } 715 | 716 | func (ms *MySuite) TestJSONPrintNoTag(c *C) { 717 | t := TestStruct{ 718 | B: "te", 719 | } 720 | err := validator.WithPrintJSON(true).Validate(t) 721 | c.Assert(err, NotNil) 722 | errs, ok := err.(validator.ErrorMap) 723 | c.Assert(ok, Equals, true) 724 | c.Assert(errs["B"], HasError, validator.ErrLen) 725 | } 726 | 727 | func (ms *MySuite) TestValidateSlice(c *C) { 728 | type test2 struct { 729 | Num int `validate:"max=2"` 730 | String string `validate:"nonzero"` 731 | } 732 | 733 | err := validator.Validate([]test2{ 734 | { 735 | Num: 6, 736 | String: "foo", 737 | }, 738 | { 739 | Num: 1, 740 | String: "foo", 741 | }, 742 | }) 743 | c.Assert(err, NotNil) 744 | errs, ok := err.(validator.ErrorMap) 745 | c.Assert(ok, Equals, true) 746 | c.Assert(errs["[0].Num"], HasError, validator.ErrMax) 747 | c.Assert(errs["[0].String"], IsNil) // sanity check 748 | c.Assert(errs["[1].Num"], IsNil) // sanity check 749 | c.Assert(errs["[1].String"], IsNil) // sanity check 750 | } 751 | 752 | func (ms *MySuite) TestValidateMap(c *C) { 753 | type test2 struct { 754 | Num int `validate:"max=2"` 755 | String string `validate:"nonzero"` 756 | } 757 | 758 | err := validator.Validate(map[string]test2{ 759 | "first": { 760 | Num: 6, 761 | String: "foo", 762 | }, 763 | "second": { 764 | Num: 1, 765 | String: "foo", 766 | }, 767 | }) 768 | c.Assert(err, NotNil) 769 | errs, ok := err.(validator.ErrorMap) 770 | c.Assert(ok, Equals, true) 771 | c.Assert(errs["[first](value).Num"], HasError, validator.ErrMax) 772 | c.Assert(errs["[first](value).String"], IsNil) // sanity check 773 | c.Assert(errs["[second](value).Num"], IsNil) // sanity check 774 | c.Assert(errs["[second](value).String"], IsNil) // sanity check 775 | 776 | err = validator.Validate(map[test2]string{ 777 | { 778 | Num: 6, 779 | String: "foo", 780 | }: "first", 781 | { 782 | Num: 1, 783 | String: "foo", 784 | }: "second", 785 | }) 786 | c.Assert(err, NotNil) 787 | errs, ok = err.(validator.ErrorMap) 788 | c.Assert(ok, Equals, true) 789 | c.Assert(errs["[{Num:6 String:foo}](key).Num"], HasError, validator.ErrMax) 790 | c.Assert(errs["[{Num:6 String:foo}](key).String"], IsNil) // sanity check 791 | c.Assert(errs["[{Num:1 String:foo}](key).Num"], IsNil) // sanity check 792 | c.Assert(errs["[{Num:1 String:foo}](key).String"], IsNil) // sanity check 793 | } 794 | 795 | func (ms *MySuite) TestNonNilFunction(c *C) { 796 | type test struct { 797 | A func() `validate:"nonnil"` 798 | } 799 | 800 | err := validator.Validate(test{}) 801 | c.Assert(err, NotNil) 802 | errs, ok := err.(validator.ErrorMap) 803 | c.Assert(ok, Equals, true) 804 | c.Assert(errs, HasLen, 1) 805 | c.Assert(errs["A"], HasError, validator.ErrZeroValue) 806 | 807 | err = validator.Validate(test{ 808 | A: func() {}, 809 | }) 810 | c.Assert(err, IsNil) 811 | } 812 | 813 | func (ms *MySuite) TestTypeAliases(c *C) { 814 | type A string 815 | type B int64 816 | type test struct { 817 | A1 A `validate:"regexp=^[0-9]+$"` 818 | A2 *A `validate:"regexp=^[0-9]+$"` 819 | B1 B `validate:"min=10"` 820 | B2 B `validate:"max=10"` 821 | } 822 | a123 := A("123") 823 | err := validator.Validate(test{ 824 | A1: a123, 825 | A2: &a123, 826 | B1: B(11), 827 | B2: B(9), 828 | }) 829 | c.Assert(err, IsNil) 830 | 831 | abc := A("abc") 832 | err = validator.Validate(test{ 833 | A1: abc, 834 | A2: &abc, 835 | B2: B(11), 836 | }) 837 | c.Assert(err, NotNil) 838 | errs, ok := err.(validator.ErrorMap) 839 | c.Assert(ok, Equals, true) 840 | c.Assert(errs, HasLen, 4) 841 | c.Assert(errs["A1"], HasError, validator.ErrRegexp) 842 | c.Assert(errs["A2"], HasError, validator.ErrRegexp) 843 | c.Assert(errs["B1"], HasError, validator.ErrMin) 844 | c.Assert(errs["B2"], HasError, validator.ErrMax) 845 | } 846 | 847 | type hasErrorChecker struct { 848 | *CheckerInfo 849 | } 850 | 851 | func (c *hasErrorChecker) Check(params []interface{}, names []string) (bool, string) { 852 | var ( 853 | ok bool 854 | slice []error 855 | value error 856 | ) 857 | slice, ok = params[0].(validator.ErrorArray) 858 | if !ok { 859 | return false, "First parameter is not an Errorarray" 860 | } 861 | value, ok = params[1].(error) 862 | if !ok { 863 | return false, "Second parameter is not an error" 864 | } 865 | 866 | for _, v := range slice { 867 | if v == value { 868 | return true, "" 869 | } 870 | } 871 | return false, "" 872 | } 873 | 874 | func (c *hasErrorChecker) Info() *CheckerInfo { 875 | return c.CheckerInfo 876 | } 877 | 878 | var HasError = &hasErrorChecker{&CheckerInfo{Name: "HasError", Params: []string{"HasError", "expected to contain"}}} 879 | --------------------------------------------------------------------------------