├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ └── bug_report.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── workflow.yml ├── .gitignore ├── .golangci.yaml ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── _examples ├── custom-validation │ └── main.go ├── custom │ └── main.go ├── dive │ └── main.go ├── gin-upgrading-overriding │ ├── main.go │ └── v8_to_v9.go ├── http-transalations │ └── main.go ├── map-validation │ └── main.go ├── simple │ └── main.go ├── struct-level │ └── main.go ├── struct-map-rules-validation │ └── main.go ├── translations │ └── main.go └── validate_fn │ ├── enum_enumer.go │ ├── go.mod │ ├── go.sum │ └── main.go ├── baked_in.go ├── benchmarks_test.go ├── cache.go ├── country_codes.go ├── currency_codes.go ├── doc.go ├── errors.go ├── field_level.go ├── go.mod ├── go.sum ├── logo.png ├── non-standard └── validators │ ├── notblank.go │ └── notblank_test.go ├── options.go ├── postcode_regexes.go ├── regexes.go ├── struct_level.go ├── testdata ├── a.go └── music.mp3 ├── translations.go ├── translations ├── ar │ ├── ar.go │ └── ar_test.go ├── de │ ├── de.go │ └── de_test.go ├── en │ ├── en.go │ └── en_test.go ├── es │ ├── es.go │ └── es_test.go ├── fa │ ├── fa.go │ └── fa_test.go ├── fr │ ├── fr.go │ └── fr_test.go ├── id │ ├── id.go │ └── id_test.go ├── it │ ├── it.go │ └── it_test.go ├── ja │ ├── ja.go │ └── ja_test.go ├── ko │ ├── ko.go │ └── ko_test.go ├── lv │ ├── lv.go │ └── lv_test.go ├── nl │ ├── nl.go │ └── nl_test.go ├── pl │ ├── pl.go │ └── pl_test.go ├── pt │ ├── pt.go │ └── pt_test.go ├── pt_BR │ ├── pt_BR.go │ └── pt_BR_test.go ├── ru │ ├── ru.go │ └── ru_test.go ├── th │ ├── th.go │ └── th_test.go ├── tr │ ├── tr.go │ └── tr_test.go ├── uk │ ├── uk.go │ └── uk_test.go ├── vi │ ├── vi.go │ └── vi_test.go ├── zh │ ├── zh.go │ └── zh_test.go └── zh_tw │ ├── zh_tw.go │ └── zh_tw_test.go ├── util.go ├── validator.go ├── validator_instance.go └── validator_test.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @go-playground/validator-maintainers -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Quality Standard 4 | 5 | To ensure the continued stability of this package, tests are required that cover the change in order for a pull request to be merged. 6 | 7 | ## Reporting issues 8 | 9 | Please open an issue or join the gitter chat [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for any issues, questions or possible enhancements to the package. 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | description: Also tell us, what did you expect to happen? 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: version 19 | attributes: 20 | label: Version 21 | description: What version of validator are you running? 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: code 26 | attributes: 27 | label: Example Code 28 | description: Please provide a code example that demonstrates the issue 29 | render: go 30 | validations: 31 | required: true -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Fixes Or Enhances 2 | 3 | 4 | **Make sure that you've checked the boxes below before you submit PR:** 5 | - [ ] Tests exist or have been written that cover this particular change. 6 | 7 | @go-playground/validator-maintainers -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for Golang 4 | - package-ecosystem: gomod 5 | directory: "/" 6 | schedule: 7 | interval: weekly 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: github-actions 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | name: Test 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | go-version: [1.22.x,1.23.x, 1.24.x] 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Install Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: ${{ matrix.go-version }} 22 | 23 | - name: Test 24 | run: go test -race -covermode=atomic -coverprofile="profile.cov" ./... 25 | 26 | - name: Send Coverage 27 | if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.23.x' 28 | uses: shogo82148/actions-goveralls@v1 29 | with: 30 | path-to-profile: profile.cov 31 | 32 | golangci: 33 | name: lint 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: actions/setup-go@v5 38 | with: 39 | go-version: 1.24.x 40 | - name: golangci-lint 41 | uses: golangci/golangci-lint-action@v8 42 | with: 43 | version: latest 44 | -------------------------------------------------------------------------------- /.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 | bin 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | *.test 27 | *.out 28 | *.txt 29 | /**/*.DS_Store 30 | cover.html 31 | README.html 32 | .idea 33 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: all 4 | disable: 5 | - copyloopvar 6 | - cyclop 7 | - depguard 8 | - dogsled 9 | - dupl 10 | - dupword 11 | - err113 12 | - errorlint 13 | - exhaustive 14 | - exhaustruct 15 | - forbidigo 16 | - forcetypeassert 17 | - funlen 18 | - gochecknoglobals 19 | - gocognit 20 | - goconst 21 | - gocritic 22 | - gocyclo 23 | - godot 24 | - gosec 25 | - gosmopolitan 26 | - interfacebloat 27 | - intrange 28 | - ireturn 29 | - lll 30 | - maintidx 31 | - misspell 32 | - mnd 33 | - nakedret 34 | - nestif 35 | - nilnil 36 | - nlreturn 37 | - nonamedreturns 38 | - paralleltest 39 | - perfsprint 40 | - prealloc 41 | - recvcheck 42 | - revive 43 | - staticcheck 44 | - tagalign 45 | - tagliatelle 46 | - testpackage 47 | - thelper 48 | - tparallel 49 | - unparam 50 | - varnamelen 51 | - wrapcheck 52 | - wsl 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dean Karn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers Guide 2 | 3 | ### Semantic Versioning 4 | Semantic versioning as defined [here](https://semver.org) must be strictly adhered to. 5 | 6 | ### External Dependencies 7 | Any new external dependencies MUST: 8 | - Have a compatible LICENSE present. 9 | - Be actively maintained. 10 | - Be approved by @go-playground/admins 11 | 12 | ### PR Merge Requirements 13 | - Up-to-date branch. 14 | - Passing tests and linting. 15 | - CODEOWNERS approval. 16 | - Tests that cover both the Happy and Unhappy paths. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOCMD=go 2 | 3 | linters-install: 4 | @golangci-lint --version >/dev/null 2>&1 || { \ 5 | echo "installing linting tools..."; \ 6 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v2.0.2; \ 7 | } 8 | 9 | lint: linters-install 10 | golangci-lint run 11 | 12 | test: 13 | $(GOCMD) test -cover -race ./... 14 | 15 | bench: 16 | $(GOCMD) test -run=NONE -bench=. -benchmem ./... 17 | 18 | .PHONY: test lint linters-install 19 | -------------------------------------------------------------------------------- /_examples/custom-validation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | // MyStruct .. 10 | type MyStruct struct { 11 | String string `validate:"is-awesome"` 12 | } 13 | 14 | // use a single instance of Validate, it caches struct info 15 | var validate *validator.Validate 16 | 17 | func main() { 18 | 19 | validate = validator.New() 20 | validate.RegisterValidation("is-awesome", ValidateMyVal) 21 | 22 | s := MyStruct{String: "awesome"} 23 | 24 | err := validate.Struct(s) 25 | if err != nil { 26 | fmt.Printf("Err(s):\n%+v\n", err) 27 | } 28 | 29 | s.String = "not awesome" 30 | err = validate.Struct(s) 31 | if err != nil { 32 | fmt.Printf("Err(s):\n%+v\n", err) 33 | } 34 | } 35 | 36 | // ValidateMyVal implements validator.Func 37 | func ValidateMyVal(fl validator.FieldLevel) bool { 38 | return fl.Field().String() == "awesome" 39 | } 40 | -------------------------------------------------------------------------------- /_examples/custom/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/go-playground/validator/v10" 10 | ) 11 | 12 | // DbBackedUser User struct 13 | type DbBackedUser struct { 14 | Name sql.NullString `validate:"required"` 15 | Age sql.NullInt64 `validate:"required"` 16 | } 17 | 18 | // use a single instance of Validate, it caches struct info 19 | var validate *validator.Validate 20 | 21 | func main() { 22 | 23 | validate = validator.New() 24 | 25 | // register all sql.Null* types to use the ValidateValuer CustomTypeFunc 26 | validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) 27 | 28 | // build object for validation 29 | x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} 30 | 31 | err := validate.Struct(x) 32 | 33 | if err != nil { 34 | fmt.Printf("Err(s):\n%+v\n", err) 35 | } 36 | } 37 | 38 | // ValidateValuer implements validator.CustomTypeFunc 39 | func ValidateValuer(field reflect.Value) interface{} { 40 | 41 | if valuer, ok := field.Interface().(driver.Valuer); ok { 42 | 43 | val, err := valuer.Value() 44 | if err == nil { 45 | return val 46 | } 47 | // handle the error how you want 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /_examples/dive/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | // Test ... 10 | type Test struct { 11 | Array []string `validate:"required,gt=0,dive,required"` 12 | Map map[string]string `validate:"required,gt=0,dive,keys,keymax,endkeys,required,max=1000"` 13 | } 14 | 15 | // use a single instance of Validate, it caches struct info 16 | var validate *validator.Validate 17 | 18 | func main() { 19 | 20 | validate = validator.New() 21 | 22 | // registering alias so we can see the differences between 23 | // map key, value validation errors 24 | validate.RegisterAlias("keymax", "max=10") 25 | 26 | var test Test 27 | 28 | val(test) 29 | 30 | test.Array = []string{""} 31 | test.Map = map[string]string{"test > than 10": ""} 32 | val(test) 33 | } 34 | 35 | func val(test Test) { 36 | fmt.Println("testing") 37 | err := validate.Struct(test) 38 | fmt.Println(err) 39 | } 40 | -------------------------------------------------------------------------------- /_examples/gin-upgrading-overriding/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/gin-gonic/gin/binding" 4 | 5 | func main() { 6 | 7 | binding.Validator = new(defaultValidator) 8 | 9 | // regular gin logic 10 | } 11 | -------------------------------------------------------------------------------- /_examples/gin-upgrading-overriding/v8_to_v9.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | 7 | "github.com/gin-gonic/gin/binding" 8 | "github.com/go-playground/validator/v10" 9 | ) 10 | 11 | type defaultValidator struct { 12 | once sync.Once 13 | validate *validator.Validate 14 | } 15 | 16 | var _ binding.StructValidator = &defaultValidator{} 17 | 18 | func (v *defaultValidator) ValidateStruct(obj interface{}) error { 19 | 20 | if kindOfData(obj) == reflect.Struct { 21 | 22 | v.lazyinit() 23 | 24 | if err := v.validate.Struct(obj); err != nil { 25 | return err 26 | } 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (v *defaultValidator) Engine() interface{} { 33 | v.lazyinit() 34 | return v.validate 35 | } 36 | 37 | func (v *defaultValidator) lazyinit() { 38 | v.once.Do(func() { 39 | v.validate = validator.New() 40 | v.validate.SetTagName("binding") 41 | 42 | // add any custom validations etc. here 43 | }) 44 | } 45 | 46 | func kindOfData(data interface{}) reflect.Kind { 47 | 48 | value := reflect.ValueOf(data) 49 | valueType := value.Kind() 50 | 51 | if valueType == reflect.Ptr { 52 | valueType = value.Elem().Kind() 53 | } 54 | return valueType 55 | } 56 | -------------------------------------------------------------------------------- /_examples/http-transalations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/go-playground/locales/en" 10 | "github.com/go-playground/locales/zh" 11 | "github.com/go-playground/locales/zh_Hant_TW" 12 | ut "github.com/go-playground/universal-translator" 13 | "github.com/go-playground/validator/v10" 14 | en_translations "github.com/go-playground/validator/v10/translations/en" 15 | zh_translations "github.com/go-playground/validator/v10/translations/zh" 16 | zh_tw_translations "github.com/go-playground/validator/v10/translations/zh_tw" 17 | ) 18 | 19 | var uni *ut.UniversalTranslator 20 | 21 | // This example showcases how to use the Validator and UniversalTranslator with both Simplified and Traditional Chinese languages. 22 | // To run the example: 23 | // Step 1: go run _examples/http-transalations/main.go 24 | // Step 2 - Simplified Chinese: curl -d '{"first_name":"foo"}' -H "Accept-Language: zh" -H "Content-Type: application/json" -X POST http://localhost:8081/users 25 | // Step 3 - Traditional Chinese: curl -d '{"first_name":"foo"}' -H "Accept-Language: zh-Hant-TW" -H "Content-Type: application/json" -X POST http://localhost:8081/users 26 | func main() { 27 | validate := validator.New() 28 | en := en.New() 29 | uni = ut.New(en, en, zh.New(), zh_Hant_TW.New()) 30 | 31 | validate = validator.New() 32 | enTrans, _ := uni.GetTranslator("en") 33 | en_translations.RegisterDefaultTranslations(validate, enTrans) 34 | zhTrans, _ := uni.GetTranslator("zh") 35 | zh_translations.RegisterDefaultTranslations(validate, zhTrans) 36 | zhHantTrans, _ := uni.GetTranslator("zh_Hant_TW") 37 | zh_tw_translations.RegisterDefaultTranslations(validate, zhHantTrans) 38 | 39 | type User struct { 40 | FirstName string `json:"first_name" validate:"required"` 41 | LastName string `json:"last_name" validate:"required"` 42 | } 43 | 44 | http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { 45 | // ... fill user value 46 | var user User 47 | 48 | // Header Accept-Language value is en or zh 49 | trans, _ := uni.GetTranslator(strings.Replace(r.Header.Get("Accept-Language"), "-", "_", -1)) 50 | if err := validate.Struct(&user); err != nil { 51 | var errs validator.ValidationErrors 52 | var httpErrors []validator.ValidationErrorsTranslations 53 | if errors.As(err, &errs) { 54 | httpErrors = append(httpErrors, errs.Translate(trans)) 55 | } 56 | r, _ := json.Marshal(httpErrors) 57 | w.Write(r) 58 | } 59 | }) 60 | 61 | http.ListenAndServe(":8081", nil) 62 | } 63 | -------------------------------------------------------------------------------- /_examples/map-validation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-playground/validator/v10" 6 | ) 7 | 8 | var validate *validator.Validate 9 | 10 | func main() { 11 | validate = validator.New() 12 | 13 | validateMap() 14 | validateNestedMap() 15 | } 16 | 17 | func validateMap() { 18 | user := map[string]interface{}{"name": "Arshiya Kiani", "email": "zytel3301@gmail.com"} 19 | 20 | // Every rule will be applied to the item of the data that the offset of rule is pointing to. 21 | // So if you have a field "email": "omitempty,required,email", the validator will apply these 22 | // rules to offset of email in user data 23 | rules := map[string]interface{}{"name": "required,min=8,max=32", "email": "omitempty,required,email"} 24 | 25 | // ValidateMap will return map[string]error. 26 | // The offset of every item in errs is the name of invalid field and the value 27 | // is the message of error. If there was no error, ValidateMap method will 28 | // return an EMPTY map of errors, not nil. If you want to check that 29 | // if there was an error or not, you must check the length of the return value 30 | errs := validate.ValidateMap(user, rules) 31 | 32 | if len(errs) > 0 { 33 | fmt.Println(errs) 34 | // The user is invalid 35 | } 36 | 37 | // The user is valid 38 | } 39 | 40 | func validateNestedMap() { 41 | 42 | data := map[string]interface{}{ 43 | "name": "Arshiya Kiani", 44 | "email": "zytel3301@gmail.com", 45 | "details": map[string]interface{}{ 46 | "family_members": map[string]interface{}{ 47 | "father_name": "Micheal", 48 | "mother_name": "Hannah", 49 | }, 50 | "salary": "1000", 51 | "phones": []map[string]interface{}{ 52 | { 53 | "number": "11-111-1111", 54 | "remark": "home", 55 | }, 56 | { 57 | "number": "22-222-2222", 58 | "remark": "work", 59 | }, 60 | }, 61 | }, 62 | } 63 | 64 | // Rules must be set as the structure as the data itself. If you want to dive into the 65 | // map, just declare its rules as a map 66 | rules := map[string]interface{}{ 67 | "name": "min=4,max=32", 68 | "email": "required,email", 69 | "details": map[string]interface{}{ 70 | "family_members": map[string]interface{}{ 71 | "father_name": "required,min=4,max=32", 72 | "mother_name": "required,min=4,max=32", 73 | }, 74 | "salary": "number", 75 | "phones": map[string]interface{}{ 76 | "number": "required,min=4,max=32", 77 | "remark": "required,min=1,max=32", 78 | }, 79 | }, 80 | } 81 | 82 | if len(validate.ValidateMap(data, rules)) == 0 { 83 | // Data is valid 84 | } 85 | 86 | // Data is invalid 87 | } 88 | -------------------------------------------------------------------------------- /_examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/go-playground/validator/v10" 8 | ) 9 | 10 | // User contains user information 11 | type User struct { 12 | FirstName string `validate:"required"` 13 | LastName string `validate:"required"` 14 | Age uint8 `validate:"gte=0,lte=130"` 15 | Email string `validate:"required,email"` 16 | Gender string `validate:"oneof=male female prefer_not_to"` 17 | FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla' 18 | Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... 19 | } 20 | 21 | // Address houses a users address information 22 | type Address struct { 23 | Street string `validate:"required"` 24 | City string `validate:"required"` 25 | Planet string `validate:"required"` 26 | Phone string `validate:"required"` 27 | } 28 | 29 | // use a single instance of Validate, it caches struct info 30 | var validate *validator.Validate 31 | 32 | func main() { 33 | 34 | validate = validator.New(validator.WithRequiredStructEnabled()) 35 | 36 | validateStruct() 37 | validateVariable() 38 | } 39 | 40 | func validateStruct() { 41 | 42 | address := &Address{ 43 | Street: "Eavesdown Docks", 44 | Planet: "Persphone", 45 | Phone: "none", 46 | } 47 | 48 | user := &User{ 49 | FirstName: "Badger", 50 | LastName: "Smith", 51 | Age: 135, 52 | Gender: "male", 53 | Email: "Badger.Smith@gmail.com", 54 | FavouriteColor: "#000-", 55 | Addresses: []*Address{address}, 56 | } 57 | 58 | // returns nil or ValidationErrors ( []FieldError ) 59 | err := validate.Struct(user) 60 | if err != nil { 61 | 62 | // this check is only needed when your code could produce 63 | // an invalid value for validation such as interface with nil 64 | // value most including myself do not usually have code like this. 65 | var invalidValidationError *validator.InvalidValidationError 66 | if errors.As(err, &invalidValidationError) { 67 | fmt.Println(err) 68 | return 69 | } 70 | 71 | var validateErrs validator.ValidationErrors 72 | if errors.As(err, &validateErrs) { 73 | for _, e := range validateErrs { 74 | fmt.Println(e.Namespace()) 75 | fmt.Println(e.Field()) 76 | fmt.Println(e.StructNamespace()) 77 | fmt.Println(e.StructField()) 78 | fmt.Println(e.Tag()) 79 | fmt.Println(e.ActualTag()) 80 | fmt.Println(e.Kind()) 81 | fmt.Println(e.Type()) 82 | fmt.Println(e.Value()) 83 | fmt.Println(e.Param()) 84 | fmt.Println() 85 | } 86 | } 87 | 88 | // from here you can create your own error messages in whatever language you wish 89 | return 90 | } 91 | 92 | // save user to database 93 | } 94 | 95 | func validateVariable() { 96 | 97 | myEmail := "joeybloggs.gmail.com" 98 | 99 | errs := validate.Var(myEmail, "required,email") 100 | 101 | if errs != nil { 102 | fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag 103 | return 104 | } 105 | 106 | // email ok, move on 107 | } 108 | -------------------------------------------------------------------------------- /_examples/struct-level/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "strings" 9 | 10 | "github.com/go-playground/validator/v10" 11 | ) 12 | 13 | type validationError struct { 14 | Namespace string `json:"namespace"` // can differ when a custom TagNameFunc is registered or 15 | Field string `json:"field"` // by passing alt name to ReportError like below 16 | StructNamespace string `json:"structNamespace"` 17 | StructField string `json:"structField"` 18 | Tag string `json:"tag"` 19 | ActualTag string `json:"actualTag"` 20 | Kind string `json:"kind"` 21 | Type string `json:"type"` 22 | Value string `json:"value"` 23 | Param string `json:"param"` 24 | Message string `json:"message"` 25 | } 26 | 27 | type Gender uint 28 | 29 | const ( 30 | Male Gender = iota + 1 31 | Female 32 | Intersex 33 | ) 34 | 35 | func (gender Gender) String() string { 36 | terms := []string{"Male", "Female", "Intersex"} 37 | if gender < Male || gender > Intersex { 38 | return "unknown" 39 | } 40 | return terms[gender] 41 | } 42 | 43 | // User contains user information 44 | type User struct { 45 | FirstName string `json:"fname"` 46 | LastName string `json:"lname"` 47 | Age uint8 `validate:"gte=0,lte=130"` 48 | Email string `json:"e-mail" validate:"required,email"` 49 | FavouriteColor string `validate:"hexcolor|rgb|rgba"` 50 | Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... 51 | Gender Gender `json:"gender" validate:"required,gender_custom_validation"` 52 | } 53 | 54 | // Address houses a users address information 55 | type Address struct { 56 | Street string `validate:"required"` 57 | City string `validate:"required"` 58 | Planet string `validate:"required"` 59 | Phone string `validate:"required"` 60 | } 61 | 62 | // use a single instance of Validate, it caches struct info 63 | var validate *validator.Validate 64 | 65 | func main() { 66 | 67 | validate = validator.New() 68 | 69 | // register function to get tag name from json tags. 70 | validate.RegisterTagNameFunc(func(fld reflect.StructField) string { 71 | name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] 72 | if name == "-" { 73 | return "" 74 | } 75 | return name 76 | }) 77 | 78 | // register validation for 'User' 79 | // NOTE: only have to register a non-pointer type for 'User', validator 80 | // internally dereferences during it's type checks. 81 | validate.RegisterStructValidation(UserStructLevelValidation, User{}) 82 | 83 | // register a custom validation for user genre on a line 84 | // validates that an enum is within the interval 85 | err := validate.RegisterValidation("gender_custom_validation", func(fl validator.FieldLevel) bool { 86 | value := fl.Field().Interface().(Gender) 87 | return value.String() != "unknown" 88 | }) 89 | if err != nil { 90 | fmt.Println(err) 91 | return 92 | } 93 | 94 | // build 'User' info, normally posted data etc... 95 | address := &Address{ 96 | Street: "Eavesdown Docks", 97 | Planet: "Persphone", 98 | Phone: "none", 99 | City: "Unknown", 100 | } 101 | 102 | user := &User{ 103 | FirstName: "", 104 | LastName: "", 105 | Age: 45, 106 | Email: "Badger.Smith@gmail", 107 | FavouriteColor: "#000", 108 | Addresses: []*Address{address}, 109 | } 110 | 111 | // returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError ) 112 | err = validate.Struct(user) 113 | if err != nil { 114 | 115 | // this check is only needed when your code could produce 116 | // an invalid value for validation such as interface with nil 117 | // value most including myself do not usually have code like this. 118 | var invalidValidationError *validator.InvalidValidationError 119 | if errors.As(err, &invalidValidationError) { 120 | fmt.Println(err) 121 | return 122 | } 123 | 124 | var validateErrs validator.ValidationErrors 125 | if errors.As(err, &validateErrs) { 126 | for _, err := range validateErrs { 127 | e := validationError{ 128 | Namespace: err.Namespace(), 129 | Field: err.Field(), 130 | StructNamespace: err.StructNamespace(), 131 | StructField: err.StructField(), 132 | Tag: err.Tag(), 133 | ActualTag: err.ActualTag(), 134 | Kind: fmt.Sprintf("%v", err.Kind()), 135 | Type: fmt.Sprintf("%v", err.Type()), 136 | Value: fmt.Sprintf("%v", err.Value()), 137 | Param: err.Param(), 138 | Message: err.Error(), 139 | } 140 | 141 | indent, err := json.MarshalIndent(e, "", " ") 142 | if err != nil { 143 | fmt.Println(err) 144 | panic(err) 145 | } 146 | 147 | fmt.Println(string(indent)) 148 | } 149 | } 150 | 151 | // from here you can create your own error messages in whatever language you wish 152 | return 153 | } 154 | 155 | // save user to database 156 | } 157 | 158 | // UserStructLevelValidation contains custom struct level validations that don't always 159 | // make sense at the field validation level. For Example this function validates that either 160 | // FirstName or LastName exist; could have done that with a custom field validation but then 161 | // would have had to add it to both fields duplicating the logic + overhead, this way it's 162 | // only validated once. 163 | // 164 | // NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way 165 | // hooks right into validator and you can combine with validation tags and still have a 166 | // common error output format. 167 | func UserStructLevelValidation(sl validator.StructLevel) { 168 | 169 | user := sl.Current().Interface().(User) 170 | 171 | if len(user.FirstName) == 0 && len(user.LastName) == 0 { 172 | sl.ReportError(user.FirstName, "fname", "FirstName", "fnameorlname", "") 173 | sl.ReportError(user.LastName, "lname", "LastName", "fnameorlname", "") 174 | } 175 | 176 | // plus can do more, even with different tag than "fnameorlname" 177 | } 178 | -------------------------------------------------------------------------------- /_examples/struct-map-rules-validation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-playground/validator/v10" 6 | ) 7 | 8 | type Data struct { 9 | Name string 10 | Email string 11 | Details *Details 12 | } 13 | 14 | type Details struct { 15 | FamilyMembers *FamilyMembers 16 | Salary string 17 | } 18 | 19 | type FamilyMembers struct { 20 | FatherName string 21 | MotherName string 22 | } 23 | 24 | type Data2 struct { 25 | Name string 26 | Age uint32 27 | } 28 | 29 | var validate = validator.New() 30 | 31 | func main() { 32 | validateStruct() 33 | // output 34 | // Key: 'Data2.Name' Error:Field validation for 'Name' failed on the 'min' tag 35 | // Key: 'Data2.Age' Error:Field validation for 'Age' failed on the 'max' tag 36 | 37 | validateStructNested() 38 | // output 39 | // Key: 'Data.Name' Error:Field validation for 'Name' failed on the 'max' tag 40 | // Key: 'Data.Details.FamilyMembers' Error:Field validation for 'FamilyMembers' failed on the 'required' tag 41 | } 42 | 43 | func validateStruct() { 44 | data := Data2{ 45 | Name: "leo", 46 | Age: 1000, 47 | } 48 | 49 | rules := map[string]string{ 50 | "Name": "min=4,max=6", 51 | "Age": "min=4,max=6", 52 | } 53 | 54 | validate.RegisterStructValidationMapRules(rules, Data2{}) 55 | 56 | err := validate.Struct(data) 57 | fmt.Println(err) 58 | fmt.Println() 59 | } 60 | 61 | func validateStructNested() { 62 | data := Data{ 63 | Name: "11sdfddd111", 64 | Email: "zytel3301@mail.com", 65 | Details: &Details{ 66 | Salary: "1000", 67 | }, 68 | } 69 | 70 | rules1 := map[string]string{ 71 | "Name": "min=4,max=6", 72 | "Email": "required,email", 73 | "Details": "required", 74 | } 75 | 76 | rules2 := map[string]string{ 77 | "Salary": "number", 78 | "FamilyMembers": "required", 79 | } 80 | 81 | rules3 := map[string]string{ 82 | "FatherName": "required,min=4,max=32", 83 | "MotherName": "required,min=4,max=32", 84 | } 85 | 86 | validate.RegisterStructValidationMapRules(rules1, Data{}) 87 | validate.RegisterStructValidationMapRules(rules2, Details{}) 88 | validate.RegisterStructValidationMapRules(rules3, FamilyMembers{}) 89 | err := validate.Struct(data) 90 | 91 | fmt.Println(err) 92 | } 93 | -------------------------------------------------------------------------------- /_examples/translations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-playground/locales/en" 7 | ut "github.com/go-playground/universal-translator" 8 | "github.com/go-playground/validator/v10" 9 | en_translations "github.com/go-playground/validator/v10/translations/en" 10 | ) 11 | 12 | // User contains user information 13 | type User struct { 14 | FirstName string `validate:"required"` 15 | LastName string `validate:"required"` 16 | Age uint8 `validate:"gte=0,lte=130"` 17 | Email string `validate:"required,email"` 18 | FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla' 19 | Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... 20 | } 21 | 22 | // Address houses a users address information 23 | type Address struct { 24 | Street string `validate:"required"` 25 | City string `validate:"required"` 26 | Planet string `validate:"required"` 27 | Phone string `validate:"required"` 28 | } 29 | 30 | // use a single instance, it caches struct info 31 | var ( 32 | uni *ut.UniversalTranslator 33 | validate *validator.Validate 34 | ) 35 | 36 | func main() { 37 | 38 | // NOTE: omitting allot of error checking for brevity 39 | 40 | en := en.New() 41 | uni = ut.New(en, en) 42 | 43 | // this is usually know or extracted from http 'Accept-Language' header 44 | // also see uni.FindTranslator(...) 45 | trans, _ := uni.GetTranslator("en") 46 | 47 | validate = validator.New() 48 | en_translations.RegisterDefaultTranslations(validate, trans) 49 | 50 | translateAll(trans) 51 | translateIndividual(trans) 52 | translateOverride(trans) // yep you can specify your own in whatever locale you want! 53 | } 54 | 55 | func translateAll(trans ut.Translator) { 56 | 57 | type User struct { 58 | Username string `validate:"required"` 59 | Tagline string `validate:"required,lt=10"` 60 | Tagline2 string `validate:"required,gt=1"` 61 | } 62 | 63 | user := User{ 64 | Username: "Joeybloggs", 65 | Tagline: "This tagline is way too long.", 66 | Tagline2: "1", 67 | } 68 | 69 | err := validate.Struct(user) 70 | if err != nil { 71 | 72 | // translate all error at once 73 | errs := err.(validator.ValidationErrors) 74 | 75 | // returns a map with key = namespace & value = translated error 76 | // NOTICE: 2 errors are returned and you'll see something surprising 77 | // translations are i18n aware!!!! 78 | // eg. '10 characters' vs '1 character' 79 | fmt.Println(errs.Translate(trans)) 80 | } 81 | } 82 | 83 | func translateIndividual(trans ut.Translator) { 84 | 85 | type User struct { 86 | Username string `validate:"required"` 87 | } 88 | 89 | var user User 90 | 91 | err := validate.Struct(user) 92 | if err != nil { 93 | 94 | errs := err.(validator.ValidationErrors) 95 | 96 | for _, e := range errs { 97 | // can translate each error one at a time. 98 | fmt.Println(e.Translate(trans)) 99 | } 100 | } 101 | } 102 | 103 | func translateOverride(trans ut.Translator) { 104 | 105 | validate.RegisterTranslation("required", trans, func(ut ut.Translator) error { 106 | return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details 107 | }, func(ut ut.Translator, fe validator.FieldError) string { 108 | t, _ := ut.T("required", fe.Field()) 109 | 110 | return t 111 | }) 112 | 113 | type User struct { 114 | Username string `validate:"required"` 115 | } 116 | 117 | var user User 118 | 119 | err := validate.Struct(user) 120 | if err != nil { 121 | 122 | errs := err.(validator.ValidationErrors) 123 | 124 | for _, e := range errs { 125 | // can translate each error one at a time. 126 | fmt.Println(e.Translate(trans)) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /_examples/validate_fn/enum_enumer.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=Enum"; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | const _EnumName = "ZeroOneTwoThree" 11 | 12 | var _EnumIndex = [...]uint8{0, 4, 7, 10, 15} 13 | 14 | const _EnumLowerName = "zeroonetwothree" 15 | 16 | func (i Enum) String() string { 17 | if i >= Enum(len(_EnumIndex)-1) { 18 | return fmt.Sprintf("Enum(%d)", i) 19 | } 20 | return _EnumName[_EnumIndex[i]:_EnumIndex[i+1]] 21 | } 22 | 23 | // An "invalid array index" compiler error signifies that the constant values have changed. 24 | // Re-run the stringer command to generate them again. 25 | func _EnumNoOp() { 26 | var x [1]struct{} 27 | _ = x[Zero-(0)] 28 | _ = x[One-(1)] 29 | _ = x[Two-(2)] 30 | _ = x[Three-(3)] 31 | } 32 | 33 | var _EnumValues = []Enum{Zero, One, Two, Three} 34 | 35 | var _EnumNameToValueMap = map[string]Enum{ 36 | _EnumName[0:4]: Zero, 37 | _EnumLowerName[0:4]: Zero, 38 | _EnumName[4:7]: One, 39 | _EnumLowerName[4:7]: One, 40 | _EnumName[7:10]: Two, 41 | _EnumLowerName[7:10]: Two, 42 | _EnumName[10:15]: Three, 43 | _EnumLowerName[10:15]: Three, 44 | } 45 | 46 | var _EnumNames = []string{ 47 | _EnumName[0:4], 48 | _EnumName[4:7], 49 | _EnumName[7:10], 50 | _EnumName[10:15], 51 | } 52 | 53 | // EnumString retrieves an enum value from the enum constants string name. 54 | // Throws an error if the param is not part of the enum. 55 | func EnumString(s string) (Enum, error) { 56 | if val, ok := _EnumNameToValueMap[s]; ok { 57 | return val, nil 58 | } 59 | 60 | if val, ok := _EnumNameToValueMap[strings.ToLower(s)]; ok { 61 | return val, nil 62 | } 63 | return 0, fmt.Errorf("%s does not belong to Enum values", s) 64 | } 65 | 66 | // EnumValues returns all values of the enum 67 | func EnumValues() []Enum { 68 | return _EnumValues 69 | } 70 | 71 | // EnumStrings returns a slice of all String values of the enum 72 | func EnumStrings() []string { 73 | strs := make([]string, len(_EnumNames)) 74 | copy(strs, _EnumNames) 75 | return strs 76 | } 77 | 78 | // IsAEnum returns "true" if the value is listed in the enum definition. "false" otherwise 79 | func (i Enum) IsAEnum() bool { 80 | for _, v := range _EnumValues { 81 | if i == v { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | -------------------------------------------------------------------------------- /_examples/validate_fn/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/peczenyj/validator/_examples/validate_fn 2 | 3 | go 1.20 4 | 5 | replace github.com/go-playground/validator/v10 => ../../../validator 6 | 7 | require github.com/go-playground/validator/v10 v10.26.0 8 | 9 | require ( 10 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 11 | github.com/go-playground/locales v0.14.1 // indirect 12 | github.com/go-playground/universal-translator v0.18.1 // indirect 13 | github.com/leodido/go-urn v1.4.0 // indirect 14 | golang.org/x/crypto v0.36.0 // indirect 15 | golang.org/x/net v0.38.0 // indirect 16 | golang.org/x/sys v0.31.0 // indirect 17 | golang.org/x/text v0.23.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /_examples/validate_fn/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 3 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 4 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 5 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 6 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 7 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 8 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 9 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 10 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 13 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 14 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 15 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 16 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 17 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 18 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 19 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 20 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 21 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 22 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 23 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 24 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 25 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 26 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 27 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 28 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 29 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 30 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 31 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 32 | -------------------------------------------------------------------------------- /_examples/validate_fn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/go-playground/validator/v10" 8 | ) 9 | 10 | //go:generate enumer -type=Enum 11 | type Enum uint8 12 | 13 | const ( 14 | Zero Enum = iota 15 | One 16 | Two 17 | Three 18 | ) 19 | 20 | func (e *Enum) Validate() error { 21 | if e == nil { 22 | return errors.New("can't be nil") 23 | } 24 | 25 | return nil 26 | } 27 | 28 | type Struct struct { 29 | Foo *Enum `validate:"validateFn"` // uses Validate() error by default 30 | Bar Enum `validate:"validateFn=IsAEnum"` // uses IsAEnum() bool provided by enumer 31 | } 32 | 33 | func main() { 34 | validate := validator.New() 35 | 36 | var x Struct 37 | 38 | x.Bar = Enum(64) 39 | 40 | if err := validate.Struct(x); err != nil { 41 | fmt.Printf("Expected Err(s):\n%+v\n", err) 42 | } 43 | 44 | x = Struct{ 45 | Foo: new(Enum), 46 | Bar: One, 47 | } 48 | 49 | if err := validate.Struct(x); err != nil { 50 | fmt.Printf("Unexpected Err(s):\n%+v\n", err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | type tagType uint8 12 | 13 | const ( 14 | typeDefault tagType = iota 15 | typeOmitEmpty 16 | typeIsDefault 17 | typeNoStructLevel 18 | typeStructOnly 19 | typeDive 20 | typeOr 21 | typeKeys 22 | typeEndKeys 23 | typeOmitNil 24 | typeOmitZero 25 | ) 26 | 27 | const ( 28 | invalidValidation = "Invalid validation tag on field '%s'" 29 | undefinedValidation = "Undefined validation function '%s' on field '%s'" 30 | keysTagNotDefined = "'" + endKeysTag + "' tag encountered without a corresponding '" + keysTag + "' tag" 31 | ) 32 | 33 | type structCache struct { 34 | lock sync.Mutex 35 | m atomic.Value // map[reflect.Type]*cStruct 36 | } 37 | 38 | func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) { 39 | c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key] 40 | return 41 | } 42 | 43 | func (sc *structCache) Set(key reflect.Type, value *cStruct) { 44 | m := sc.m.Load().(map[reflect.Type]*cStruct) 45 | nm := make(map[reflect.Type]*cStruct, len(m)+1) 46 | for k, v := range m { 47 | nm[k] = v 48 | } 49 | nm[key] = value 50 | sc.m.Store(nm) 51 | } 52 | 53 | type tagCache struct { 54 | lock sync.Mutex 55 | m atomic.Value // map[string]*cTag 56 | } 57 | 58 | func (tc *tagCache) Get(key string) (c *cTag, found bool) { 59 | c, found = tc.m.Load().(map[string]*cTag)[key] 60 | return 61 | } 62 | 63 | func (tc *tagCache) Set(key string, value *cTag) { 64 | m := tc.m.Load().(map[string]*cTag) 65 | nm := make(map[string]*cTag, len(m)+1) 66 | for k, v := range m { 67 | nm[k] = v 68 | } 69 | nm[key] = value 70 | tc.m.Store(nm) 71 | } 72 | 73 | type cStruct struct { 74 | name string 75 | fields []*cField 76 | fn StructLevelFuncCtx 77 | } 78 | 79 | type cField struct { 80 | idx int 81 | name string 82 | altName string 83 | namesEqual bool 84 | cTags *cTag 85 | } 86 | 87 | type cTag struct { 88 | tag string 89 | aliasTag string 90 | actualAliasTag string 91 | param string 92 | keys *cTag // only populated when using tag's 'keys' and 'endkeys' for map key validation 93 | next *cTag 94 | fn FuncCtx 95 | typeof tagType 96 | hasTag bool 97 | hasAlias bool 98 | hasParam bool // true if parameter used eg. eq= where the equal sign has been set 99 | isBlockEnd bool // indicates the current tag represents the last validation in the block 100 | runValidationWhenNil bool 101 | } 102 | 103 | func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct { 104 | v.structCache.lock.Lock() 105 | defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise! 106 | 107 | typ := current.Type() 108 | 109 | // could have been multiple trying to access, but once first is done this ensures struct 110 | // isn't parsed again. 111 | cs, ok := v.structCache.Get(typ) 112 | if ok { 113 | return cs 114 | } 115 | 116 | cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]} 117 | 118 | numFields := current.NumField() 119 | rules := v.rules[typ] 120 | 121 | var ctag *cTag 122 | var fld reflect.StructField 123 | var tag string 124 | var customName string 125 | 126 | for i := 0; i < numFields; i++ { 127 | fld = typ.Field(i) 128 | 129 | if !v.privateFieldValidation && !fld.Anonymous && len(fld.PkgPath) > 0 { 130 | continue 131 | } 132 | 133 | if rtag, ok := rules[fld.Name]; ok { 134 | tag = rtag 135 | } else { 136 | tag = fld.Tag.Get(v.tagName) 137 | } 138 | 139 | if tag == skipValidationTag { 140 | continue 141 | } 142 | 143 | customName = fld.Name 144 | 145 | if v.hasTagNameFunc { 146 | name := v.tagNameFunc(fld) 147 | if len(name) > 0 { 148 | customName = name 149 | } 150 | } 151 | 152 | // NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different 153 | // and so only struct level caching can be used instead of combined with Field tag caching 154 | 155 | if len(tag) > 0 { 156 | ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false) 157 | } else { 158 | // even if field doesn't have validations need cTag for traversing to potential inner/nested 159 | // elements of the field. 160 | ctag = new(cTag) 161 | } 162 | 163 | cs.fields = append(cs.fields, &cField{ 164 | idx: i, 165 | name: fld.Name, 166 | altName: customName, 167 | cTags: ctag, 168 | namesEqual: fld.Name == customName, 169 | }) 170 | } 171 | v.structCache.Set(typ, cs) 172 | return cs 173 | } 174 | 175 | func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) { 176 | var t string 177 | noAlias := len(alias) == 0 178 | tags := strings.Split(tag, tagSeparator) 179 | 180 | for i := 0; i < len(tags); i++ { 181 | t = tags[i] 182 | if noAlias { 183 | alias = t 184 | } 185 | 186 | // check map for alias and process new tags, otherwise process as usual 187 | if tagsVal, found := v.aliases[t]; found { 188 | if i == 0 { 189 | firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true) 190 | } else { 191 | next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true) 192 | current.next, current = next, curr 193 | } 194 | continue 195 | } 196 | 197 | var prevTag tagType 198 | 199 | if i == 0 { 200 | current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true, typeof: typeDefault} 201 | firstCtag = current 202 | } else { 203 | prevTag = current.typeof 204 | current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true} 205 | current = current.next 206 | } 207 | 208 | switch t { 209 | case diveTag: 210 | current.typeof = typeDive 211 | 212 | case keysTag: 213 | current.typeof = typeKeys 214 | 215 | if i == 0 || prevTag != typeDive { 216 | panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag)) 217 | } 218 | 219 | // need to pass along only keys tag 220 | // need to increment i to skip over the keys tags 221 | b := make([]byte, 0, 64) 222 | 223 | i++ 224 | 225 | for ; i < len(tags); i++ { 226 | b = append(b, tags[i]...) 227 | b = append(b, ',') 228 | 229 | if tags[i] == endKeysTag { 230 | break 231 | } 232 | } 233 | 234 | current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false) 235 | 236 | case endKeysTag: 237 | current.typeof = typeEndKeys 238 | 239 | // if there are more in tags then there was no keysTag defined 240 | // and an error should be thrown 241 | if i != len(tags)-1 { 242 | panic(keysTagNotDefined) 243 | } 244 | return 245 | 246 | case omitzero: 247 | current.typeof = typeOmitZero 248 | continue 249 | 250 | case omitempty: 251 | current.typeof = typeOmitEmpty 252 | 253 | case omitnil: 254 | current.typeof = typeOmitNil 255 | 256 | case structOnlyTag: 257 | current.typeof = typeStructOnly 258 | 259 | case noStructLevelTag: 260 | current.typeof = typeNoStructLevel 261 | 262 | default: 263 | if t == isdefault { 264 | current.typeof = typeIsDefault 265 | } 266 | // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C" 267 | orVals := strings.Split(t, orSeparator) 268 | 269 | for j := 0; j < len(orVals); j++ { 270 | vals := strings.SplitN(orVals[j], tagKeySeparator, 2) 271 | if noAlias { 272 | alias = vals[0] 273 | current.aliasTag = alias 274 | } else { 275 | current.actualAliasTag = t 276 | } 277 | 278 | if j > 0 { 279 | current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true} 280 | current = current.next 281 | } 282 | current.hasParam = len(vals) > 1 283 | 284 | current.tag = vals[0] 285 | if len(current.tag) == 0 { 286 | panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName))) 287 | } 288 | 289 | if wrapper, ok := v.validations[current.tag]; ok { 290 | current.fn = wrapper.fn 291 | current.runValidationWhenNil = wrapper.runValidationOnNil 292 | } else { 293 | panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName))) 294 | } 295 | 296 | if len(orVals) > 1 { 297 | current.typeof = typeOr 298 | } 299 | 300 | if len(vals) > 1 { 301 | current.param = strings.ReplaceAll(strings.ReplaceAll(vals[1], utf8HexComma, ","), utf8Pipe, "|") 302 | } 303 | } 304 | current.isBlockEnd = true 305 | } 306 | } 307 | return 308 | } 309 | 310 | func (v *Validate) fetchCacheTag(tag string) *cTag { 311 | // find cached tag 312 | ctag, found := v.tagCache.Get(tag) 313 | if !found { 314 | v.tagCache.lock.Lock() 315 | defer v.tagCache.lock.Unlock() 316 | 317 | // could have been multiple trying to access, but once first is done this ensures tag 318 | // isn't parsed again. 319 | ctag, found = v.tagCache.Get(tag) 320 | if !found { 321 | ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false) 322 | v.tagCache.Set(tag, ctag) 323 | } 324 | } 325 | return ctag 326 | } 327 | -------------------------------------------------------------------------------- /currency_codes.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | var iso4217 = map[string]struct{}{ 4 | "AFN": {}, "EUR": {}, "ALL": {}, "DZD": {}, "USD": {}, 5 | "AOA": {}, "XCD": {}, "ARS": {}, "AMD": {}, "AWG": {}, 6 | "AUD": {}, "AZN": {}, "BSD": {}, "BHD": {}, "BDT": {}, 7 | "BBD": {}, "BYN": {}, "BZD": {}, "XOF": {}, "BMD": {}, 8 | "INR": {}, "BTN": {}, "BOB": {}, "BOV": {}, "BAM": {}, 9 | "BWP": {}, "NOK": {}, "BRL": {}, "BND": {}, "BGN": {}, 10 | "BIF": {}, "CVE": {}, "KHR": {}, "XAF": {}, "CAD": {}, 11 | "KYD": {}, "CLP": {}, "CLF": {}, "CNY": {}, "COP": {}, 12 | "COU": {}, "KMF": {}, "CDF": {}, "NZD": {}, "CRC": {}, 13 | "HRK": {}, "CUP": {}, "CUC": {}, "ANG": {}, "CZK": {}, 14 | "DKK": {}, "DJF": {}, "DOP": {}, "EGP": {}, "SVC": {}, 15 | "ERN": {}, "SZL": {}, "ETB": {}, "FKP": {}, "FJD": {}, 16 | "XPF": {}, "GMD": {}, "GEL": {}, "GHS": {}, "GIP": {}, 17 | "GTQ": {}, "GBP": {}, "GNF": {}, "GYD": {}, "HTG": {}, 18 | "HNL": {}, "HKD": {}, "HUF": {}, "ISK": {}, "IDR": {}, 19 | "XDR": {}, "IRR": {}, "IQD": {}, "ILS": {}, "JMD": {}, 20 | "JPY": {}, "JOD": {}, "KZT": {}, "KES": {}, "KPW": {}, 21 | "KRW": {}, "KWD": {}, "KGS": {}, "LAK": {}, "LBP": {}, 22 | "LSL": {}, "ZAR": {}, "LRD": {}, "LYD": {}, "CHF": {}, 23 | "MOP": {}, "MKD": {}, "MGA": {}, "MWK": {}, "MYR": {}, 24 | "MVR": {}, "MRU": {}, "MUR": {}, "XUA": {}, "MXN": {}, 25 | "MXV": {}, "MDL": {}, "MNT": {}, "MAD": {}, "MZN": {}, 26 | "MMK": {}, "NAD": {}, "NPR": {}, "NIO": {}, "NGN": {}, 27 | "OMR": {}, "PKR": {}, "PAB": {}, "PGK": {}, "PYG": {}, 28 | "PEN": {}, "PHP": {}, "PLN": {}, "QAR": {}, "RON": {}, 29 | "RUB": {}, "RWF": {}, "SHP": {}, "WST": {}, "STN": {}, 30 | "SAR": {}, "RSD": {}, "SCR": {}, "SLL": {}, "SGD": {}, 31 | "XSU": {}, "SBD": {}, "SOS": {}, "SSP": {}, "LKR": {}, 32 | "SDG": {}, "SRD": {}, "SEK": {}, "CHE": {}, "CHW": {}, 33 | "SYP": {}, "TWD": {}, "TJS": {}, "TZS": {}, "THB": {}, 34 | "TOP": {}, "TTD": {}, "TND": {}, "TRY": {}, "TMT": {}, 35 | "UGX": {}, "UAH": {}, "AED": {}, "USN": {}, "UYU": {}, 36 | "UYI": {}, "UYW": {}, "UZS": {}, "VUV": {}, "VES": {}, 37 | "VND": {}, "YER": {}, "ZMW": {}, "ZWL": {}, "XBA": {}, 38 | "XBB": {}, "XBC": {}, "XBD": {}, "XTS": {}, "XXX": {}, 39 | "XAU": {}, "XPD": {}, "XPT": {}, "XAG": {}, 40 | } 41 | 42 | var iso4217_numeric = map[int]struct{}{ 43 | 8: {}, 12: {}, 32: {}, 36: {}, 44: {}, 44 | 48: {}, 50: {}, 51: {}, 52: {}, 60: {}, 45 | 64: {}, 68: {}, 72: {}, 84: {}, 90: {}, 46 | 96: {}, 104: {}, 108: {}, 116: {}, 124: {}, 47 | 132: {}, 136: {}, 144: {}, 152: {}, 156: {}, 48 | 170: {}, 174: {}, 188: {}, 191: {}, 192: {}, 49 | 203: {}, 208: {}, 214: {}, 222: {}, 230: {}, 50 | 232: {}, 238: {}, 242: {}, 262: {}, 270: {}, 51 | 292: {}, 320: {}, 324: {}, 328: {}, 332: {}, 52 | 340: {}, 344: {}, 348: {}, 352: {}, 356: {}, 53 | 360: {}, 364: {}, 368: {}, 376: {}, 388: {}, 54 | 392: {}, 398: {}, 400: {}, 404: {}, 408: {}, 55 | 410: {}, 414: {}, 417: {}, 418: {}, 422: {}, 56 | 426: {}, 430: {}, 434: {}, 446: {}, 454: {}, 57 | 458: {}, 462: {}, 480: {}, 484: {}, 496: {}, 58 | 498: {}, 504: {}, 512: {}, 516: {}, 524: {}, 59 | 532: {}, 533: {}, 548: {}, 554: {}, 558: {}, 60 | 566: {}, 578: {}, 586: {}, 590: {}, 598: {}, 61 | 600: {}, 604: {}, 608: {}, 634: {}, 643: {}, 62 | 646: {}, 654: {}, 682: {}, 690: {}, 694: {}, 63 | 702: {}, 704: {}, 706: {}, 710: {}, 728: {}, 64 | 748: {}, 752: {}, 756: {}, 760: {}, 764: {}, 65 | 776: {}, 780: {}, 784: {}, 788: {}, 800: {}, 66 | 807: {}, 818: {}, 826: {}, 834: {}, 840: {}, 67 | 858: {}, 860: {}, 882: {}, 886: {}, 901: {}, 68 | 927: {}, 928: {}, 929: {}, 930: {}, 931: {}, 69 | 932: {}, 933: {}, 934: {}, 936: {}, 938: {}, 70 | 940: {}, 941: {}, 943: {}, 944: {}, 946: {}, 71 | 947: {}, 948: {}, 949: {}, 950: {}, 951: {}, 72 | 952: {}, 953: {}, 955: {}, 956: {}, 957: {}, 73 | 958: {}, 959: {}, 960: {}, 961: {}, 962: {}, 74 | 963: {}, 964: {}, 965: {}, 967: {}, 968: {}, 75 | 969: {}, 970: {}, 971: {}, 972: {}, 973: {}, 76 | 975: {}, 976: {}, 977: {}, 978: {}, 979: {}, 77 | 980: {}, 981: {}, 984: {}, 985: {}, 986: {}, 78 | 990: {}, 994: {}, 997: {}, 999: {}, 79 | } 80 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | 9 | ut "github.com/go-playground/universal-translator" 10 | ) 11 | 12 | const ( 13 | fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag" 14 | ) 15 | 16 | // ValidationErrorsTranslations is the translation return type 17 | type ValidationErrorsTranslations map[string]string 18 | 19 | // InvalidValidationError describes an invalid argument passed to 20 | // `Struct`, `StructExcept`, StructPartial` or `Field` 21 | type InvalidValidationError struct { 22 | Type reflect.Type 23 | } 24 | 25 | // Error returns InvalidValidationError message 26 | func (e *InvalidValidationError) Error() string { 27 | if e.Type == nil { 28 | return "validator: (nil)" 29 | } 30 | 31 | return "validator: (nil " + e.Type.String() + ")" 32 | } 33 | 34 | // ValidationErrors is an array of FieldError's 35 | // for use in custom error messages post validation. 36 | type ValidationErrors []FieldError 37 | 38 | // Error is intended for use in development + debugging and not intended to be a production error message. 39 | // It allows ValidationErrors to subscribe to the Error interface. 40 | // All information to create an error message specific to your application is contained within 41 | // the FieldError found within the ValidationErrors array 42 | func (ve ValidationErrors) Error() string { 43 | buff := bytes.NewBufferString("") 44 | 45 | for i := 0; i < len(ve); i++ { 46 | buff.WriteString(ve[i].Error()) 47 | buff.WriteString("\n") 48 | } 49 | 50 | return strings.TrimSpace(buff.String()) 51 | } 52 | 53 | // Translate translates all of the ValidationErrors 54 | func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations { 55 | trans := make(ValidationErrorsTranslations) 56 | 57 | var fe *fieldError 58 | 59 | for i := 0; i < len(ve); i++ { 60 | fe = ve[i].(*fieldError) 61 | 62 | // // in case an Anonymous struct was used, ensure that the key 63 | // // would be 'Username' instead of ".Username" 64 | // if len(fe.ns) > 0 && fe.ns[:1] == "." { 65 | // trans[fe.ns[1:]] = fe.Translate(ut) 66 | // continue 67 | // } 68 | 69 | trans[fe.ns] = fe.Translate(ut) 70 | } 71 | 72 | return trans 73 | } 74 | 75 | // FieldError contains all functions to get error details 76 | type FieldError interface { 77 | 78 | // Tag returns the validation tag that failed. if the 79 | // validation was an alias, this will return the 80 | // alias name and not the underlying tag that failed. 81 | // 82 | // eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla" 83 | // will return "iscolor" 84 | Tag() string 85 | 86 | // ActualTag returns the validation tag that failed, even if an 87 | // alias the actual tag within the alias will be returned. 88 | // If an 'or' validation fails the entire or will be returned. 89 | // 90 | // eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla" 91 | // will return "hexcolor|rgb|rgba|hsl|hsla" 92 | ActualTag() string 93 | 94 | // Namespace returns the namespace for the field error, with the tag 95 | // name taking precedence over the field's actual name. 96 | // 97 | // eg. JSON name "User.fname" 98 | // 99 | // See StructNamespace() for a version that returns actual names. 100 | // 101 | // NOTE: this field can be blank when validating a single primitive field 102 | // using validate.Field(...) as there is no way to extract it's name 103 | Namespace() string 104 | 105 | // StructNamespace returns the namespace for the field error, with the field's 106 | // actual name. 107 | // 108 | // eq. "User.FirstName" see Namespace for comparison 109 | // 110 | // NOTE: this field can be blank when validating a single primitive field 111 | // using validate.Field(...) as there is no way to extract its name 112 | StructNamespace() string 113 | 114 | // Field returns the fields name with the tag name taking precedence over the 115 | // field's actual name. 116 | // 117 | // `RegisterTagNameFunc` must be registered to get tag value. 118 | // 119 | // eq. JSON name "fname" 120 | // see StructField for comparison 121 | Field() string 122 | 123 | // StructField returns the field's actual name from the struct, when able to determine. 124 | // 125 | // eq. "FirstName" 126 | // see Field for comparison 127 | StructField() string 128 | 129 | // Value returns the actual field's value in case needed for creating the error 130 | // message 131 | Value() interface{} 132 | 133 | // Param returns the param value, in string form for comparison; this will also 134 | // help with generating an error message 135 | Param() string 136 | 137 | // Kind returns the Field's reflect Kind 138 | // 139 | // eg. time.Time's kind is a struct 140 | Kind() reflect.Kind 141 | 142 | // Type returns the Field's reflect Type 143 | // 144 | // eg. time.Time's type is time.Time 145 | Type() reflect.Type 146 | 147 | // Translate returns the FieldError's translated error 148 | // from the provided 'ut.Translator' and registered 'TranslationFunc' 149 | // 150 | // NOTE: if no registered translator can be found it returns the same as 151 | // calling fe.Error() 152 | Translate(ut ut.Translator) string 153 | 154 | // Error returns the FieldError's message 155 | Error() string 156 | } 157 | 158 | // compile time interface checks 159 | var _ FieldError = new(fieldError) 160 | var _ error = new(fieldError) 161 | 162 | // fieldError contains a single field's validation error along 163 | // with other properties that may be needed for error message creation 164 | // it complies with the FieldError interface 165 | type fieldError struct { 166 | v *Validate 167 | tag string 168 | actualTag string 169 | ns string 170 | structNs string 171 | fieldLen uint8 172 | structfieldLen uint8 173 | value interface{} 174 | param string 175 | kind reflect.Kind 176 | typ reflect.Type 177 | } 178 | 179 | // Tag returns the validation tag that failed. 180 | func (fe *fieldError) Tag() string { 181 | return fe.tag 182 | } 183 | 184 | // ActualTag returns the validation tag that failed, even if an 185 | // alias the actual tag within the alias will be returned. 186 | func (fe *fieldError) ActualTag() string { 187 | return fe.actualTag 188 | } 189 | 190 | // Namespace returns the namespace for the field error, with the tag 191 | // name taking precedence over the field's actual name. 192 | func (fe *fieldError) Namespace() string { 193 | return fe.ns 194 | } 195 | 196 | // StructNamespace returns the namespace for the field error, with the field's 197 | // actual name. 198 | func (fe *fieldError) StructNamespace() string { 199 | return fe.structNs 200 | } 201 | 202 | // Field returns the field's name with the tag name taking precedence over the 203 | // field's actual name. 204 | func (fe *fieldError) Field() string { 205 | return fe.ns[len(fe.ns)-int(fe.fieldLen):] 206 | // // return fe.field 207 | // fld := fe.ns[len(fe.ns)-int(fe.fieldLen):] 208 | 209 | // log.Println("FLD:", fld) 210 | 211 | // if len(fld) > 0 && fld[:1] == "." { 212 | // return fld[1:] 213 | // } 214 | 215 | // return fld 216 | } 217 | 218 | // StructField returns the field's actual name from the struct, when able to determine. 219 | func (fe *fieldError) StructField() string { 220 | // return fe.structField 221 | return fe.structNs[len(fe.structNs)-int(fe.structfieldLen):] 222 | } 223 | 224 | // Value returns the actual field's value in case needed for creating the error 225 | // message 226 | func (fe *fieldError) Value() interface{} { 227 | return fe.value 228 | } 229 | 230 | // Param returns the param value, in string form for comparison; this will 231 | // also help with generating an error message 232 | func (fe *fieldError) Param() string { 233 | return fe.param 234 | } 235 | 236 | // Kind returns the Field's reflect Kind 237 | func (fe *fieldError) Kind() reflect.Kind { 238 | return fe.kind 239 | } 240 | 241 | // Type returns the Field's reflect Type 242 | func (fe *fieldError) Type() reflect.Type { 243 | return fe.typ 244 | } 245 | 246 | // Error returns the fieldError's error message 247 | func (fe *fieldError) Error() string { 248 | return fmt.Sprintf(fieldErrMsg, fe.ns, fe.Field(), fe.tag) 249 | } 250 | 251 | // Translate returns the FieldError's translated error 252 | // from the provided 'ut.Translator' and registered 'TranslationFunc' 253 | // 254 | // NOTE: if no registered translation can be found, it returns the original 255 | // untranslated error message. 256 | func (fe *fieldError) Translate(ut ut.Translator) string { 257 | var fn TranslationFunc 258 | 259 | m, ok := fe.v.transTagFunc[ut] 260 | if !ok { 261 | return fe.Error() 262 | } 263 | 264 | fn, ok = m[fe.tag] 265 | if !ok { 266 | fn, ok = m[fe.actualTag] 267 | if !ok { 268 | return fe.Error() 269 | } 270 | } 271 | 272 | return fn(ut, fe) 273 | } 274 | -------------------------------------------------------------------------------- /field_level.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import "reflect" 4 | 5 | // FieldLevel contains all the information and helper functions 6 | // to validate a field 7 | type FieldLevel interface { 8 | 9 | // Top returns the top level struct, if any 10 | Top() reflect.Value 11 | 12 | // Parent returns the current fields parent struct, if any or 13 | // the comparison value if called 'VarWithValue' 14 | Parent() reflect.Value 15 | 16 | // Field returns current field for validation 17 | Field() reflect.Value 18 | 19 | // FieldName returns the field's name with the tag 20 | // name taking precedence over the fields actual name. 21 | FieldName() string 22 | 23 | // StructFieldName returns the struct field's name 24 | StructFieldName() string 25 | 26 | // Param returns param for validation against current field 27 | Param() string 28 | 29 | // GetTag returns the current validations tag name 30 | GetTag() string 31 | 32 | // ExtractType gets the actual underlying type of field value. 33 | // It will dive into pointers, customTypes and return you the 34 | // underlying value and it's kind. 35 | ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) 36 | 37 | // GetStructFieldOK traverses the parent struct to retrieve a specific field denoted by the provided namespace 38 | // in the param and returns the field, field kind and whether is was successful in retrieving 39 | // the field at all. 40 | // 41 | // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field 42 | // could not be retrieved because it didn't exist. 43 | // 44 | // Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable. 45 | GetStructFieldOK() (reflect.Value, reflect.Kind, bool) 46 | 47 | // GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for 48 | // the field and namespace allowing more extensibility for validators. 49 | // 50 | // Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable. 51 | GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) 52 | 53 | // GetStructFieldOK2 traverses the parent struct to retrieve a specific field denoted by the provided namespace 54 | // in the param and returns the field, field kind, if it's a nullable type and whether is was successful in retrieving 55 | // the field at all. 56 | // 57 | // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field 58 | // could not be retrieved because it didn't exist. 59 | GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) 60 | 61 | // GetStructFieldOKAdvanced2 is the same as GetStructFieldOK except that it accepts the parent struct to start looking for 62 | // the field and namespace allowing more extensibility for validators. 63 | GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) 64 | } 65 | 66 | var _ FieldLevel = new(validate) 67 | 68 | // Field returns current field for validation 69 | func (v *validate) Field() reflect.Value { 70 | return v.flField 71 | } 72 | 73 | // FieldName returns the field's name with the tag 74 | // name taking precedence over the fields actual name. 75 | func (v *validate) FieldName() string { 76 | return v.cf.altName 77 | } 78 | 79 | // GetTag returns the current validations tag name 80 | func (v *validate) GetTag() string { 81 | return v.ct.tag 82 | } 83 | 84 | // StructFieldName returns the struct field's name 85 | func (v *validate) StructFieldName() string { 86 | return v.cf.name 87 | } 88 | 89 | // Param returns param for validation against current field 90 | func (v *validate) Param() string { 91 | return v.ct.param 92 | } 93 | 94 | // GetStructFieldOK returns Param returns param for validation against current field 95 | // 96 | // Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable. 97 | func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) { 98 | current, kind, _, found := v.getStructFieldOKInternal(v.slflParent, v.ct.param) 99 | return current, kind, found 100 | } 101 | 102 | // GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for 103 | // the field and namespace allowing more extensibility for validators. 104 | // 105 | // Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable. 106 | func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) { 107 | current, kind, _, found := v.GetStructFieldOKAdvanced2(val, namespace) 108 | return current, kind, found 109 | } 110 | 111 | // GetStructFieldOK2 returns Param returns param for validation against current field 112 | func (v *validate) GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) { 113 | return v.getStructFieldOKInternal(v.slflParent, v.ct.param) 114 | } 115 | 116 | // GetStructFieldOKAdvanced2 is the same as GetStructFieldOK except that it accepts the parent struct to start looking for 117 | // the field and namespace allowing more extensibility for validators. 118 | func (v *validate) GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) { 119 | return v.getStructFieldOKInternal(val, namespace) 120 | } 121 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-playground/validator/v10 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gabriel-vasile/mimetype v1.4.8 7 | github.com/go-playground/assert/v2 v2.2.0 8 | github.com/go-playground/locales v0.14.1 9 | github.com/go-playground/universal-translator v0.18.1 10 | github.com/leodido/go-urn v1.4.0 11 | golang.org/x/crypto v0.33.0 12 | golang.org/x/text v0.22.0 13 | ) 14 | 15 | require ( 16 | golang.org/x/net v0.34.0 // indirect 17 | golang.org/x/sys v0.30.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 3 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 4 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 5 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 6 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 7 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 8 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 9 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 10 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 11 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 14 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 15 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 16 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 17 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 18 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 19 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 20 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 21 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 22 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 23 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-playground/validator/5b9542b93487972cdfa75ed03ebe4286c3f44c01/logo.png -------------------------------------------------------------------------------- /non-standard/validators/notblank.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | 7 | "github.com/go-playground/validator/v10" 8 | ) 9 | 10 | // NotBlank is the validation function for validating if the current field 11 | // has a value or length greater than zero, or is not a space only string. 12 | func NotBlank(fl validator.FieldLevel) bool { 13 | field := fl.Field() 14 | 15 | switch field.Kind() { 16 | case reflect.String: 17 | return len(strings.Trim(strings.TrimSpace(field.String()), "\x1c\x1d\x1e\x1f")) > 0 18 | case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array: 19 | return field.Len() > 0 20 | case reflect.Ptr, reflect.Interface, reflect.Func: 21 | return !field.IsNil() 22 | default: 23 | return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /non-standard/validators/notblank_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-playground/assert/v2" 7 | "github.com/go-playground/validator/v10" 8 | ) 9 | 10 | type test struct { 11 | String string `validate:"notblank"` 12 | Array []int `validate:"notblank"` 13 | Pointer *int `validate:"notblank"` 14 | Number int `validate:"notblank"` 15 | Interface interface{} `validate:"notblank"` 16 | Func func() `validate:"notblank"` 17 | } 18 | 19 | func TestNotBlank(t *testing.T) { 20 | v := validator.New() 21 | err := v.RegisterValidation("notblank", NotBlank) 22 | assert.Equal(t, nil, err) 23 | 24 | // Errors 25 | var x *int 26 | invalid := test{ 27 | String: " \x1c\x1d\x1e\x1f\r\n", 28 | Array: []int{}, 29 | Pointer: x, 30 | Number: 0, 31 | Interface: nil, 32 | Func: nil, 33 | } 34 | fieldsWithError := []string{ 35 | "String", 36 | "Array", 37 | "Pointer", 38 | "Number", 39 | "Interface", 40 | "Func", 41 | } 42 | 43 | errors := v.Struct(invalid).(validator.ValidationErrors) 44 | var fields []string 45 | for _, err := range errors { 46 | fields = append(fields, err.Field()) 47 | } 48 | 49 | assert.Equal(t, fieldsWithError, fields) 50 | 51 | // No errors 52 | y := 1 53 | x = &y 54 | valid := test{ 55 | String: "str", 56 | Array: []int{1}, 57 | Pointer: x, 58 | Number: 1, 59 | Interface: "value", 60 | Func: func() {}, 61 | } 62 | 63 | err = v.Struct(valid) 64 | assert.Equal(t, nil, err) 65 | } 66 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | // Option represents a configurations option to be applied to validator during initialization. 4 | type Option func(*Validate) 5 | 6 | // WithRequiredStructEnabled enables required tag on non-pointer structs to be applied instead of ignored. 7 | // 8 | // This was made opt-in behaviour in order to maintain backward compatibility with the behaviour previous 9 | // to being able to apply struct level validations on struct fields directly. 10 | // 11 | // It is recommended you enabled this as it will be the default behaviour in v11+ 12 | func WithRequiredStructEnabled() Option { 13 | return func(v *Validate) { 14 | v.requiredStructEnabled = true 15 | } 16 | } 17 | 18 | // WithPrivateFieldValidation activates validation for unexported fields via the use of the `unsafe` package. 19 | // 20 | // By opting into this feature you are acknowledging that you are aware of the risks and accept any current or future 21 | // consequences of using this feature. 22 | func WithPrivateFieldValidation() Option { 23 | return func(v *Validate) { 24 | v.privateFieldValidation = true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /postcode_regexes.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "regexp" 5 | "sync" 6 | ) 7 | 8 | var postCodePatternDict = map[string]string{ 9 | "GB": `^GIR[ ]?0AA|((AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}))|BFPO[ ]?\d{1,4}$`, 10 | "JE": `^JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, 11 | "GG": `^GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, 12 | "IM": `^IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, 13 | "US": `^\d{5}([ \-]\d{4})?$`, 14 | "CA": `^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ ]?\d[ABCEGHJ-NPRSTV-Z]\d$`, 15 | "DE": `^\d{5}$`, 16 | "JP": `^\d{3}-\d{4}$`, 17 | "FR": `^\d{2}[ ]?\d{3}$`, 18 | "AU": `^\d{4}$`, 19 | "IT": `^\d{5}$`, 20 | "CH": `^\d{4}$`, 21 | "AT": `^\d{4}$`, 22 | "ES": `^\d{5}$`, 23 | "NL": `^\d{4}[ ]?[A-Z]{2}$`, 24 | "BE": `^\d{4}$`, 25 | "DK": `^\d{4}$`, 26 | "SE": `^\d{3}[ ]?\d{2}$`, 27 | "NO": `^\d{4}$`, 28 | "BR": `^\d{5}[\-]?\d{3}$`, 29 | "PT": `^\d{4}([\-]\d{3})?$`, 30 | "FI": `^\d{5}$`, 31 | "AX": `^22\d{3}$`, 32 | "KR": `^\d{3}[\-]\d{3}$`, 33 | "CN": `^\d{6}$`, 34 | "TW": `^\d{3}(\d{2})?$`, 35 | "SG": `^\d{6}$`, 36 | "DZ": `^\d{5}$`, 37 | "AD": `^AD\d{3}$`, 38 | "AR": `^([A-HJ-NP-Z])?\d{4}([A-Z]{3})?$`, 39 | "AM": `^(37)?\d{4}$`, 40 | "AZ": `^\d{4}$`, 41 | "BH": `^((1[0-2]|[2-9])\d{2})?$`, 42 | "BD": `^\d{4}$`, 43 | "BB": `^(BB\d{5})?$`, 44 | "BY": `^\d{6}$`, 45 | "BM": `^[A-Z]{2}[ ]?[A-Z0-9]{2}$`, 46 | "BA": `^\d{5}$`, 47 | "IO": `^BBND 1ZZ$`, 48 | "BN": `^[A-Z]{2}[ ]?\d{4}$`, 49 | "BG": `^\d{4}$`, 50 | "KH": `^\d{5}$`, 51 | "CV": `^\d{4}$`, 52 | "CL": `^\d{7}$`, 53 | "CR": `^\d{4,5}|\d{3}-\d{4}$`, 54 | "HR": `^\d{5}$`, 55 | "CY": `^\d{4}$`, 56 | "CZ": `^\d{3}[ ]?\d{2}$`, 57 | "DO": `^\d{5}$`, 58 | "EC": `^([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?$`, 59 | "EG": `^\d{5}$`, 60 | "EE": `^\d{5}$`, 61 | "FO": `^\d{3}$`, 62 | "GE": `^\d{4}$`, 63 | "GR": `^\d{3}[ ]?\d{2}$`, 64 | "GL": `^39\d{2}$`, 65 | "GT": `^\d{5}$`, 66 | "HT": `^\d{4}$`, 67 | "HN": `^(?:\d{5})?$`, 68 | "HU": `^\d{4}$`, 69 | "IS": `^\d{3}$`, 70 | "IN": `^\d{6}$`, 71 | "ID": `^\d{5}$`, 72 | "IL": `^\d{5}$`, 73 | "JO": `^\d{5}$`, 74 | "KZ": `^\d{6}$`, 75 | "KE": `^\d{5}$`, 76 | "KW": `^\d{5}$`, 77 | "LA": `^\d{5}$`, 78 | "LV": `^\d{4}$`, 79 | "LB": `^(\d{4}([ ]?\d{4})?)?$`, 80 | "LI": `^(948[5-9])|(949[0-7])$`, 81 | "LT": `^\d{5}$`, 82 | "LU": `^\d{4}$`, 83 | "MK": `^\d{4}$`, 84 | "MY": `^\d{5}$`, 85 | "MV": `^\d{5}$`, 86 | "MT": `^[A-Z]{3}[ ]?\d{2,4}$`, 87 | "MU": `^(\d{3}[A-Z]{2}\d{3})?$`, 88 | "MX": `^\d{5}$`, 89 | "MD": `^\d{4}$`, 90 | "MC": `^980\d{2}$`, 91 | "MA": `^\d{5}$`, 92 | "NP": `^\d{5}$`, 93 | "NZ": `^\d{4}$`, 94 | "NI": `^((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?$`, 95 | "NG": `^(\d{6})?$`, 96 | "OM": `^(PC )?\d{3}$`, 97 | "PK": `^\d{5}$`, 98 | "PY": `^\d{4}$`, 99 | "PH": `^\d{4}$`, 100 | "PL": `^\d{2}-\d{3}$`, 101 | "PR": `^00[679]\d{2}([ \-]\d{4})?$`, 102 | "RO": `^\d{6}$`, 103 | "RU": `^\d{6}$`, 104 | "SM": `^4789\d$`, 105 | "SA": `^\d{5}$`, 106 | "SN": `^\d{5}$`, 107 | "SK": `^\d{3}[ ]?\d{2}$`, 108 | "SI": `^\d{4}$`, 109 | "ZA": `^\d{4}$`, 110 | "LK": `^\d{5}$`, 111 | "TJ": `^\d{6}$`, 112 | "TH": `^\d{5}$`, 113 | "TN": `^\d{4}$`, 114 | "TR": `^\d{5}$`, 115 | "TM": `^\d{6}$`, 116 | "UA": `^\d{5}$`, 117 | "UY": `^\d{5}$`, 118 | "UZ": `^\d{6}$`, 119 | "VA": `^00120$`, 120 | "VE": `^\d{4}$`, 121 | "ZM": `^\d{5}$`, 122 | "AS": `^96799$`, 123 | "CC": `^6799$`, 124 | "CK": `^\d{4}$`, 125 | "RS": `^\d{6}$`, 126 | "ME": `^8\d{4}$`, 127 | "CS": `^\d{5}$`, 128 | "YU": `^\d{5}$`, 129 | "CX": `^6798$`, 130 | "ET": `^\d{4}$`, 131 | "FK": `^FIQQ 1ZZ$`, 132 | "NF": `^2899$`, 133 | "FM": `^(9694[1-4])([ \-]\d{4})?$`, 134 | "GF": `^9[78]3\d{2}$`, 135 | "GN": `^\d{3}$`, 136 | "GP": `^9[78][01]\d{2}$`, 137 | "GS": `^SIQQ 1ZZ$`, 138 | "GU": `^969[123]\d([ \-]\d{4})?$`, 139 | "GW": `^\d{4}$`, 140 | "HM": `^\d{4}$`, 141 | "IQ": `^\d{5}$`, 142 | "KG": `^\d{6}$`, 143 | "LR": `^\d{4}$`, 144 | "LS": `^\d{3}$`, 145 | "MG": `^\d{3}$`, 146 | "MH": `^969[67]\d([ \-]\d{4})?$`, 147 | "MN": `^\d{6}$`, 148 | "MP": `^9695[012]([ \-]\d{4})?$`, 149 | "MQ": `^9[78]2\d{2}$`, 150 | "NC": `^988\d{2}$`, 151 | "NE": `^\d{4}$`, 152 | "VI": `^008(([0-4]\d)|(5[01]))([ \-]\d{4})?$`, 153 | "VN": `^[0-9]{1,6}$`, 154 | "PF": `^987\d{2}$`, 155 | "PG": `^\d{3}$`, 156 | "PM": `^9[78]5\d{2}$`, 157 | "PN": `^PCRN 1ZZ$`, 158 | "PW": `^96940$`, 159 | "RE": `^9[78]4\d{2}$`, 160 | "SH": `^(ASCN|STHL) 1ZZ$`, 161 | "SJ": `^\d{4}$`, 162 | "SO": `^\d{5}$`, 163 | "SZ": `^[HLMS]\d{3}$`, 164 | "TC": `^TKCA 1ZZ$`, 165 | "WF": `^986\d{2}$`, 166 | "XK": `^\d{5}$`, 167 | "YT": `^976\d{2}$`, 168 | } 169 | 170 | var ( 171 | postcodeRegexInit sync.Once 172 | postCodeRegexDict = map[string]*regexp.Regexp{} 173 | ) 174 | 175 | func initPostcodes() { 176 | for countryCode, pattern := range postCodePatternDict { 177 | postCodeRegexDict[countryCode] = regexp.MustCompile(pattern) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /regexes.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "regexp" 5 | "sync" 6 | ) 7 | 8 | const ( 9 | alphaRegexString = "^[a-zA-Z]+$" 10 | alphaNumericRegexString = "^[a-zA-Z0-9]+$" 11 | alphaUnicodeRegexString = "^[\\p{L}]+$" 12 | alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" 13 | numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" 14 | numberRegexString = "^[0-9]+$" 15 | hexadecimalRegexString = "^(0[xX])?[0-9a-fA-F]+$" 16 | hexColorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" 17 | rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" 18 | rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" 19 | hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" 20 | hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" 21 | emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" 22 | e164RegexString = "^\\+[1-9]?[0-9]{7,14}$" 23 | base32RegexString = "^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=|[A-Z2-7]{8})$" 24 | base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" 25 | base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" 26 | base64RawURLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2,4})$" 27 | iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" 28 | iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" 29 | iSSNRegexString = "^(?:[0-9]{4}-[0-9]{3}[0-9X])$" 30 | uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" 31 | uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" 32 | uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" 33 | uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" 34 | uUID3RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-3[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" 35 | uUID4RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" 36 | uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" 37 | uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" 38 | uLIDRegexString = "^(?i)[A-HJKMNP-TV-Z0-9]{26}$" 39 | md4RegexString = "^[0-9a-f]{32}$" 40 | md5RegexString = "^[0-9a-f]{32}$" 41 | sha256RegexString = "^[0-9a-f]{64}$" 42 | sha384RegexString = "^[0-9a-f]{96}$" 43 | sha512RegexString = "^[0-9a-f]{128}$" 44 | ripemd128RegexString = "^[0-9a-f]{32}$" 45 | ripemd160RegexString = "^[0-9a-f]{40}$" 46 | tiger128RegexString = "^[0-9a-f]{32}$" 47 | tiger160RegexString = "^[0-9a-f]{40}$" 48 | tiger192RegexString = "^[0-9a-f]{48}$" 49 | aSCIIRegexString = "^[\x00-\x7F]*$" 50 | printableASCIIRegexString = "^[\x20-\x7E]*$" 51 | multibyteRegexString = "[^\x00-\x7F]" 52 | dataURIRegexString = `^data:((?:\w+\/(?:([^;]|;[^;]).)+)?)` 53 | latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" 54 | longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" 55 | sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$` 56 | hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 57 | hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 58 | fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.') 59 | btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address 60 | btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 61 | btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 62 | ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` 63 | ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` 64 | ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` 65 | uRLEncodedRegexString = `^(?:[^%]|%[0-9A-Fa-f]{2})*$` 66 | hTMLEncodedRegexString = `&#[x]?([0-9a-fA-F]{2})|(>)|(<)|(")|(&)+[;]?` 67 | hTMLRegexString = `<[/]?([a-zA-Z]+).*?>` 68 | jWTRegexString = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$" 69 | splitParamsRegexString = `'[^']*'|\S+` 70 | bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` 71 | semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/ 72 | dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9])?$" 73 | cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html 74 | mongodbIdRegexString = "^[a-f\\d]{24}$" 75 | mongodbConnStringRegexString = "^mongodb(\\+srv)?:\\/\\/(([a-zA-Z\\d]+):([a-zA-Z\\d$:\\/?#\\[\\]@]+)@)?(([a-z\\d.-]+)(:[\\d]+)?)((,(([a-z\\d.-]+)(:(\\d+))?))*)?(\\/[a-zA-Z-_]{1,64})?(\\?(([a-zA-Z]+)=([a-zA-Z\\d]+))(&(([a-zA-Z\\d]+)=([a-zA-Z\\d]+))?)*)?$" 76 | cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|((\*|\d+)(\/|-)\d+)|\d+|\*) ?){5,7})` 77 | spicedbIDRegexString = `^(([a-zA-Z0-9/_|\-=+]{1,})|\*)$` 78 | spicedbPermissionRegexString = "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 79 | spicedbTypeRegexString = "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)?[a-z][a-z0-9_]{1,62}[a-z0-9]$" 80 | einRegexString = "^(\\d{2}-\\d{7})$" 81 | ) 82 | 83 | func lazyRegexCompile(str string) func() *regexp.Regexp { 84 | var regex *regexp.Regexp 85 | var once sync.Once 86 | return func() *regexp.Regexp { 87 | once.Do(func() { 88 | regex = regexp.MustCompile(str) 89 | }) 90 | return regex 91 | } 92 | } 93 | 94 | var ( 95 | alphaRegex = lazyRegexCompile(alphaRegexString) 96 | alphaNumericRegex = lazyRegexCompile(alphaNumericRegexString) 97 | alphaUnicodeRegex = lazyRegexCompile(alphaUnicodeRegexString) 98 | alphaUnicodeNumericRegex = lazyRegexCompile(alphaUnicodeNumericRegexString) 99 | numericRegex = lazyRegexCompile(numericRegexString) 100 | numberRegex = lazyRegexCompile(numberRegexString) 101 | hexadecimalRegex = lazyRegexCompile(hexadecimalRegexString) 102 | hexColorRegex = lazyRegexCompile(hexColorRegexString) 103 | rgbRegex = lazyRegexCompile(rgbRegexString) 104 | rgbaRegex = lazyRegexCompile(rgbaRegexString) 105 | hslRegex = lazyRegexCompile(hslRegexString) 106 | hslaRegex = lazyRegexCompile(hslaRegexString) 107 | e164Regex = lazyRegexCompile(e164RegexString) 108 | emailRegex = lazyRegexCompile(emailRegexString) 109 | base32Regex = lazyRegexCompile(base32RegexString) 110 | base64Regex = lazyRegexCompile(base64RegexString) 111 | base64URLRegex = lazyRegexCompile(base64URLRegexString) 112 | base64RawURLRegex = lazyRegexCompile(base64RawURLRegexString) 113 | iSBN10Regex = lazyRegexCompile(iSBN10RegexString) 114 | iSBN13Regex = lazyRegexCompile(iSBN13RegexString) 115 | iSSNRegex = lazyRegexCompile(iSSNRegexString) 116 | uUID3Regex = lazyRegexCompile(uUID3RegexString) 117 | uUID4Regex = lazyRegexCompile(uUID4RegexString) 118 | uUID5Regex = lazyRegexCompile(uUID5RegexString) 119 | uUIDRegex = lazyRegexCompile(uUIDRegexString) 120 | uUID3RFC4122Regex = lazyRegexCompile(uUID3RFC4122RegexString) 121 | uUID4RFC4122Regex = lazyRegexCompile(uUID4RFC4122RegexString) 122 | uUID5RFC4122Regex = lazyRegexCompile(uUID5RFC4122RegexString) 123 | uUIDRFC4122Regex = lazyRegexCompile(uUIDRFC4122RegexString) 124 | uLIDRegex = lazyRegexCompile(uLIDRegexString) 125 | md4Regex = lazyRegexCompile(md4RegexString) 126 | md5Regex = lazyRegexCompile(md5RegexString) 127 | sha256Regex = lazyRegexCompile(sha256RegexString) 128 | sha384Regex = lazyRegexCompile(sha384RegexString) 129 | sha512Regex = lazyRegexCompile(sha512RegexString) 130 | ripemd128Regex = lazyRegexCompile(ripemd128RegexString) 131 | ripemd160Regex = lazyRegexCompile(ripemd160RegexString) 132 | tiger128Regex = lazyRegexCompile(tiger128RegexString) 133 | tiger160Regex = lazyRegexCompile(tiger160RegexString) 134 | tiger192Regex = lazyRegexCompile(tiger192RegexString) 135 | aSCIIRegex = lazyRegexCompile(aSCIIRegexString) 136 | printableASCIIRegex = lazyRegexCompile(printableASCIIRegexString) 137 | multibyteRegex = lazyRegexCompile(multibyteRegexString) 138 | dataURIRegex = lazyRegexCompile(dataURIRegexString) 139 | latitudeRegex = lazyRegexCompile(latitudeRegexString) 140 | longitudeRegex = lazyRegexCompile(longitudeRegexString) 141 | sSNRegex = lazyRegexCompile(sSNRegexString) 142 | hostnameRegexRFC952 = lazyRegexCompile(hostnameRegexStringRFC952) 143 | hostnameRegexRFC1123 = lazyRegexCompile(hostnameRegexStringRFC1123) 144 | fqdnRegexRFC1123 = lazyRegexCompile(fqdnRegexStringRFC1123) 145 | btcAddressRegex = lazyRegexCompile(btcAddressRegexString) 146 | btcUpperAddressRegexBech32 = lazyRegexCompile(btcAddressUpperRegexStringBech32) 147 | btcLowerAddressRegexBech32 = lazyRegexCompile(btcAddressLowerRegexStringBech32) 148 | ethAddressRegex = lazyRegexCompile(ethAddressRegexString) 149 | uRLEncodedRegex = lazyRegexCompile(uRLEncodedRegexString) 150 | hTMLEncodedRegex = lazyRegexCompile(hTMLEncodedRegexString) 151 | hTMLRegex = lazyRegexCompile(hTMLRegexString) 152 | jWTRegex = lazyRegexCompile(jWTRegexString) 153 | splitParamsRegex = lazyRegexCompile(splitParamsRegexString) 154 | bicRegex = lazyRegexCompile(bicRegexString) 155 | semverRegex = lazyRegexCompile(semverRegexString) 156 | dnsRegexRFC1035Label = lazyRegexCompile(dnsRegexStringRFC1035Label) 157 | cveRegex = lazyRegexCompile(cveRegexString) 158 | mongodbIdRegex = lazyRegexCompile(mongodbIdRegexString) 159 | mongodbConnectionRegex = lazyRegexCompile(mongodbConnStringRegexString) 160 | cronRegex = lazyRegexCompile(cronRegexString) 161 | spicedbIDRegex = lazyRegexCompile(spicedbIDRegexString) 162 | spicedbPermissionRegex = lazyRegexCompile(spicedbPermissionRegexString) 163 | spicedbTypeRegex = lazyRegexCompile(spicedbTypeRegexString) 164 | einRegex = lazyRegexCompile(einRegexString) 165 | ) 166 | -------------------------------------------------------------------------------- /struct_level.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | ) 7 | 8 | // StructLevelFunc accepts all values needed for struct level validation 9 | type StructLevelFunc func(sl StructLevel) 10 | 11 | // StructLevelFuncCtx accepts all values needed for struct level validation 12 | // but also allows passing of contextual validation information via context.Context. 13 | type StructLevelFuncCtx func(ctx context.Context, sl StructLevel) 14 | 15 | // wrapStructLevelFunc wraps normal StructLevelFunc makes it compatible with StructLevelFuncCtx 16 | func wrapStructLevelFunc(fn StructLevelFunc) StructLevelFuncCtx { 17 | return func(ctx context.Context, sl StructLevel) { 18 | fn(sl) 19 | } 20 | } 21 | 22 | // StructLevel contains all the information and helper functions 23 | // to validate a struct 24 | type StructLevel interface { 25 | 26 | // Validator returns the main validation object, in case one wants to call validations internally. 27 | // this is so you don't have to use anonymous functions to get access to the validate 28 | // instance. 29 | Validator() *Validate 30 | 31 | // Top returns the top level struct, if any 32 | Top() reflect.Value 33 | 34 | // Parent returns the current fields parent struct, if any 35 | Parent() reflect.Value 36 | 37 | // Current returns the current struct. 38 | Current() reflect.Value 39 | 40 | // ExtractType gets the actual underlying type of field value. 41 | // It will dive into pointers, customTypes and return you the 42 | // underlying value and its kind. 43 | ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) 44 | 45 | // ReportError reports an error just by passing the field and tag information 46 | // 47 | // NOTES: 48 | // 49 | // fieldName and structFieldName get appended to the existing 50 | // namespace that validator is on. e.g. pass 'FirstName' or 51 | // 'Names[0]' depending on the nesting 52 | // 53 | // tag can be an existing validation tag or just something you make up 54 | // and process on the flip side it's up to you. 55 | ReportError(field interface{}, fieldName, structFieldName string, tag, param string) 56 | 57 | // ReportValidationErrors reports an error just by passing ValidationErrors 58 | // 59 | // NOTES: 60 | // 61 | // relativeNamespace and relativeActualNamespace get appended to the 62 | // existing namespace that validator is on. 63 | // e.g. pass 'User.FirstName' or 'Users[0].FirstName' depending 64 | // on the nesting. most of the time they will be blank, unless you validate 65 | // at a level lower the current field depth 66 | ReportValidationErrors(relativeNamespace, relativeActualNamespace string, errs ValidationErrors) 67 | } 68 | 69 | var _ StructLevel = new(validate) 70 | 71 | // Top returns the top level struct 72 | // 73 | // NOTE: this can be the same as the current struct being validated 74 | // if not is a nested struct. 75 | // 76 | // this is only called when within Struct and Field Level validation and 77 | // should not be relied upon for an accurate value otherwise. 78 | func (v *validate) Top() reflect.Value { 79 | return v.top 80 | } 81 | 82 | // Parent returns the current structs parent 83 | // 84 | // NOTE: this can be the same as the current struct being validated 85 | // if not is a nested struct. 86 | // 87 | // this is only called when within Struct and Field Level validation and 88 | // should not be relied upon for an accurate value otherwise. 89 | func (v *validate) Parent() reflect.Value { 90 | return v.slflParent 91 | } 92 | 93 | // Current returns the current struct. 94 | func (v *validate) Current() reflect.Value { 95 | return v.slCurrent 96 | } 97 | 98 | // Validator returns the main validation object, in case one want to call validations internally. 99 | func (v *validate) Validator() *Validate { 100 | return v.v 101 | } 102 | 103 | // ExtractType gets the actual underlying type of field value. 104 | func (v *validate) ExtractType(field reflect.Value) (reflect.Value, reflect.Kind, bool) { 105 | return v.extractTypeInternal(field, false) 106 | } 107 | 108 | // ReportError reports an error just by passing the field and tag information 109 | func (v *validate) ReportError(field interface{}, fieldName, structFieldName, tag, param string) { 110 | fv, kind, _ := v.extractTypeInternal(reflect.ValueOf(field), false) 111 | 112 | if len(structFieldName) == 0 { 113 | structFieldName = fieldName 114 | } 115 | 116 | v.str1 = string(append(v.ns, fieldName...)) 117 | 118 | if v.v.hasTagNameFunc || fieldName != structFieldName { 119 | v.str2 = string(append(v.actualNs, structFieldName...)) 120 | } else { 121 | v.str2 = v.str1 122 | } 123 | 124 | if kind == reflect.Invalid { 125 | v.errs = append(v.errs, 126 | &fieldError{ 127 | v: v.v, 128 | tag: tag, 129 | actualTag: tag, 130 | ns: v.str1, 131 | structNs: v.str2, 132 | fieldLen: uint8(len(fieldName)), 133 | structfieldLen: uint8(len(structFieldName)), 134 | param: param, 135 | kind: kind, 136 | }, 137 | ) 138 | return 139 | } 140 | 141 | v.errs = append(v.errs, 142 | &fieldError{ 143 | v: v.v, 144 | tag: tag, 145 | actualTag: tag, 146 | ns: v.str1, 147 | structNs: v.str2, 148 | fieldLen: uint8(len(fieldName)), 149 | structfieldLen: uint8(len(structFieldName)), 150 | value: getValue(fv), 151 | param: param, 152 | kind: kind, 153 | typ: fv.Type(), 154 | }, 155 | ) 156 | } 157 | 158 | // ReportValidationErrors reports ValidationErrors obtained from running validations within the Struct Level validation. 159 | // 160 | // NOTE: this function prepends the current namespace to the relative ones. 161 | func (v *validate) ReportValidationErrors(relativeNamespace, relativeStructNamespace string, errs ValidationErrors) { 162 | var err *fieldError 163 | 164 | for i := 0; i < len(errs); i++ { 165 | err = errs[i].(*fieldError) 166 | err.ns = string(append(append(v.ns, relativeNamespace...), err.ns...)) 167 | err.structNs = string(append(append(v.actualNs, relativeStructNamespace...), err.structNs...)) 168 | 169 | v.errs = append(v.errs, err) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /testdata/a.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | -------------------------------------------------------------------------------- /testdata/music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-playground/validator/5b9542b93487972cdfa75ed03ebe4286c3f44c01/testdata/music.mp3 -------------------------------------------------------------------------------- /translations.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ut "github.com/go-playground/universal-translator" 4 | 5 | // TranslationFunc is the function type used to register or override 6 | // custom translations 7 | type TranslationFunc func(ut ut.Translator, fe FieldError) string 8 | 9 | // RegisterTranslationsFunc allows for registering of translations 10 | // for a 'ut.Translator' for use within the 'TranslationFunc' 11 | type RegisterTranslationsFunc func(ut ut.Translator) error 12 | -------------------------------------------------------------------------------- /translations/fr/fr_test.go: -------------------------------------------------------------------------------- 1 | package fr 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/go-playground/assert/v2" 8 | french "github.com/go-playground/locales/fr" 9 | ut "github.com/go-playground/universal-translator" 10 | "github.com/go-playground/validator/v10" 11 | ) 12 | 13 | func TestTranslations(t *testing.T) { 14 | fre := french.New() 15 | uni := ut.New(fre, fre) 16 | trans, _ := uni.GetTranslator("fr") 17 | 18 | validate := validator.New() 19 | 20 | err := RegisterDefaultTranslations(validate, trans) 21 | Equal(t, err, nil) 22 | 23 | type Inner struct { 24 | EqCSFieldString string 25 | NeCSFieldString string 26 | GtCSFieldString string 27 | GteCSFieldString string 28 | LtCSFieldString string 29 | LteCSFieldString string 30 | } 31 | 32 | type Test struct { 33 | Inner Inner 34 | RequiredString string `validate:"required"` 35 | RequiredNumber int `validate:"required"` 36 | RequiredMultiple []string `validate:"required"` 37 | LenString string `validate:"len=1"` 38 | LenNumber float64 `validate:"len=1113.00"` 39 | LenMultiple []string `validate:"len=7"` 40 | MinString string `validate:"min=1"` 41 | MinNumber float64 `validate:"min=1113.00"` 42 | MinMultiple []string `validate:"min=7"` 43 | MaxString string `validate:"max=3"` 44 | MaxNumber float64 `validate:"max=1113.00"` 45 | MaxMultiple []string `validate:"max=7"` 46 | EqString string `validate:"eq=3"` 47 | EqNumber float64 `validate:"eq=2.33"` 48 | EqMultiple []string `validate:"eq=7"` 49 | NeString string `validate:"ne="` 50 | NeNumber float64 `validate:"ne=0.00"` 51 | NeMultiple []string `validate:"ne=0"` 52 | LtString string `validate:"lt=3"` 53 | LtNumber float64 `validate:"lt=5.56"` 54 | LtMultiple []string `validate:"lt=2"` 55 | LtTime time.Time `validate:"lt"` 56 | LteString string `validate:"lte=3"` 57 | LteNumber float64 `validate:"lte=5.56"` 58 | LteMultiple []string `validate:"lte=2"` 59 | LteTime time.Time `validate:"lte"` 60 | GtString string `validate:"gt=3"` 61 | GtNumber float64 `validate:"gt=5.56"` 62 | GtMultiple []string `validate:"gt=2"` 63 | GtTime time.Time `validate:"gt"` 64 | GteString string `validate:"gte=3"` 65 | GteNumber float64 `validate:"gte=5.56"` 66 | GteMultiple []string `validate:"gte=2"` 67 | GteTime time.Time `validate:"gte"` 68 | EqFieldString string `validate:"eqfield=MaxString"` 69 | EqCSFieldString string `validate:"eqcsfield=Inner.EqCSFieldString"` 70 | NeCSFieldString string `validate:"necsfield=Inner.NeCSFieldString"` 71 | GtCSFieldString string `validate:"gtcsfield=Inner.GtCSFieldString"` 72 | GteCSFieldString string `validate:"gtecsfield=Inner.GteCSFieldString"` 73 | LtCSFieldString string `validate:"ltcsfield=Inner.LtCSFieldString"` 74 | LteCSFieldString string `validate:"ltecsfield=Inner.LteCSFieldString"` 75 | NeFieldString string `validate:"nefield=EqFieldString"` 76 | GtFieldString string `validate:"gtfield=MaxString"` 77 | GteFieldString string `validate:"gtefield=MaxString"` 78 | LtFieldString string `validate:"ltfield=MaxString"` 79 | LteFieldString string `validate:"ltefield=MaxString"` 80 | AlphaString string `validate:"alpha"` 81 | AlphanumString string `validate:"alphanum"` 82 | NumericString string `validate:"numeric"` 83 | NumberString string `validate:"number"` 84 | HexadecimalString string `validate:"hexadecimal"` 85 | HexColorString string `validate:"hexcolor"` 86 | RGBColorString string `validate:"rgb"` 87 | RGBAColorString string `validate:"rgba"` 88 | HSLColorString string `validate:"hsl"` 89 | HSLAColorString string `validate:"hsla"` 90 | Email string `validate:"email"` 91 | URL string `validate:"url"` 92 | URI string `validate:"uri"` 93 | Base64 string `validate:"base64"` 94 | Contains string `validate:"contains=purpose"` 95 | ContainsAny string `validate:"containsany=!@#$"` 96 | Excludes string `validate:"excludes=text"` 97 | ExcludesAll string `validate:"excludesall=!@#$"` 98 | ExcludesRune string `validate:"excludesrune=☻"` 99 | ISBN string `validate:"isbn"` 100 | ISBN10 string `validate:"isbn10"` 101 | ISBN13 string `validate:"isbn13"` 102 | ISSN string `validate:"issn"` 103 | UUID string `validate:"uuid"` 104 | UUID3 string `validate:"uuid3"` 105 | UUID4 string `validate:"uuid4"` 106 | UUID5 string `validate:"uuid5"` 107 | ULID string `validate:"ulid"` 108 | ASCII string `validate:"ascii"` 109 | PrintableASCII string `validate:"printascii"` 110 | MultiByte string `validate:"multibyte"` 111 | DataURI string `validate:"datauri"` 112 | Latitude string `validate:"latitude"` 113 | Longitude string `validate:"longitude"` 114 | SSN string `validate:"ssn"` 115 | IP string `validate:"ip"` 116 | IPv4 string `validate:"ipv4"` 117 | IPv6 string `validate:"ipv6"` 118 | CIDR string `validate:"cidr"` 119 | CIDRv4 string `validate:"cidrv4"` 120 | CIDRv6 string `validate:"cidrv6"` 121 | TCPAddr string `validate:"tcp_addr"` 122 | TCPAddrv4 string `validate:"tcp4_addr"` 123 | TCPAddrv6 string `validate:"tcp6_addr"` 124 | UDPAddr string `validate:"udp_addr"` 125 | UDPAddrv4 string `validate:"udp4_addr"` 126 | UDPAddrv6 string `validate:"udp6_addr"` 127 | IPAddr string `validate:"ip_addr"` 128 | IPAddrv4 string `validate:"ip4_addr"` 129 | IPAddrv6 string `validate:"ip6_addr"` 130 | UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future 131 | MAC string `validate:"mac"` 132 | IsColor string `validate:"iscolor"` 133 | StrPtrMinLen *string `validate:"min=10"` 134 | StrPtrMaxLen *string `validate:"max=1"` 135 | StrPtrLen *string `validate:"len=2"` 136 | StrPtrLt *string `validate:"lt=1"` 137 | StrPtrLte *string `validate:"lte=1"` 138 | StrPtrGt *string `validate:"gt=10"` 139 | StrPtrGte *string `validate:"gte=10"` 140 | OneOfString string `validate:"oneof=red green"` 141 | OneOfInt int `validate:"oneof=5 63"` 142 | Image string `validate:"image"` 143 | } 144 | 145 | var test Test 146 | 147 | test.Inner.EqCSFieldString = "1234" 148 | test.Inner.GtCSFieldString = "1234" 149 | test.Inner.GteCSFieldString = "1234" 150 | 151 | test.MaxString = "1234" 152 | test.MaxNumber = 2000 153 | test.MaxMultiple = make([]string, 9) 154 | 155 | test.LtString = "1234" 156 | test.LtNumber = 6 157 | test.LtMultiple = make([]string, 3) 158 | test.LtTime = time.Now().Add(time.Hour * 24) 159 | 160 | test.LteString = "1234" 161 | test.LteNumber = 6 162 | test.LteMultiple = make([]string, 3) 163 | test.LteTime = time.Now().Add(time.Hour * 24) 164 | 165 | test.LtFieldString = "12345" 166 | test.LteFieldString = "12345" 167 | 168 | test.LtCSFieldString = "1234" 169 | test.LteCSFieldString = "1234" 170 | 171 | test.AlphaString = "abc3" 172 | test.AlphanumString = "abc3!" 173 | test.NumericString = "12E.00" 174 | test.NumberString = "12E" 175 | 176 | test.Excludes = "this is some test text" 177 | test.ExcludesAll = "This is Great!" 178 | test.ExcludesRune = "Love it ☻" 179 | 180 | test.ASCII = "カタカナ" 181 | test.PrintableASCII = "カタカナ" 182 | 183 | test.MultiByte = "1234feerf" 184 | 185 | s := "toolong" 186 | test.StrPtrMaxLen = &s 187 | test.StrPtrLen = &s 188 | 189 | err = validate.Struct(test) 190 | NotEqual(t, err, nil) 191 | 192 | errs, ok := err.(validator.ValidationErrors) 193 | Equal(t, ok, true) 194 | 195 | tests := []struct { 196 | ns string 197 | expected string 198 | }{ 199 | { 200 | ns: "Test.IsColor", 201 | expected: "IsColor doit être une couleur valide", 202 | }, 203 | { 204 | ns: "Test.MAC", 205 | expected: "MAC doit contenir une adresse MAC valide", 206 | }, 207 | { 208 | ns: "Test.IPAddr", 209 | expected: "IPAddr doit être une adresse IP résolvable", 210 | }, 211 | { 212 | ns: "Test.IPAddrv4", 213 | expected: "IPAddrv4 doit être une adresse IPv4 résolvable", 214 | }, 215 | { 216 | ns: "Test.IPAddrv6", 217 | expected: "IPAddrv6 doit être une adresse IPv6 résolvable", 218 | }, 219 | { 220 | ns: "Test.UDPAddr", 221 | expected: "UDPAddr doit être une adressse UDP valide", 222 | }, 223 | { 224 | ns: "Test.UDPAddrv4", 225 | expected: "UDPAddrv4 doit être une adressse IPv4 UDP valide", 226 | }, 227 | { 228 | ns: "Test.UDPAddrv6", 229 | expected: "UDPAddrv6 doit être une adressse IPv6 UDP valide", 230 | }, 231 | { 232 | ns: "Test.TCPAddr", 233 | expected: "TCPAddr doit être une adressse TCP valide", 234 | }, 235 | { 236 | ns: "Test.TCPAddrv4", 237 | expected: "TCPAddrv4 doit être une adressse IPv4 TCP valide", 238 | }, 239 | { 240 | ns: "Test.TCPAddrv6", 241 | expected: "TCPAddrv6 doit être une adressse IPv6 TCP valide", 242 | }, 243 | { 244 | ns: "Test.CIDR", 245 | expected: "CIDR doit contenir une notation CIDR valide", 246 | }, 247 | { 248 | ns: "Test.CIDRv4", 249 | expected: "CIDRv4 doit contenir une notation CIDR valide pour une adresse IPv4", 250 | }, 251 | { 252 | ns: "Test.CIDRv6", 253 | expected: "CIDRv6 doit contenir une notation CIDR valide pour une adresse IPv6", 254 | }, 255 | { 256 | ns: "Test.SSN", 257 | expected: "SSN doit être un numéro SSN valide", 258 | }, 259 | { 260 | ns: "Test.IP", 261 | expected: "IP doit être une adressse IP valide", 262 | }, 263 | { 264 | ns: "Test.IPv4", 265 | expected: "IPv4 doit être une adressse IPv4 valide", 266 | }, 267 | { 268 | ns: "Test.IPv6", 269 | expected: "IPv6 doit être une adressse IPv6 valide", 270 | }, 271 | { 272 | ns: "Test.DataURI", 273 | expected: "DataURI doit contenir une URI data valide", 274 | }, 275 | { 276 | ns: "Test.Latitude", 277 | expected: "Latitude doit contenir des coordonnées latitude valides", 278 | }, 279 | { 280 | ns: "Test.Longitude", 281 | expected: "Longitude doit contenir des coordonnées longitudes valides", 282 | }, 283 | { 284 | ns: "Test.MultiByte", 285 | expected: "MultiByte doit contenir des caractères multioctets", 286 | }, 287 | { 288 | ns: "Test.ASCII", 289 | expected: "ASCII ne doit contenir que des caractères ascii", 290 | }, 291 | { 292 | ns: "Test.PrintableASCII", 293 | expected: "PrintableASCII ne doit contenir que des caractères ascii affichables", 294 | }, 295 | { 296 | ns: "Test.UUID", 297 | expected: "UUID doit être un UUID valid", 298 | }, 299 | { 300 | ns: "Test.UUID3", 301 | expected: "UUID3 doit être un UUID version 3 valid", 302 | }, 303 | { 304 | ns: "Test.UUID4", 305 | expected: "UUID4 doit être un UUID version 4 valid", 306 | }, 307 | { 308 | ns: "Test.UUID5", 309 | expected: "UUID5 doit être un UUID version 5 valid", 310 | }, 311 | { 312 | ns: "Test.ULID", 313 | expected: "ULID doit être une ULID valide", 314 | }, 315 | { 316 | ns: "Test.ISBN", 317 | expected: "ISBN doit être un numéro ISBN valid", 318 | }, 319 | { 320 | ns: "Test.ISBN10", 321 | expected: "ISBN10 doit être un numéro ISBN-10 valid", 322 | }, 323 | { 324 | ns: "Test.ISBN13", 325 | expected: "ISBN13 doit être un numéro ISBN-13 valid", 326 | }, 327 | { 328 | ns: "Test.ISSN", 329 | expected: "ISSN doit être un numéro ISSN valid", 330 | }, 331 | { 332 | ns: "Test.Excludes", 333 | expected: "Excludes ne doit pas contenir le texte 'text'", 334 | }, 335 | { 336 | ns: "Test.ExcludesAll", 337 | expected: "ExcludesAll ne doit pas contenir l'un des caractères suivants '!@#$'", 338 | }, 339 | { 340 | ns: "Test.ExcludesRune", 341 | expected: "ExcludesRune ne doit pas contenir ce qui suit '☻'", 342 | }, 343 | { 344 | ns: "Test.ContainsAny", 345 | expected: "ContainsAny doit contenir au moins l' un des caractères suivants '!@#$'", 346 | }, 347 | { 348 | ns: "Test.Contains", 349 | expected: "Contains doit contenir le texte 'purpose'", 350 | }, 351 | { 352 | ns: "Test.Base64", 353 | expected: "Base64 doit être une chaîne de caractères au format Base64 valide", 354 | }, 355 | { 356 | ns: "Test.Email", 357 | expected: "Email doit être une adresse email valide", 358 | }, 359 | { 360 | ns: "Test.URL", 361 | expected: "URL doit être une URL valide", 362 | }, 363 | { 364 | ns: "Test.URI", 365 | expected: "URI doit être une URI valide", 366 | }, 367 | { 368 | ns: "Test.RGBColorString", 369 | expected: "RGBColorString doit être une couleur au format RGB valide", 370 | }, 371 | { 372 | ns: "Test.RGBAColorString", 373 | expected: "RGBAColorString doit être une couleur au format RGBA valide", 374 | }, 375 | { 376 | ns: "Test.HSLColorString", 377 | expected: "HSLColorString doit être une couleur au format HSL valide", 378 | }, 379 | { 380 | ns: "Test.HSLAColorString", 381 | expected: "HSLAColorString doit être une couleur au format HSLA valide", 382 | }, 383 | { 384 | ns: "Test.HexadecimalString", 385 | expected: "HexadecimalString doit être une chaîne de caractères au format hexadécimal valide", 386 | }, 387 | { 388 | ns: "Test.HexColorString", 389 | expected: "HexColorString doit être une couleur au format HEX valide", 390 | }, 391 | { 392 | ns: "Test.NumberString", 393 | expected: "NumberString doit être un nombre valid", 394 | }, 395 | { 396 | ns: "Test.NumericString", 397 | expected: "NumericString doit être une valeur numérique valide", 398 | }, 399 | { 400 | ns: "Test.AlphanumString", 401 | expected: "AlphanumString ne doit contenir que des caractères alphanumériques", 402 | }, 403 | { 404 | ns: "Test.AlphaString", 405 | expected: "AlphaString ne doit contenir que des caractères alphabétiques", 406 | }, 407 | { 408 | ns: "Test.LtFieldString", 409 | expected: "LtFieldString doit être inférieur à MaxString", 410 | }, 411 | { 412 | ns: "Test.LteFieldString", 413 | expected: "LteFieldString doit être inférieur ou égal à MaxString", 414 | }, 415 | { 416 | ns: "Test.GtFieldString", 417 | expected: "GtFieldString doit être supérieur à MaxString", 418 | }, 419 | { 420 | ns: "Test.GteFieldString", 421 | expected: "GteFieldString doit être supérieur ou égal à MaxString", 422 | }, 423 | { 424 | ns: "Test.NeFieldString", 425 | expected: "NeFieldString ne doit pas être égal à EqFieldString", 426 | }, 427 | { 428 | ns: "Test.LtCSFieldString", 429 | expected: "LtCSFieldString doit être inférieur à Inner.LtCSFieldString", 430 | }, 431 | { 432 | ns: "Test.LteCSFieldString", 433 | expected: "LteCSFieldString doit être inférieur ou égal à Inner.LteCSFieldString", 434 | }, 435 | { 436 | ns: "Test.GtCSFieldString", 437 | expected: "GtCSFieldString doit être supérieur à Inner.GtCSFieldString", 438 | }, 439 | { 440 | ns: "Test.GteCSFieldString", 441 | expected: "GteCSFieldString doit être supérieur ou égal à Inner.GteCSFieldString", 442 | }, 443 | { 444 | ns: "Test.NeCSFieldString", 445 | expected: "NeCSFieldString ne doit pas être égal à Inner.NeCSFieldString", 446 | }, 447 | { 448 | ns: "Test.EqCSFieldString", 449 | expected: "EqCSFieldString doit être égal à Inner.EqCSFieldString", 450 | }, 451 | { 452 | ns: "Test.EqFieldString", 453 | expected: "EqFieldString doit être égal à MaxString", 454 | }, 455 | { 456 | ns: "Test.GteString", 457 | expected: "GteString doit faire une taille d'au moins 3 caractères", 458 | }, 459 | { 460 | ns: "Test.GteNumber", 461 | expected: "GteNumber doit être 5,56 ou plus", 462 | }, 463 | { 464 | ns: "Test.GteMultiple", 465 | expected: "GteMultiple doit contenir au moins 2 elements", 466 | }, 467 | { 468 | ns: "Test.GteTime", 469 | expected: "GteTime doit être après ou pendant la date et l'heure actuelle", 470 | }, 471 | { 472 | ns: "Test.GtString", 473 | expected: "GtString doit avoir une taille supérieur à 3 caractères", 474 | }, 475 | { 476 | ns: "Test.GtNumber", 477 | expected: "GtNumber doit être supérieur à 5,56", 478 | }, 479 | { 480 | ns: "Test.GtMultiple", 481 | expected: "GtMultiple doit contenir plus de 2 elements", 482 | }, 483 | { 484 | ns: "Test.GtTime", 485 | expected: "GtTime doit être après la date et l'heure actuelle", 486 | }, 487 | { 488 | ns: "Test.LteString", 489 | expected: "LteString doit faire une taille maximum de 3 caractères", 490 | }, 491 | { 492 | ns: "Test.LteNumber", 493 | expected: "LteNumber doit faire 5,56 ou moins", 494 | }, 495 | { 496 | ns: "Test.LteMultiple", 497 | expected: "LteMultiple doit contenir un maximum de 2 elements", 498 | }, 499 | { 500 | ns: "Test.LteTime", 501 | expected: "LteTime doit être avant ou pendant la date et l'heure actuelle", 502 | }, 503 | { 504 | ns: "Test.LtString", 505 | expected: "LtString doit avoir une taille inférieure à 3 caractères", 506 | }, 507 | { 508 | ns: "Test.LtNumber", 509 | expected: "LtNumber doit être inférieur à 5,56", 510 | }, 511 | { 512 | ns: "Test.LtMultiple", 513 | expected: "LtMultiple doit contenir mois de 2 elements", 514 | }, 515 | { 516 | ns: "Test.LtTime", 517 | expected: "LtTime doit être avant la date et l'heure actuelle", 518 | }, 519 | { 520 | ns: "Test.NeString", 521 | expected: "NeString ne doit pas être égal à ", 522 | }, 523 | { 524 | ns: "Test.NeNumber", 525 | expected: "NeNumber ne doit pas être égal à 0.00", 526 | }, 527 | { 528 | ns: "Test.NeMultiple", 529 | expected: "NeMultiple ne doit pas être égal à 0", 530 | }, 531 | { 532 | ns: "Test.EqString", 533 | expected: "EqString n'est pas égal à 3", 534 | }, 535 | { 536 | ns: "Test.EqNumber", 537 | expected: "EqNumber n'est pas égal à 2.33", 538 | }, 539 | { 540 | ns: "Test.EqMultiple", 541 | expected: "EqMultiple n'est pas égal à 7", 542 | }, 543 | { 544 | ns: "Test.MaxString", 545 | expected: "MaxString doit faire une taille maximum de 3 caractères", 546 | }, 547 | { 548 | ns: "Test.MaxNumber", 549 | expected: "MaxNumber doit être égal à 1 113,00 ou moins", 550 | }, 551 | { 552 | ns: "Test.MaxMultiple", 553 | expected: "MaxMultiple doit contenir au maximum 7 elements", 554 | }, 555 | { 556 | ns: "Test.MinString", 557 | expected: "MinString doit faire une taille minimum de 1 caractère", 558 | }, 559 | { 560 | ns: "Test.MinNumber", 561 | expected: "MinNumber doit être égal à 1 113,00 ou plus", 562 | }, 563 | { 564 | ns: "Test.MinMultiple", 565 | expected: "MinMultiple doit contenir au moins 7 elements", 566 | }, 567 | { 568 | ns: "Test.LenString", 569 | expected: "LenString doit faire une taille de 1 caractère", 570 | }, 571 | { 572 | ns: "Test.LenNumber", 573 | expected: "LenNumber doit être égal à 1 113,00", 574 | }, 575 | { 576 | ns: "Test.LenMultiple", 577 | expected: "LenMultiple doit contenir 7 elements", 578 | }, 579 | { 580 | ns: "Test.RequiredString", 581 | expected: "RequiredString est un champ obligatoire", 582 | }, 583 | { 584 | ns: "Test.RequiredNumber", 585 | expected: "RequiredNumber est un champ obligatoire", 586 | }, 587 | { 588 | ns: "Test.RequiredMultiple", 589 | expected: "RequiredMultiple est un champ obligatoire", 590 | }, 591 | { 592 | ns: "Test.StrPtrMinLen", 593 | expected: "StrPtrMinLen doit faire une taille minimum de 10 caractères", 594 | }, 595 | { 596 | ns: "Test.StrPtrMaxLen", 597 | expected: "StrPtrMaxLen doit faire une taille maximum de 1 caractère", 598 | }, 599 | { 600 | ns: "Test.StrPtrLen", 601 | expected: "StrPtrLen doit faire une taille de 2 caractères", 602 | }, 603 | { 604 | ns: "Test.StrPtrLt", 605 | expected: "StrPtrLt doit avoir une taille inférieure à 1 caractère", 606 | }, 607 | { 608 | ns: "Test.StrPtrLte", 609 | expected: "StrPtrLte doit faire une taille maximum de 1 caractère", 610 | }, 611 | { 612 | ns: "Test.StrPtrGt", 613 | expected: "StrPtrGt doit avoir une taille supérieur à 10 caractères", 614 | }, 615 | { 616 | ns: "Test.StrPtrGte", 617 | expected: "StrPtrGte doit faire une taille d'au moins 10 caractères", 618 | }, 619 | { 620 | ns: "Test.OneOfString", 621 | expected: "OneOfString doit être l'un des choix suivants [red green]", 622 | }, 623 | { 624 | ns: "Test.OneOfInt", 625 | expected: "OneOfInt doit être l'un des choix suivants [5 63]", 626 | }, 627 | { 628 | ns: "Test.Image", 629 | expected: "Image doit être une image valide", 630 | }, 631 | } 632 | 633 | for _, tt := range tests { 634 | var fe validator.FieldError 635 | 636 | for _, e := range errs { 637 | if tt.ns == e.Namespace() { 638 | fe = e 639 | break 640 | } 641 | } 642 | 643 | NotEqual(t, fe, nil) 644 | Equal(t, tt.expected, fe.Translate(trans)) 645 | } 646 | } 647 | -------------------------------------------------------------------------------- /translations/nl/nl_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/go-playground/assert/v2" 8 | english "github.com/go-playground/locales/en" 9 | ut "github.com/go-playground/universal-translator" 10 | "github.com/go-playground/validator/v10" 11 | ) 12 | 13 | func TestTranslations(t *testing.T) { 14 | eng := english.New() 15 | uni := ut.New(eng, eng) 16 | trans, _ := uni.GetTranslator("en") 17 | 18 | validate := validator.New() 19 | 20 | err := RegisterDefaultTranslations(validate, trans) 21 | Equal(t, err, nil) 22 | 23 | type Inner struct { 24 | EqCSFieldString string 25 | NeCSFieldString string 26 | GtCSFieldString string 27 | GteCSFieldString string 28 | LtCSFieldString string 29 | LteCSFieldString string 30 | } 31 | 32 | type Test struct { 33 | Inner Inner 34 | RequiredString string `validate:"required"` 35 | RequiredNumber int `validate:"required"` 36 | RequiredMultiple []string `validate:"required"` 37 | LenString string `validate:"len=1"` 38 | LenNumber float64 `validate:"len=1113.00"` 39 | LenMultiple []string `validate:"len=7"` 40 | MinString string `validate:"min=1"` 41 | MinNumber float64 `validate:"min=1113.00"` 42 | MinMultiple []string `validate:"min=7"` 43 | MaxString string `validate:"max=3"` 44 | MaxNumber float64 `validate:"max=1113.00"` 45 | MaxMultiple []string `validate:"max=7"` 46 | EqString string `validate:"eq=3"` 47 | EqNumber float64 `validate:"eq=2.33"` 48 | EqMultiple []string `validate:"eq=7"` 49 | NeString string `validate:"ne="` 50 | NeNumber float64 `validate:"ne=0.00"` 51 | NeMultiple []string `validate:"ne=0"` 52 | LtString string `validate:"lt=3"` 53 | LtNumber float64 `validate:"lt=5.56"` 54 | LtMultiple []string `validate:"lt=2"` 55 | LtTime time.Time `validate:"lt"` 56 | LteString string `validate:"lte=3"` 57 | LteNumber float64 `validate:"lte=5.56"` 58 | LteMultiple []string `validate:"lte=2"` 59 | LteTime time.Time `validate:"lte"` 60 | GtString string `validate:"gt=3"` 61 | GtNumber float64 `validate:"gt=5.56"` 62 | GtMultiple []string `validate:"gt=2"` 63 | GtTime time.Time `validate:"gt"` 64 | GteString string `validate:"gte=3"` 65 | GteNumber float64 `validate:"gte=5.56"` 66 | GteMultiple []string `validate:"gte=2"` 67 | GteTime time.Time `validate:"gte"` 68 | EqFieldString string `validate:"eqfield=MaxString"` 69 | EqCSFieldString string `validate:"eqcsfield=Inner.EqCSFieldString"` 70 | NeCSFieldString string `validate:"necsfield=Inner.NeCSFieldString"` 71 | GtCSFieldString string `validate:"gtcsfield=Inner.GtCSFieldString"` 72 | GteCSFieldString string `validate:"gtecsfield=Inner.GteCSFieldString"` 73 | LtCSFieldString string `validate:"ltcsfield=Inner.LtCSFieldString"` 74 | LteCSFieldString string `validate:"ltecsfield=Inner.LteCSFieldString"` 75 | NeFieldString string `validate:"nefield=EqFieldString"` 76 | GtFieldString string `validate:"gtfield=MaxString"` 77 | GteFieldString string `validate:"gtefield=MaxString"` 78 | LtFieldString string `validate:"ltfield=MaxString"` 79 | LteFieldString string `validate:"ltefield=MaxString"` 80 | AlphaString string `validate:"alpha"` 81 | AlphanumString string `validate:"alphanum"` 82 | NumericString string `validate:"numeric"` 83 | NumberString string `validate:"number"` 84 | HexadecimalString string `validate:"hexadecimal"` 85 | HexColorString string `validate:"hexcolor"` 86 | RGBColorString string `validate:"rgb"` 87 | RGBAColorString string `validate:"rgba"` 88 | HSLColorString string `validate:"hsl"` 89 | HSLAColorString string `validate:"hsla"` 90 | Email string `validate:"email"` 91 | URL string `validate:"url"` 92 | URI string `validate:"uri"` 93 | Base64 string `validate:"base64"` 94 | Contains string `validate:"contains=purpose"` 95 | ContainsAny string `validate:"containsany=!@#$"` 96 | Excludes string `validate:"excludes=text"` 97 | ExcludesAll string `validate:"excludesall=!@#$"` 98 | ExcludesRune string `validate:"excludesrune=☻"` 99 | ISBN string `validate:"isbn"` 100 | ISBN10 string `validate:"isbn10"` 101 | ISBN13 string `validate:"isbn13"` 102 | ISSN string `validate:"issn"` 103 | UUID string `validate:"uuid"` 104 | UUID3 string `validate:"uuid3"` 105 | UUID4 string `validate:"uuid4"` 106 | UUID5 string `validate:"uuid5"` 107 | ULID string `validate:"ulid"` 108 | ASCII string `validate:"ascii"` 109 | PrintableASCII string `validate:"printascii"` 110 | MultiByte string `validate:"multibyte"` 111 | DataURI string `validate:"datauri"` 112 | Latitude string `validate:"latitude"` 113 | Longitude string `validate:"longitude"` 114 | SSN string `validate:"ssn"` 115 | IP string `validate:"ip"` 116 | IPv4 string `validate:"ipv4"` 117 | IPv6 string `validate:"ipv6"` 118 | CIDR string `validate:"cidr"` 119 | CIDRv4 string `validate:"cidrv4"` 120 | CIDRv6 string `validate:"cidrv6"` 121 | TCPAddr string `validate:"tcp_addr"` 122 | TCPAddrv4 string `validate:"tcp4_addr"` 123 | TCPAddrv6 string `validate:"tcp6_addr"` 124 | UDPAddr string `validate:"udp_addr"` 125 | UDPAddrv4 string `validate:"udp4_addr"` 126 | UDPAddrv6 string `validate:"udp6_addr"` 127 | IPAddr string `validate:"ip_addr"` 128 | IPAddrv4 string `validate:"ip4_addr"` 129 | IPAddrv6 string `validate:"ip6_addr"` 130 | UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future 131 | MAC string `validate:"mac"` 132 | IsColor string `validate:"iscolor"` 133 | StrPtrMinLen *string `validate:"min=10"` 134 | StrPtrMaxLen *string `validate:"max=1"` 135 | StrPtrLen *string `validate:"len=2"` 136 | StrPtrLt *string `validate:"lt=1"` 137 | StrPtrLte *string `validate:"lte=1"` 138 | StrPtrGt *string `validate:"gt=10"` 139 | StrPtrGte *string `validate:"gte=10"` 140 | OneOfString string `validate:"oneof=red green"` 141 | OneOfInt int `validate:"oneof=5 63"` 142 | Image string `validate:"image"` 143 | } 144 | 145 | var test Test 146 | 147 | test.Inner.EqCSFieldString = "1234" 148 | test.Inner.GtCSFieldString = "1234" 149 | test.Inner.GteCSFieldString = "1234" 150 | 151 | test.MaxString = "1234" 152 | test.MaxNumber = 2000 153 | test.MaxMultiple = make([]string, 9) 154 | 155 | test.LtString = "1234" 156 | test.LtNumber = 6 157 | test.LtMultiple = make([]string, 3) 158 | test.LtTime = time.Now().Add(time.Hour * 24) 159 | 160 | test.LteString = "1234" 161 | test.LteNumber = 6 162 | test.LteMultiple = make([]string, 3) 163 | test.LteTime = time.Now().Add(time.Hour * 24) 164 | 165 | test.LtFieldString = "12345" 166 | test.LteFieldString = "12345" 167 | 168 | test.LtCSFieldString = "1234" 169 | test.LteCSFieldString = "1234" 170 | 171 | test.AlphaString = "abc3" 172 | test.AlphanumString = "abc3!" 173 | test.NumericString = "12E.00" 174 | test.NumberString = "12E" 175 | 176 | test.Excludes = "this is some test text" 177 | test.ExcludesAll = "This is Great!" 178 | test.ExcludesRune = "Love it ☻" 179 | 180 | test.ASCII = "カタカナ" 181 | test.PrintableASCII = "カタカナ" 182 | 183 | test.MultiByte = "1234feerf" 184 | 185 | s := "toolong" 186 | test.StrPtrMaxLen = &s 187 | test.StrPtrLen = &s 188 | 189 | err = validate.Struct(test) 190 | NotEqual(t, err, nil) 191 | 192 | errs, ok := err.(validator.ValidationErrors) 193 | Equal(t, ok, true) 194 | 195 | tests := []struct { 196 | ns string 197 | expected string 198 | }{ 199 | { 200 | ns: "Test.IsColor", 201 | expected: "IsColor moet een geldige kleur zijn", 202 | }, 203 | { 204 | ns: "Test.MAC", 205 | expected: "MAC moet een geldig MAC adres bevatten", 206 | }, 207 | { 208 | ns: "Test.IPAddr", 209 | expected: "IPAddr moet een oplosbaar IP adres zijn", 210 | }, 211 | { 212 | ns: "Test.IPAddrv4", 213 | expected: "IPAddrv4 moet een oplosbaar IPv4 adres zijn", 214 | }, 215 | { 216 | ns: "Test.IPAddrv6", 217 | expected: "IPAddrv6 moet een oplosbaar IPv6 adres zijn", 218 | }, 219 | { 220 | ns: "Test.UDPAddr", 221 | expected: "UDPAddr moet een geldig UDP adres zijn", 222 | }, 223 | { 224 | ns: "Test.UDPAddrv4", 225 | expected: "UDPAddrv4 moet een geldig IPv4 UDP adres zijn", 226 | }, 227 | { 228 | ns: "Test.UDPAddrv6", 229 | expected: "UDPAddrv6 moet een geldig IPv6 UDP adres zijn", 230 | }, 231 | { 232 | ns: "Test.TCPAddr", 233 | expected: "TCPAddr moet een geldig TCP adres zijn", 234 | }, 235 | { 236 | ns: "Test.TCPAddrv4", 237 | expected: "TCPAddrv4 moet een geldig IPv4 TCP adres zijn", 238 | }, 239 | { 240 | ns: "Test.TCPAddrv6", 241 | expected: "TCPAddrv6 moet een geldig IPv6 TCP adres zijn", 242 | }, 243 | { 244 | ns: "Test.CIDR", 245 | expected: "CIDR moet een geldige CIDR notatie bevatten", 246 | }, 247 | { 248 | ns: "Test.CIDRv4", 249 | expected: "CIDRv4 moet een geldige CIDR notatie voor een IPv4 adres bevatten", 250 | }, 251 | { 252 | ns: "Test.CIDRv6", 253 | expected: "CIDRv6 moet een geldige CIDR notatie voor een IPv6 adres bevatten", 254 | }, 255 | { 256 | ns: "Test.SSN", 257 | expected: "SSN moet een geldig SSN nummer zijn", 258 | }, 259 | { 260 | ns: "Test.IP", 261 | expected: "IP moet een geldig IP adres zijn", 262 | }, 263 | { 264 | ns: "Test.IPv4", 265 | expected: "IPv4 moet een geldig IPv4 adres zijn", 266 | }, 267 | { 268 | ns: "Test.IPv6", 269 | expected: "IPv6 moet een geldig IPv6 adres zijn", 270 | }, 271 | { 272 | ns: "Test.DataURI", 273 | expected: "DataURI moet een geldige Data URI bevatten", 274 | }, 275 | { 276 | ns: "Test.Latitude", 277 | expected: "Latitude moet geldige breedtegraadcoördinaten bevatten", 278 | }, 279 | { 280 | ns: "Test.Longitude", 281 | expected: "Longitude moet geldige lengtegraadcoördinaten bevatten", 282 | }, 283 | { 284 | ns: "Test.MultiByte", 285 | expected: "MultiByte moet multibyte karakters bevatten", 286 | }, 287 | { 288 | ns: "Test.ASCII", 289 | expected: "ASCII mag alleen ascii karakters bevatten", 290 | }, 291 | { 292 | ns: "Test.PrintableASCII", 293 | expected: "PrintableASCII mag alleen afdrukbare ascii karakters bevatten", 294 | }, 295 | { 296 | ns: "Test.UUID", 297 | expected: "UUID moet een geldige UUID zijn", 298 | }, 299 | { 300 | ns: "Test.UUID3", 301 | expected: "UUID3 moet een geldige versie 3 UUID zijn", 302 | }, 303 | { 304 | ns: "Test.UUID4", 305 | expected: "UUID4 moet een geldige versie 4 UUID zijn", 306 | }, 307 | { 308 | ns: "Test.UUID5", 309 | expected: "UUID5 moet een geldige versie 5 UUID zijn", 310 | }, 311 | { 312 | ns: "Test.ULID", 313 | expected: "ULID moet een geldige ULID zijn", 314 | }, 315 | { 316 | ns: "Test.ISBN", 317 | expected: "ISBN moet een geldig ISBN nummer zijn", 318 | }, 319 | { 320 | ns: "Test.ISBN10", 321 | expected: "ISBN10 moet een geldig ISBN-10 nummer zijn", 322 | }, 323 | { 324 | ns: "Test.ISBN13", 325 | expected: "ISBN13 moet een geldig ISBN-13 nummer zijn", 326 | }, 327 | { 328 | ns: "Test.ISSN", 329 | expected: "ISSN moet een geldig ISSN nummer zijn", 330 | }, 331 | { 332 | ns: "Test.Excludes", 333 | expected: "Excludes mag niet de tekst 'text' bevatten", 334 | }, 335 | { 336 | ns: "Test.ExcludesAll", 337 | expected: "ExcludesAll mag niet een van de volgende karakters bevatten '!@#$'", 338 | }, 339 | { 340 | ns: "Test.ExcludesRune", 341 | expected: "ExcludesRune mag niet het volgende bevatten '☻'", 342 | }, 343 | { 344 | ns: "Test.ContainsAny", 345 | expected: "ContainsAny moet tenminste een van de volgende karakters bevatten '!@#$'", 346 | }, 347 | { 348 | ns: "Test.Contains", 349 | expected: "Contains moet de tekst 'purpose' bevatten", 350 | }, 351 | { 352 | ns: "Test.Base64", 353 | expected: "Base64 moet een geldige Base64 string zijn", 354 | }, 355 | { 356 | ns: "Test.Email", 357 | expected: "Email moet een geldig email adres zijn", 358 | }, 359 | { 360 | ns: "Test.URL", 361 | expected: "URL moet een geldige URL zijn", 362 | }, 363 | { 364 | ns: "Test.URI", 365 | expected: "URI moet een geldige URI zijn", 366 | }, 367 | { 368 | ns: "Test.RGBColorString", 369 | expected: "RGBColorString moet een geldige RGB kleur zijn", 370 | }, 371 | { 372 | ns: "Test.RGBAColorString", 373 | expected: "RGBAColorString moet een geldige RGBA kleur zijn", 374 | }, 375 | { 376 | ns: "Test.HSLColorString", 377 | expected: "HSLColorString moet een geldige HSL kleur zijn", 378 | }, 379 | { 380 | ns: "Test.HSLAColorString", 381 | expected: "HSLAColorString moet een geldige HSLA kleur zijn", 382 | }, 383 | { 384 | ns: "Test.HexadecimalString", 385 | expected: "HexadecimalString moet een geldig hexadecimaal getal zijn", 386 | }, 387 | { 388 | ns: "Test.HexColorString", 389 | expected: "HexColorString moet een geldige HEX kleur zijn", 390 | }, 391 | { 392 | ns: "Test.NumberString", 393 | expected: "NumberString moet een geldig getal zijn", 394 | }, 395 | { 396 | ns: "Test.NumericString", 397 | expected: "NumericString moet een geldige numerieke waarde zijn", 398 | }, 399 | { 400 | ns: "Test.AlphanumString", 401 | expected: "AlphanumString mag alleen alfanumerieke karakters bevatten", 402 | }, 403 | { 404 | ns: "Test.AlphaString", 405 | expected: "AlphaString mag alleen alfabetische karakters bevatten", 406 | }, 407 | { 408 | ns: "Test.LtFieldString", 409 | expected: "LtFieldString moet kleiner zijn dan MaxString", 410 | }, 411 | { 412 | ns: "Test.LteFieldString", 413 | expected: "LteFieldString moet kleiner dan of gelijk aan MaxString zijn", 414 | }, 415 | { 416 | ns: "Test.GtFieldString", 417 | expected: "GtFieldString moet groter zijn dan MaxString", 418 | }, 419 | { 420 | ns: "Test.GteFieldString", 421 | expected: "GteFieldString moet groter dan of gelijk aan MaxString zijn", 422 | }, 423 | { 424 | ns: "Test.NeFieldString", 425 | expected: "NeFieldString mag niet gelijk zijn aan EqFieldString", 426 | }, 427 | { 428 | ns: "Test.LtCSFieldString", 429 | expected: "LtCSFieldString moet kleiner zijn dan Inner.LtCSFieldString", 430 | }, 431 | { 432 | ns: "Test.LteCSFieldString", 433 | expected: "LteCSFieldString moet kleiner dan of gelijk aan Inner.LteCSFieldString zijn", 434 | }, 435 | { 436 | ns: "Test.GtCSFieldString", 437 | expected: "GtCSFieldString moet groter zijn dan Inner.GtCSFieldString", 438 | }, 439 | { 440 | ns: "Test.GteCSFieldString", 441 | expected: "GteCSFieldString moet groter dan of gelijk aan Inner.GteCSFieldString zijn", 442 | }, 443 | { 444 | ns: "Test.NeCSFieldString", 445 | expected: "NeCSFieldString mag niet gelijk zijn aan Inner.NeCSFieldString", 446 | }, 447 | { 448 | ns: "Test.EqCSFieldString", 449 | expected: "EqCSFieldString moet gelijk zijn aan Inner.EqCSFieldString", 450 | }, 451 | { 452 | ns: "Test.EqFieldString", 453 | expected: "EqFieldString moet gelijk zijn aan MaxString", 454 | }, 455 | { 456 | ns: "Test.GteString", 457 | expected: "GteString moet tenminste 3 karakters lang zijn", 458 | }, 459 | { 460 | ns: "Test.GteNumber", 461 | expected: "GteNumber moet 5.56 of groter zijn", 462 | }, 463 | { 464 | ns: "Test.GteMultiple", 465 | expected: "GteMultiple moet tenminste 2 items bevatten", 466 | }, 467 | { 468 | ns: "Test.GteTime", 469 | expected: "GteTime moet groter dan of gelijk zijn aan de huidige datum & tijd", 470 | }, 471 | { 472 | ns: "Test.GtString", 473 | expected: "GtString moet langer dan 3 karakters zijn", 474 | }, 475 | { 476 | ns: "Test.GtNumber", 477 | expected: "GtNumber moet groter zijn dan 5.56", 478 | }, 479 | { 480 | ns: "Test.GtMultiple", 481 | expected: "GtMultiple moet meer dan 2 items bevatten", 482 | }, 483 | { 484 | ns: "Test.GtTime", 485 | expected: "GtTime moet groter zijn dan de huidige datum & tijd", 486 | }, 487 | { 488 | ns: "Test.LteString", 489 | expected: "LteString mag maximaal 3 karakters lang zijn", 490 | }, 491 | { 492 | ns: "Test.LteNumber", 493 | expected: "LteNumber moet 5.56 of minder zijn", 494 | }, 495 | { 496 | ns: "Test.LteMultiple", 497 | expected: "LteMultiple mag maximaal 2 items bevatten", 498 | }, 499 | { 500 | ns: "Test.LteTime", 501 | expected: "LteTime moet kleiner dan of gelijk aan de huidige datum & tijd zijn", 502 | }, 503 | { 504 | ns: "Test.LtString", 505 | expected: "LtString moet minder dan 3 karakters lang zijn", 506 | }, 507 | { 508 | ns: "Test.LtNumber", 509 | expected: "LtNumber moet kleiner zijn dan 5.56", 510 | }, 511 | { 512 | ns: "Test.LtMultiple", 513 | expected: "LtMultiple moet minder dan 2 items bevatten", 514 | }, 515 | { 516 | ns: "Test.LtTime", 517 | expected: "LtTime moet kleiner zijn dan de huidige datum & tijd", 518 | }, 519 | { 520 | ns: "Test.NeString", 521 | expected: "NeString mag niet gelijk zijn aan ", 522 | }, 523 | { 524 | ns: "Test.NeNumber", 525 | expected: "NeNumber mag niet gelijk zijn aan 0.00", 526 | }, 527 | { 528 | ns: "Test.NeMultiple", 529 | expected: "NeMultiple mag niet gelijk zijn aan 0", 530 | }, 531 | { 532 | ns: "Test.EqString", 533 | expected: "EqString is niet gelijk aan 3", 534 | }, 535 | { 536 | ns: "Test.EqNumber", 537 | expected: "EqNumber is niet gelijk aan 2.33", 538 | }, 539 | { 540 | ns: "Test.EqMultiple", 541 | expected: "EqMultiple is niet gelijk aan 7", 542 | }, 543 | { 544 | ns: "Test.MaxString", 545 | expected: "MaxString mag maximaal 3 karakters lang zijn", 546 | }, 547 | { 548 | ns: "Test.MaxNumber", 549 | expected: "MaxNumber moet 1,113.00 of kleiner zijn", 550 | }, 551 | { 552 | ns: "Test.MaxMultiple", 553 | expected: "MaxMultiple mag maximaal 7 items bevatten", 554 | }, 555 | { 556 | ns: "Test.MinString", 557 | expected: "MinString moet tenminste 1 karakter lang zijn", 558 | }, 559 | { 560 | ns: "Test.MinNumber", 561 | expected: "MinNumber moet 1,113.00 of groter zijn", 562 | }, 563 | { 564 | ns: "Test.MinMultiple", 565 | expected: "MinMultiple moet tenminste 7 items bevatten", 566 | }, 567 | { 568 | ns: "Test.LenString", 569 | expected: "LenString moet 1 karakter lang zijn", 570 | }, 571 | { 572 | ns: "Test.LenNumber", 573 | expected: "LenNumber moet gelijk zijn aan 1,113.00", 574 | }, 575 | { 576 | ns: "Test.LenMultiple", 577 | expected: "LenMultiple moet 7 items bevatten", 578 | }, 579 | { 580 | ns: "Test.RequiredString", 581 | expected: "RequiredString is een verplicht veld", 582 | }, 583 | { 584 | ns: "Test.RequiredNumber", 585 | expected: "RequiredNumber is een verplicht veld", 586 | }, 587 | { 588 | ns: "Test.RequiredMultiple", 589 | expected: "RequiredMultiple is een verplicht veld", 590 | }, 591 | { 592 | ns: "Test.StrPtrMinLen", 593 | expected: "StrPtrMinLen moet tenminste 10 karakters lang zijn", 594 | }, 595 | { 596 | ns: "Test.StrPtrMaxLen", 597 | expected: "StrPtrMaxLen mag maximaal 1 karakter lang zijn", 598 | }, 599 | { 600 | ns: "Test.StrPtrLen", 601 | expected: "StrPtrLen moet 2 karakters lang zijn", 602 | }, 603 | { 604 | ns: "Test.StrPtrLt", 605 | expected: "StrPtrLt moet minder dan 1 karakter lang zijn", 606 | }, 607 | { 608 | ns: "Test.StrPtrLte", 609 | expected: "StrPtrLte mag maximaal 1 karakter lang zijn", 610 | }, 611 | { 612 | ns: "Test.StrPtrGt", 613 | expected: "StrPtrGt moet langer dan 10 karakters zijn", 614 | }, 615 | { 616 | ns: "Test.StrPtrGte", 617 | expected: "StrPtrGte moet tenminste 10 karakters lang zijn", 618 | }, 619 | { 620 | ns: "Test.OneOfString", 621 | expected: "OneOfString moet een van de volgende zijn [red green]", 622 | }, 623 | { 624 | ns: "Test.OneOfInt", 625 | expected: "OneOfInt moet een van de volgende zijn [5 63]", 626 | }, 627 | { 628 | ns: "Test.Image", 629 | expected: "Image moet een geldige afbeelding zijn", 630 | }, 631 | } 632 | 633 | for _, tt := range tests { 634 | var fe validator.FieldError 635 | 636 | for _, e := range errs { 637 | if tt.ns == e.Namespace() { 638 | fe = e 639 | break 640 | } 641 | } 642 | 643 | NotEqual(t, fe, nil) 644 | Equal(t, tt.expected, fe.Translate(trans)) 645 | } 646 | } 647 | -------------------------------------------------------------------------------- /translations/pt_BR/pt_BR_test.go: -------------------------------------------------------------------------------- 1 | package pt_BR 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/go-playground/assert/v2" 8 | brazilian_portuguese "github.com/go-playground/locales/pt_BR" 9 | ut "github.com/go-playground/universal-translator" 10 | "github.com/go-playground/validator/v10" 11 | ) 12 | 13 | type Foo struct{} 14 | 15 | func (Foo) IsBar() bool { return false } 16 | 17 | func TestTranslations(t *testing.T) { 18 | ptbr := brazilian_portuguese.New() 19 | uni := ut.New(ptbr, ptbr) 20 | trans, _ := uni.GetTranslator("pt_BR") 21 | 22 | validate := validator.New() 23 | 24 | err := RegisterDefaultTranslations(validate, trans) 25 | Equal(t, err, nil) 26 | 27 | type Inner struct { 28 | EqCSFieldString string 29 | NeCSFieldString string 30 | GtCSFieldString string 31 | GteCSFieldString string 32 | LtCSFieldString string 33 | LteCSFieldString string 34 | } 35 | 36 | type Test struct { 37 | Inner Inner 38 | RequiredString string `validate:"required"` 39 | RequiredNumber int `validate:"required"` 40 | RequiredMultiple []string `validate:"required"` 41 | LenString string `validate:"len=1"` 42 | LenNumber float64 `validate:"len=1113.00"` 43 | LenMultiple []string `validate:"len=7"` 44 | MinString string `validate:"min=1"` 45 | MinNumber float64 `validate:"min=1113.00"` 46 | MinMultiple []string `validate:"min=7"` 47 | MaxString string `validate:"max=3"` 48 | MaxNumber float64 `validate:"max=1113.00"` 49 | MaxMultiple []string `validate:"max=7"` 50 | EqString string `validate:"eq=3"` 51 | EqNumber float64 `validate:"eq=2.33"` 52 | EqMultiple []string `validate:"eq=7"` 53 | NeString string `validate:"ne="` 54 | NeNumber float64 `validate:"ne=0.00"` 55 | NeMultiple []string `validate:"ne=0"` 56 | LtString string `validate:"lt=3"` 57 | LtNumber float64 `validate:"lt=5.56"` 58 | LtMultiple []string `validate:"lt=2"` 59 | LtTime time.Time `validate:"lt"` 60 | LteString string `validate:"lte=3"` 61 | LteNumber float64 `validate:"lte=5.56"` 62 | LteMultiple []string `validate:"lte=2"` 63 | LteTime time.Time `validate:"lte"` 64 | GtString string `validate:"gt=3"` 65 | GtNumber float64 `validate:"gt=5.56"` 66 | GtMultiple []string `validate:"gt=2"` 67 | GtTime time.Time `validate:"gt"` 68 | GteString string `validate:"gte=3"` 69 | GteNumber float64 `validate:"gte=5.56"` 70 | GteMultiple []string `validate:"gte=2"` 71 | GteTime time.Time `validate:"gte"` 72 | EqFieldString string `validate:"eqfield=MaxString"` 73 | EqCSFieldString string `validate:"eqcsfield=Inner.EqCSFieldString"` 74 | NeCSFieldString string `validate:"necsfield=Inner.NeCSFieldString"` 75 | GtCSFieldString string `validate:"gtcsfield=Inner.GtCSFieldString"` 76 | GteCSFieldString string `validate:"gtecsfield=Inner.GteCSFieldString"` 77 | LtCSFieldString string `validate:"ltcsfield=Inner.LtCSFieldString"` 78 | LteCSFieldString string `validate:"ltecsfield=Inner.LteCSFieldString"` 79 | NeFieldString string `validate:"nefield=EqFieldString"` 80 | GtFieldString string `validate:"gtfield=MaxString"` 81 | GteFieldString string `validate:"gtefield=MaxString"` 82 | LtFieldString string `validate:"ltfield=MaxString"` 83 | LteFieldString string `validate:"ltefield=MaxString"` 84 | AlphaString string `validate:"alpha"` 85 | AlphanumString string `validate:"alphanum"` 86 | NumericString string `validate:"numeric"` 87 | NumberString string `validate:"number"` 88 | HexadecimalString string `validate:"hexadecimal"` 89 | HexColorString string `validate:"hexcolor"` 90 | RGBColorString string `validate:"rgb"` 91 | RGBAColorString string `validate:"rgba"` 92 | HSLColorString string `validate:"hsl"` 93 | HSLAColorString string `validate:"hsla"` 94 | Email string `validate:"email"` 95 | URL string `validate:"url"` 96 | URI string `validate:"uri"` 97 | Base64 string `validate:"base64"` 98 | Contains string `validate:"contains=purpose"` 99 | ContainsAny string `validate:"containsany=!@#$"` 100 | Excludes string `validate:"excludes=text"` 101 | ExcludesAll string `validate:"excludesall=!@#$"` 102 | ExcludesRune string `validate:"excludesrune=☻"` 103 | ISBN string `validate:"isbn"` 104 | ISBN10 string `validate:"isbn10"` 105 | ISBN13 string `validate:"isbn13"` 106 | ISSN string `validate:"issn"` 107 | UUID string `validate:"uuid"` 108 | UUID3 string `validate:"uuid3"` 109 | UUID4 string `validate:"uuid4"` 110 | UUID5 string `validate:"uuid5"` 111 | ULID string `validate:"ulid"` 112 | ASCII string `validate:"ascii"` 113 | PrintableASCII string `validate:"printascii"` 114 | MultiByte string `validate:"multibyte"` 115 | DataURI string `validate:"datauri"` 116 | Latitude string `validate:"latitude"` 117 | Longitude string `validate:"longitude"` 118 | SSN string `validate:"ssn"` 119 | IP string `validate:"ip"` 120 | IPv4 string `validate:"ipv4"` 121 | IPv6 string `validate:"ipv6"` 122 | CIDR string `validate:"cidr"` 123 | CIDRv4 string `validate:"cidrv4"` 124 | CIDRv6 string `validate:"cidrv6"` 125 | TCPAddr string `validate:"tcp_addr"` 126 | TCPAddrv4 string `validate:"tcp4_addr"` 127 | TCPAddrv6 string `validate:"tcp6_addr"` 128 | UDPAddr string `validate:"udp_addr"` 129 | UDPAddrv4 string `validate:"udp4_addr"` 130 | UDPAddrv6 string `validate:"udp6_addr"` 131 | IPAddr string `validate:"ip_addr"` 132 | IPAddrv4 string `validate:"ip4_addr"` 133 | IPAddrv6 string `validate:"ip6_addr"` 134 | UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future 135 | MAC string `validate:"mac"` 136 | IsColor string `validate:"iscolor"` 137 | StrPtrMinLen *string `validate:"min=10"` 138 | StrPtrMaxLen *string `validate:"max=1"` 139 | StrPtrLen *string `validate:"len=2"` 140 | StrPtrLt *string `validate:"lt=1"` 141 | StrPtrLte *string `validate:"lte=1"` 142 | StrPtrGt *string `validate:"gt=10"` 143 | StrPtrGte *string `validate:"gte=10"` 144 | OneOfString string `validate:"oneof=red green"` 145 | OneOfInt int `validate:"oneof=5 63"` 146 | BooleanString string `validate:"boolean"` 147 | Image string `validate:"image"` 148 | CveString string `validate:"cve"` 149 | ValidateFn Foo `validate:"validateFn=IsBar"` 150 | } 151 | 152 | var test Test 153 | 154 | test.Inner.EqCSFieldString = "1234" 155 | test.Inner.GtCSFieldString = "1234" 156 | test.Inner.GteCSFieldString = "1234" 157 | 158 | test.MaxString = "1234" 159 | test.MaxNumber = 2000 160 | test.MaxMultiple = make([]string, 9) 161 | 162 | test.LtString = "1234" 163 | test.LtNumber = 6 164 | test.LtMultiple = make([]string, 3) 165 | test.LtTime = time.Now().Add(time.Hour * 24) 166 | 167 | test.LteString = "1234" 168 | test.LteNumber = 6 169 | test.LteMultiple = make([]string, 3) 170 | test.LteTime = time.Now().Add(time.Hour * 24) 171 | 172 | test.LtFieldString = "12345" 173 | test.LteFieldString = "12345" 174 | 175 | test.LtCSFieldString = "1234" 176 | test.LteCSFieldString = "1234" 177 | 178 | test.AlphaString = "abc3" 179 | test.AlphanumString = "abc3!" 180 | test.NumericString = "12E.00" 181 | test.NumberString = "12E" 182 | test.BooleanString = "A" 183 | test.CveString = "A" 184 | 185 | test.Excludes = "este é um texto de teste" 186 | test.ExcludesAll = "Isso é Ótimo!" 187 | test.ExcludesRune = "Amo isso ☻" 188 | 189 | test.ASCII = "カタカナ" 190 | test.PrintableASCII = "カタカナ" 191 | 192 | test.MultiByte = "1234feerf" 193 | 194 | s := "toolong" 195 | test.StrPtrMaxLen = &s 196 | test.StrPtrLen = &s 197 | 198 | err = validate.Struct(test) 199 | NotEqual(t, err, nil) 200 | 201 | errs, ok := err.(validator.ValidationErrors) 202 | Equal(t, ok, true) 203 | 204 | tests := []struct { 205 | ns string 206 | expected string 207 | }{ 208 | { 209 | ns: "Test.IsColor", 210 | expected: "IsColor deve ser uma cor válida", 211 | }, 212 | { 213 | ns: "Test.MAC", 214 | expected: "MAC deve conter um endereço MAC válido", 215 | }, 216 | { 217 | ns: "Test.IPAddr", 218 | expected: "IPAddr deve ser um endereço IP resolvível", 219 | }, 220 | { 221 | ns: "Test.IPAddrv4", 222 | expected: "IPAddrv4 deve ser um endereço IPv4 resolvível", 223 | }, 224 | { 225 | ns: "Test.IPAddrv6", 226 | expected: "IPAddrv6 deve ser um endereço IPv6 resolvível", 227 | }, 228 | { 229 | ns: "Test.UDPAddr", 230 | expected: "UDPAddr deve ser um endereço UDP válido", 231 | }, 232 | { 233 | ns: "Test.UDPAddrv4", 234 | expected: "UDPAddrv4 deve ser um endereço IPv4 UDP válido", 235 | }, 236 | { 237 | ns: "Test.UDPAddrv6", 238 | expected: "UDPAddrv6 deve ser um endereço IPv6 UDP válido", 239 | }, 240 | { 241 | ns: "Test.TCPAddr", 242 | expected: "TCPAddr deve ser um endereço TCP válido", 243 | }, 244 | { 245 | ns: "Test.TCPAddrv4", 246 | expected: "TCPAddrv4 deve ser um endereço IPv4 TCP válido", 247 | }, 248 | { 249 | ns: "Test.TCPAddrv6", 250 | expected: "TCPAddrv6 deve ser um endereço IPv6 TCP válido", 251 | }, 252 | { 253 | ns: "Test.CIDR", 254 | expected: "CIDR deve conter uma notação CIDR válida", 255 | }, 256 | { 257 | ns: "Test.CIDRv4", 258 | expected: "CIDRv4 deve conter uma notação CIDR válida para um endereço IPv4", 259 | }, 260 | { 261 | ns: "Test.CIDRv6", 262 | expected: "CIDRv6 deve conter uma notação CIDR válida para um endereço IPv6", 263 | }, 264 | { 265 | ns: "Test.SSN", 266 | expected: "SSN deve ser um número SSN válido", 267 | }, 268 | { 269 | ns: "Test.IP", 270 | expected: "IP deve ser um endereço de IP válido", 271 | }, 272 | { 273 | ns: "Test.IPv4", 274 | expected: "IPv4 deve ser um endereço IPv4 válido", 275 | }, 276 | { 277 | ns: "Test.IPv6", 278 | expected: "IPv6 deve ser um endereço IPv6 válido", 279 | }, 280 | { 281 | ns: "Test.DataURI", 282 | expected: "DataURI deve conter um URI data válido", 283 | }, 284 | { 285 | ns: "Test.Latitude", 286 | expected: "Latitude deve conter uma coordenada de latitude válida", 287 | }, 288 | { 289 | ns: "Test.Longitude", 290 | expected: "Longitude deve conter uma coordenada de longitude válida", 291 | }, 292 | { 293 | ns: "Test.MultiByte", 294 | expected: "MultiByte deve conter caracteres multibyte", 295 | }, 296 | { 297 | ns: "Test.ASCII", 298 | expected: "ASCII deve conter apenas caracteres ascii", 299 | }, 300 | { 301 | ns: "Test.PrintableASCII", 302 | expected: "PrintableASCII deve conter apenas caracteres ascii imprimíveis", 303 | }, 304 | { 305 | ns: "Test.UUID", 306 | expected: "UUID deve ser um UUID válido", 307 | }, 308 | { 309 | ns: "Test.UUID3", 310 | expected: "UUID3 deve ser um UUID versão 3 válido", 311 | }, 312 | { 313 | ns: "Test.UUID4", 314 | expected: "UUID4 deve ser um UUID versão 4 válido", 315 | }, 316 | { 317 | ns: "Test.UUID5", 318 | expected: "UUID5 deve ser um UUID versão 5 válido", 319 | }, 320 | { 321 | ns: "Test.ULID", 322 | expected: "ULID deve ser uma ULID válida", 323 | }, 324 | { 325 | ns: "Test.ISBN", 326 | expected: "ISBN deve ser um número ISBN válido", 327 | }, 328 | { 329 | ns: "Test.ISBN10", 330 | expected: "ISBN10 deve ser um número ISBN-10 válido", 331 | }, 332 | { 333 | ns: "Test.ISBN13", 334 | expected: "ISBN13 deve ser um número ISBN-13 válido", 335 | }, 336 | { 337 | ns: "Test.ISSN", 338 | expected: "ISSN deve ser um número ISSN válido", 339 | }, 340 | { 341 | ns: "Test.Excludes", 342 | expected: "Excludes não deve conter o texto 'text'", 343 | }, 344 | { 345 | ns: "Test.ExcludesAll", 346 | expected: "ExcludesAll não deve conter nenhum dos caracteres '!@#$'", 347 | }, 348 | { 349 | ns: "Test.ExcludesRune", 350 | expected: "ExcludesRune não deve conter '☻'", 351 | }, 352 | { 353 | ns: "Test.ContainsAny", 354 | expected: "ContainsAny deve conter pelo menos um dos caracteres '!@#$'", 355 | }, 356 | { 357 | ns: "Test.Contains", 358 | expected: "Contains deve conter o texto 'purpose'", 359 | }, 360 | { 361 | ns: "Test.Base64", 362 | expected: "Base64 deve ser uma string Base64 válida", 363 | }, 364 | { 365 | ns: "Test.Email", 366 | expected: "Email deve ser um endereço de e-mail válido", 367 | }, 368 | { 369 | ns: "Test.URL", 370 | expected: "URL deve ser uma URL válida", 371 | }, 372 | { 373 | ns: "Test.URI", 374 | expected: "URI deve ser uma URI válida", 375 | }, 376 | { 377 | ns: "Test.RGBColorString", 378 | expected: "RGBColorString deve ser uma cor RGB válida", 379 | }, 380 | { 381 | ns: "Test.RGBAColorString", 382 | expected: "RGBAColorString deve ser uma cor RGBA válida", 383 | }, 384 | { 385 | ns: "Test.HSLColorString", 386 | expected: "HSLColorString deve ser uma cor HSL válida", 387 | }, 388 | { 389 | ns: "Test.HSLAColorString", 390 | expected: "HSLAColorString deve ser uma cor HSLA válida", 391 | }, 392 | { 393 | ns: "Test.HexadecimalString", 394 | expected: "HexadecimalString deve ser um hexadecimal válido", 395 | }, 396 | { 397 | ns: "Test.HexColorString", 398 | expected: "HexColorString deve ser uma cor HEX válida", 399 | }, 400 | { 401 | ns: "Test.NumberString", 402 | expected: "NumberString deve ser um número válido", 403 | }, 404 | { 405 | ns: "Test.NumericString", 406 | expected: "NumericString deve ser um valor numérico válido", 407 | }, 408 | { 409 | ns: "Test.AlphanumString", 410 | expected: "AlphanumString deve conter caracteres alfanuméricos", 411 | }, 412 | { 413 | ns: "Test.AlphaString", 414 | expected: "AlphaString deve conter caracteres alfabéticos", 415 | }, 416 | { 417 | ns: "Test.LtFieldString", 418 | expected: "LtFieldString deve ser menor que MaxString", 419 | }, 420 | { 421 | ns: "Test.LteFieldString", 422 | expected: "LteFieldString deve ser menor ou igual a MaxString", 423 | }, 424 | { 425 | ns: "Test.GtFieldString", 426 | expected: "GtFieldString deve ser maior do que MaxString", 427 | }, 428 | { 429 | ns: "Test.GteFieldString", 430 | expected: "GteFieldString deve ser maior ou igual a MaxString", 431 | }, 432 | { 433 | ns: "Test.NeFieldString", 434 | expected: "NeFieldString não deve ser igual a EqFieldString", 435 | }, 436 | { 437 | ns: "Test.LtCSFieldString", 438 | expected: "LtCSFieldString deve ser menor que Inner.LtCSFieldString", 439 | }, 440 | { 441 | ns: "Test.LteCSFieldString", 442 | expected: "LteCSFieldString deve ser menor ou igual a Inner.LteCSFieldString", 443 | }, 444 | { 445 | ns: "Test.GtCSFieldString", 446 | expected: "GtCSFieldString deve ser maior do que Inner.GtCSFieldString", 447 | }, 448 | { 449 | ns: "Test.GteCSFieldString", 450 | expected: "GteCSFieldString deve ser maior ou igual a Inner.GteCSFieldString", 451 | }, 452 | { 453 | ns: "Test.NeCSFieldString", 454 | expected: "NeCSFieldString não deve ser igual a Inner.NeCSFieldString", 455 | }, 456 | { 457 | ns: "Test.EqCSFieldString", 458 | expected: "EqCSFieldString deve ser igual a Inner.EqCSFieldString", 459 | }, 460 | { 461 | ns: "Test.EqFieldString", 462 | expected: "EqFieldString deve ser igual a MaxString", 463 | }, 464 | { 465 | ns: "Test.GteString", 466 | expected: "GteString deve ter pelo menos 3 caracteres", 467 | }, 468 | { 469 | ns: "Test.GteNumber", 470 | expected: "GteNumber deve ser 5,56 ou superior", 471 | }, 472 | { 473 | ns: "Test.GteMultiple", 474 | expected: "GteMultiple deve conter pelo menos 2 itens", 475 | }, 476 | { 477 | ns: "Test.GteTime", 478 | expected: "GteTime deve ser maior ou igual à Data e Hora atual", 479 | }, 480 | { 481 | ns: "Test.GtString", 482 | expected: "GtString deve ter mais de 3 caracteres", 483 | }, 484 | { 485 | ns: "Test.GtNumber", 486 | expected: "GtNumber deve ser maior do que 5,56", 487 | }, 488 | { 489 | ns: "Test.GtMultiple", 490 | expected: "GtMultiple deve conter mais de 2 itens", 491 | }, 492 | { 493 | ns: "Test.GtTime", 494 | expected: "GtTime deve ser maior que a Data e Hora atual", 495 | }, 496 | { 497 | ns: "Test.LteString", 498 | expected: "LteString deve ter no máximo 3 caracteres", 499 | }, 500 | { 501 | ns: "Test.LteNumber", 502 | expected: "LteNumber deve ser 5,56 ou menor", 503 | }, 504 | { 505 | ns: "Test.LteMultiple", 506 | expected: "LteMultiple deve conter no máximo 2 itens", 507 | }, 508 | { 509 | ns: "Test.LteTime", 510 | expected: "LteTime deve ser menor ou igual à Data e Hora atual", 511 | }, 512 | { 513 | ns: "Test.LtString", 514 | expected: "LtString deve ter menos de 3 caracteres", 515 | }, 516 | { 517 | ns: "Test.LtNumber", 518 | expected: "LtNumber deve ser menor que 5,56", 519 | }, 520 | { 521 | ns: "Test.LtMultiple", 522 | expected: "LtMultiple deve conter menos de 2 itens", 523 | }, 524 | { 525 | ns: "Test.LtTime", 526 | expected: "LtTime deve ser inferior à Data e Hora atual", 527 | }, 528 | { 529 | ns: "Test.NeString", 530 | expected: "NeString não deve ser igual a ", 531 | }, 532 | { 533 | ns: "Test.NeNumber", 534 | expected: "NeNumber não deve ser igual a 0.00", 535 | }, 536 | { 537 | ns: "Test.NeMultiple", 538 | expected: "NeMultiple não deve ser igual a 0", 539 | }, 540 | { 541 | ns: "Test.EqString", 542 | expected: "EqString não é igual a 3", 543 | }, 544 | { 545 | ns: "Test.EqNumber", 546 | expected: "EqNumber não é igual a 2.33", 547 | }, 548 | { 549 | ns: "Test.EqMultiple", 550 | expected: "EqMultiple não é igual a 7", 551 | }, 552 | { 553 | ns: "Test.MaxString", 554 | expected: "MaxString deve ter no máximo 3 caracteres", 555 | }, 556 | { 557 | ns: "Test.MaxNumber", 558 | expected: "MaxNumber deve ser 1.113,00 ou menor", 559 | }, 560 | { 561 | ns: "Test.MaxMultiple", 562 | expected: "MaxMultiple deve conter no máximo 7 itens", 563 | }, 564 | { 565 | ns: "Test.MinString", 566 | expected: "MinString deve ter pelo menos 1 caractere", 567 | }, 568 | { 569 | ns: "Test.MinNumber", 570 | expected: "MinNumber deve ser 1.113,00 ou superior", 571 | }, 572 | { 573 | ns: "Test.MinMultiple", 574 | expected: "MinMultiple deve conter pelo menos 7 itens", 575 | }, 576 | { 577 | ns: "Test.LenString", 578 | expected: "LenString deve ter 1 caractere", 579 | }, 580 | { 581 | ns: "Test.LenNumber", 582 | expected: "LenNumber deve ser igual a 1.113,00", 583 | }, 584 | { 585 | ns: "Test.LenMultiple", 586 | expected: "LenMultiple deve conter 7 itens", 587 | }, 588 | { 589 | ns: "Test.RequiredString", 590 | expected: "RequiredString é um campo obrigatório", 591 | }, 592 | { 593 | ns: "Test.RequiredNumber", 594 | expected: "RequiredNumber é um campo obrigatório", 595 | }, 596 | { 597 | ns: "Test.RequiredMultiple", 598 | expected: "RequiredMultiple é um campo obrigatório", 599 | }, 600 | { 601 | ns: "Test.StrPtrMinLen", 602 | expected: "StrPtrMinLen deve ter pelo menos 10 caracteres", 603 | }, 604 | { 605 | ns: "Test.StrPtrMaxLen", 606 | expected: "StrPtrMaxLen deve ter no máximo 1 caractere", 607 | }, 608 | { 609 | ns: "Test.StrPtrLen", 610 | expected: "StrPtrLen deve ter 2 caracteres", 611 | }, 612 | { 613 | ns: "Test.StrPtrLt", 614 | expected: "StrPtrLt deve ter menos de 1 caractere", 615 | }, 616 | { 617 | ns: "Test.StrPtrLte", 618 | expected: "StrPtrLte deve ter no máximo 1 caractere", 619 | }, 620 | { 621 | ns: "Test.StrPtrGt", 622 | expected: "StrPtrGt deve ter mais de 10 caracteres", 623 | }, 624 | { 625 | ns: "Test.StrPtrGte", 626 | expected: "StrPtrGte deve ter pelo menos 10 caracteres", 627 | }, 628 | { 629 | ns: "Test.OneOfString", 630 | expected: "OneOfString deve ser um de [red green]", 631 | }, 632 | { 633 | ns: "Test.OneOfInt", 634 | expected: "OneOfInt deve ser um de [5 63]", 635 | }, 636 | { 637 | ns: "Test.BooleanString", 638 | expected: "BooleanString deve ser um valor booleano válido", 639 | }, 640 | { 641 | ns: "Test.Image", 642 | expected: "Image deve ser uma imagen válido", 643 | }, 644 | { 645 | ns: "Test.CveString", 646 | expected: "CveString deve ser um identificador cve válido", 647 | }, 648 | { 649 | ns: "Test.ValidateFn", 650 | expected: "ValidateFn deve ser um objeto válido", 651 | }, 652 | } 653 | 654 | for _, tt := range tests { 655 | var fe validator.FieldError 656 | 657 | for _, e := range errs { 658 | if tt.ns == e.Namespace() { 659 | fe = e 660 | break 661 | } 662 | } 663 | 664 | NotEqual(t, fe, nil) 665 | Equal(t, tt.expected, fe.Translate(trans)) 666 | } 667 | } 668 | -------------------------------------------------------------------------------- /translations/zh_tw/zh_tw_test.go: -------------------------------------------------------------------------------- 1 | package zh_tw 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/go-playground/assert/v2" 8 | zhongwen "github.com/go-playground/locales/zh_Hant_TW" 9 | ut "github.com/go-playground/universal-translator" 10 | "github.com/go-playground/validator/v10" 11 | ) 12 | 13 | func TestTranslations(t *testing.T) { 14 | zh := zhongwen.New() 15 | uni := ut.New(zh, zh) 16 | trans, _ := uni.GetTranslator("zh") 17 | 18 | validate := validator.New() 19 | 20 | err := RegisterDefaultTranslations(validate, trans) 21 | Equal(t, err, nil) 22 | 23 | type Inner struct { 24 | EqCSFieldString string 25 | NeCSFieldString string 26 | GtCSFieldString string 27 | GteCSFieldString string 28 | LtCSFieldString string 29 | LteCSFieldString string 30 | RequiredIf string 31 | RequiredUnless string 32 | RequiredWith string 33 | RequiredWithAll string 34 | RequiredWithout string 35 | RequiredWithoutAll string 36 | ExcludedIf string 37 | ExcludedUnless string 38 | ExcludedWith string 39 | ExcludedWithAll string 40 | ExcludedWithout string 41 | ExcludedWithoutAll string 42 | } 43 | 44 | type Test struct { 45 | Inner Inner 46 | RequiredString string `validate:"required"` 47 | RequiredNumber int `validate:"required"` 48 | RequiredMultiple []string `validate:"required"` 49 | RequiredIf string `validate:"required_if=Inner.RequiredIf abcd"` 50 | RequiredUnless string `validate:"required_unless=Inner.RequiredUnless abcd"` 51 | RequiredWith string `validate:"required_with=Inner.RequiredWith"` 52 | RequiredWithAll string `validate:"required_with_all=Inner.RequiredWith Inner.RequiredWithAll"` 53 | RequiredWithout string `validate:"required_without=Inner.RequiredWithout"` 54 | RequiredWithoutAll string `validate:"required_without_all=Inner.RequiredWithout Inner.RequiredWithoutAll"` 55 | ExcludedIf string `validate:"excluded_if=Inner.ExcludedIf abcd"` 56 | ExcludedUnless string `validate:"excluded_unless=Inner.ExcludedUnless abcd"` 57 | ExcludedWith string `validate:"excluded_with=Inner.ExcludedWith"` 58 | ExcludedWithout string `validate:"excluded_with_all=Inner.ExcludedWithAll"` 59 | ExcludedWithAll string `validate:"excluded_without=Inner.ExcludedWithout"` 60 | ExcludedWithoutAll string `validate:"excluded_without_all=Inner.ExcludedWithoutAll"` 61 | IsDefault string `validate:"isdefault"` 62 | LenString string `validate:"len=1"` 63 | LenNumber float64 `validate:"len=1113.00"` 64 | LenMultiple []string `validate:"len=7"` 65 | MinString string `validate:"min=1"` 66 | MinNumber float64 `validate:"min=1113.00"` 67 | MinMultiple []string `validate:"min=7"` 68 | MaxString string `validate:"max=3"` 69 | MaxNumber float64 `validate:"max=1113.00"` 70 | MaxMultiple []string `validate:"max=7"` 71 | EqString string `validate:"eq=3"` 72 | EqNumber float64 `validate:"eq=2.33"` 73 | EqMultiple []string `validate:"eq=7"` 74 | NeString string `validate:"ne="` 75 | NeNumber float64 `validate:"ne=0.00"` 76 | NeMultiple []string `validate:"ne=0"` 77 | LtString string `validate:"lt=3"` 78 | LtNumber float64 `validate:"lt=5.56"` 79 | LtMultiple []string `validate:"lt=2"` 80 | LtTime time.Time `validate:"lt"` 81 | LteString string `validate:"lte=3"` 82 | LteNumber float64 `validate:"lte=5.56"` 83 | LteMultiple []string `validate:"lte=2"` 84 | LteTime time.Time `validate:"lte"` 85 | GtString string `validate:"gt=3"` 86 | GtNumber float64 `validate:"gt=5.56"` 87 | GtMultiple []string `validate:"gt=2"` 88 | GtTime time.Time `validate:"gt"` 89 | GteString string `validate:"gte=3"` 90 | GteNumber float64 `validate:"gte=5.56"` 91 | GteMultiple []string `validate:"gte=2"` 92 | GteTime time.Time `validate:"gte"` 93 | EqFieldString string `validate:"eqfield=MaxString"` 94 | EqCSFieldString string `validate:"eqcsfield=Inner.EqCSFieldString"` 95 | NeCSFieldString string `validate:"necsfield=Inner.NeCSFieldString"` 96 | GtCSFieldString string `validate:"gtcsfield=Inner.GtCSFieldString"` 97 | GteCSFieldString string `validate:"gtecsfield=Inner.GteCSFieldString"` 98 | LtCSFieldString string `validate:"ltcsfield=Inner.LtCSFieldString"` 99 | LteCSFieldString string `validate:"ltecsfield=Inner.LteCSFieldString"` 100 | NeFieldString string `validate:"nefield=EqFieldString"` 101 | GtFieldString string `validate:"gtfield=MaxString"` 102 | GteFieldString string `validate:"gtefield=MaxString"` 103 | LtFieldString string `validate:"ltfield=MaxString"` 104 | LteFieldString string `validate:"ltefield=MaxString"` 105 | AlphaString string `validate:"alpha"` 106 | AlphanumString string `validate:"alphanum"` 107 | NumericString string `validate:"numeric"` 108 | NumberString string `validate:"number"` 109 | HexadecimalString string `validate:"hexadecimal"` 110 | HexColorString string `validate:"hexcolor"` 111 | RGBColorString string `validate:"rgb"` 112 | RGBAColorString string `validate:"rgba"` 113 | HSLColorString string `validate:"hsl"` 114 | HSLAColorString string `validate:"hsla"` 115 | Email string `validate:"email"` 116 | URL string `validate:"url"` 117 | URI string `validate:"uri"` 118 | Base64 string `validate:"base64"` 119 | Contains string `validate:"contains=purpose"` 120 | ContainsAny string `validate:"containsany=!@#$"` 121 | Excludes string `validate:"excludes=text"` 122 | ExcludesAll string `validate:"excludesall=!@#$"` 123 | ExcludesRune string `validate:"excludesrune=☻"` 124 | ISBN string `validate:"isbn"` 125 | ISBN10 string `validate:"isbn10"` 126 | ISBN13 string `validate:"isbn13"` 127 | ISSN string `validate:"issn"` 128 | UUID string `validate:"uuid"` 129 | UUID3 string `validate:"uuid3"` 130 | UUID4 string `validate:"uuid4"` 131 | UUID5 string `validate:"uuid5"` 132 | ULID string `validate:"ulid"` 133 | ASCII string `validate:"ascii"` 134 | PrintableASCII string `validate:"printascii"` 135 | MultiByte string `validate:"multibyte"` 136 | DataURI string `validate:"datauri"` 137 | Latitude string `validate:"latitude"` 138 | Longitude string `validate:"longitude"` 139 | SSN string `validate:"ssn"` 140 | IP string `validate:"ip"` 141 | IPv4 string `validate:"ipv4"` 142 | IPv6 string `validate:"ipv6"` 143 | CIDR string `validate:"cidr"` 144 | CIDRv4 string `validate:"cidrv4"` 145 | CIDRv6 string `validate:"cidrv6"` 146 | TCPAddr string `validate:"tcp_addr"` 147 | TCPAddrv4 string `validate:"tcp4_addr"` 148 | TCPAddrv6 string `validate:"tcp6_addr"` 149 | UDPAddr string `validate:"udp_addr"` 150 | UDPAddrv4 string `validate:"udp4_addr"` 151 | UDPAddrv6 string `validate:"udp6_addr"` 152 | IPAddr string `validate:"ip_addr"` 153 | IPAddrv4 string `validate:"ip4_addr"` 154 | IPAddrv6 string `validate:"ip6_addr"` 155 | UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future 156 | MAC string `validate:"mac"` 157 | IsColor string `validate:"iscolor"` 158 | StrPtrMinLen *string `validate:"min=10"` 159 | StrPtrMaxLen *string `validate:"max=1"` 160 | StrPtrLen *string `validate:"len=2"` 161 | StrPtrLt *string `validate:"lt=1"` 162 | StrPtrLte *string `validate:"lte=1"` 163 | StrPtrGt *string `validate:"gt=10"` 164 | StrPtrGte *string `validate:"gte=10"` 165 | OneOfString string `validate:"oneof=red green"` 166 | OneOfInt int `validate:"oneof=5 63"` 167 | Datetime string `validate:"datetime=2006-01-02"` 168 | Image string `validate:"image"` 169 | } 170 | 171 | var test Test 172 | 173 | test.Inner.EqCSFieldString = "1234" 174 | test.Inner.GtCSFieldString = "1234" 175 | test.Inner.GteCSFieldString = "1234" 176 | test.Inner.RequiredIf = "abcd" 177 | test.Inner.RequiredUnless = "1234" 178 | test.Inner.RequiredWith = "1234" 179 | test.Inner.RequiredWithAll = "1234" 180 | test.Inner.ExcludedIf = "abcd" 181 | test.Inner.ExcludedUnless = "1234" 182 | test.Inner.ExcludedWith = "1234" 183 | test.Inner.ExcludedWithAll = "1234" 184 | 185 | test.ExcludedIf = "1234" 186 | test.ExcludedUnless = "1234" 187 | test.ExcludedWith = "1234" 188 | test.ExcludedWithAll = "1234" 189 | test.ExcludedWithout = "1234" 190 | test.ExcludedWithoutAll = "1234" 191 | 192 | test.MaxString = "1234" 193 | test.MaxNumber = 2000 194 | test.MaxMultiple = make([]string, 9) 195 | 196 | test.LtString = "1234" 197 | test.LtNumber = 6 198 | test.LtMultiple = make([]string, 3) 199 | test.LtTime = time.Now().Add(time.Hour * 24) 200 | 201 | test.LteString = "1234" 202 | test.LteNumber = 6 203 | test.LteMultiple = make([]string, 3) 204 | test.LteTime = time.Now().Add(time.Hour * 24) 205 | 206 | test.LtFieldString = "12345" 207 | test.LteFieldString = "12345" 208 | 209 | test.LtCSFieldString = "1234" 210 | test.LteCSFieldString = "1234" 211 | 212 | test.AlphaString = "abc3" 213 | test.AlphanumString = "abc3!" 214 | test.NumericString = "12E.00" 215 | test.NumberString = "12E" 216 | 217 | test.Excludes = "this is some test text" 218 | test.ExcludesAll = "This is Great!" 219 | test.ExcludesRune = "Love it ☻" 220 | 221 | test.ASCII = "カタカナ" 222 | test.PrintableASCII = "カタカナ" 223 | 224 | test.MultiByte = "1234feerf" 225 | 226 | s := "toolong" 227 | test.StrPtrMaxLen = &s 228 | test.StrPtrLen = &s 229 | 230 | test.Datetime = "2008-Feb-01" 231 | 232 | err = validate.Struct(test) 233 | NotEqual(t, err, nil) 234 | 235 | errs, ok := err.(validator.ValidationErrors) 236 | Equal(t, ok, true) 237 | 238 | tests := []struct { 239 | ns string 240 | expected string 241 | }{ 242 | { 243 | ns: "Test.IsColor", 244 | expected: "IsColor必須是一個有效的顏色", 245 | }, 246 | { 247 | ns: "Test.MAC", 248 | expected: "MAC必須是一個有效的MAC地址", 249 | }, 250 | { 251 | ns: "Test.IPAddr", 252 | expected: "IPAddr必須是一個有效的IP地址", 253 | }, 254 | { 255 | ns: "Test.IPAddrv4", 256 | expected: "IPAddrv4必須是一個有效的IPv4地址", 257 | }, 258 | { 259 | ns: "Test.IPAddrv6", 260 | expected: "IPAddrv6必須是一個有效的IPv6地址", 261 | }, 262 | { 263 | ns: "Test.UDPAddr", 264 | expected: "UDPAddr必須是一個有效的UDP地址", 265 | }, 266 | { 267 | ns: "Test.UDPAddrv4", 268 | expected: "UDPAddrv4必須是一個有效的IPv4 UDP地址", 269 | }, 270 | { 271 | ns: "Test.UDPAddrv6", 272 | expected: "UDPAddrv6必須是一個有效的IPv6 UDP地址", 273 | }, 274 | { 275 | ns: "Test.TCPAddr", 276 | expected: "TCPAddr必須是一個有效的TCP地址", 277 | }, 278 | { 279 | ns: "Test.TCPAddrv4", 280 | expected: "TCPAddrv4必須是一個有效的IPv4 TCP地址", 281 | }, 282 | { 283 | ns: "Test.TCPAddrv6", 284 | expected: "TCPAddrv6必須是一個有效的IPv6 TCP地址", 285 | }, 286 | { 287 | ns: "Test.CIDR", 288 | expected: "CIDR必須是一個有效的無類別域間路由(CIDR)", 289 | }, 290 | { 291 | ns: "Test.CIDRv4", 292 | expected: "CIDRv4必須是一个包含IPv4地址的有效無類別域間路由(CIDR)", 293 | }, 294 | { 295 | ns: "Test.CIDRv6", 296 | expected: "CIDRv6必須是一个包含IPv6地址的有效無類別域間路由(CIDR)", 297 | }, 298 | { 299 | ns: "Test.SSN", 300 | expected: "SSN必須是一個有效的社會安全編號(SSN)", 301 | }, 302 | { 303 | ns: "Test.IP", 304 | expected: "IP必須是一個有效的IP地址", 305 | }, 306 | { 307 | ns: "Test.IPv4", 308 | expected: "IPv4必須是一個有效的IPv4地址", 309 | }, 310 | { 311 | ns: "Test.IPv6", 312 | expected: "IPv6必須是一個有效的IPv6地址", 313 | }, 314 | { 315 | ns: "Test.DataURI", 316 | expected: "DataURI必須包含有效的數據URI", 317 | }, 318 | { 319 | ns: "Test.Latitude", 320 | expected: "Latitude必須包含有效的緯度座標", 321 | }, 322 | { 323 | ns: "Test.Longitude", 324 | expected: "Longitude必須包含有效的經度座標", 325 | }, 326 | { 327 | ns: "Test.MultiByte", 328 | expected: "MultiByte必須包含多個字元", 329 | }, 330 | { 331 | ns: "Test.ASCII", 332 | expected: "ASCII必須只包含ascii字元", 333 | }, 334 | { 335 | ns: "Test.PrintableASCII", 336 | expected: "PrintableASCII必須只包含可輸出的ascii字元", 337 | }, 338 | { 339 | ns: "Test.UUID", 340 | expected: "UUID必須是一個有效的UUID", 341 | }, 342 | { 343 | ns: "Test.UUID3", 344 | expected: "UUID3必須是一個有效的V3 UUID", 345 | }, 346 | { 347 | ns: "Test.UUID4", 348 | expected: "UUID4必須是一個有效的V4 UUID", 349 | }, 350 | { 351 | ns: "Test.UUID5", 352 | expected: "UUID5必須是一個有效的V5 UUID", 353 | }, 354 | { 355 | ns: "Test.ULID", 356 | expected: "ULID必須是一個有效的ULID", 357 | }, 358 | { 359 | ns: "Test.ISBN", 360 | expected: "ISBN必須是一個有效的ISBN編號", 361 | }, 362 | { 363 | ns: "Test.ISBN10", 364 | expected: "ISBN10必須是一個有效的ISBN-10編號", 365 | }, 366 | { 367 | ns: "Test.ISBN13", 368 | expected: "ISBN13必須是一個有效的ISBN-13編號", 369 | }, 370 | { 371 | ns: "Test.ISSN", 372 | expected: "ISSN必須是一個有效的ISSN編號", 373 | }, 374 | { 375 | ns: "Test.Excludes", 376 | expected: "Excludes不能包含文字'text'", 377 | }, 378 | { 379 | ns: "Test.ExcludesAll", 380 | expected: "ExcludesAll不能包含以下任何字元'!@#$'", 381 | }, 382 | { 383 | ns: "Test.ExcludesRune", 384 | expected: "ExcludesRune不能包含'☻'", 385 | }, 386 | { 387 | ns: "Test.ContainsAny", 388 | expected: "ContainsAny必須包含至少一個以下字元'!@#$'", 389 | }, 390 | { 391 | ns: "Test.Contains", 392 | expected: "Contains必須包含文字'purpose'", 393 | }, 394 | { 395 | ns: "Test.Base64", 396 | expected: "Base64必須是一個有效的Base64字元串", 397 | }, 398 | { 399 | ns: "Test.Email", 400 | expected: "Email必須是一個有效的信箱", 401 | }, 402 | { 403 | ns: "Test.URL", 404 | expected: "URL必須是一個有效的URL", 405 | }, 406 | { 407 | ns: "Test.URI", 408 | expected: "URI必須是一個有效的URI", 409 | }, 410 | { 411 | ns: "Test.RGBColorString", 412 | expected: "RGBColorString必須是一個有效的RGB顏色", 413 | }, 414 | { 415 | ns: "Test.RGBAColorString", 416 | expected: "RGBAColorString必須是一個有效的RGBA顏色", 417 | }, 418 | { 419 | ns: "Test.HSLColorString", 420 | expected: "HSLColorString必須是一個有效的HSL顏色", 421 | }, 422 | { 423 | ns: "Test.HSLAColorString", 424 | expected: "HSLAColorString必須是一個有效的HSLA顏色", 425 | }, 426 | { 427 | ns: "Test.HexadecimalString", 428 | expected: "HexadecimalString必須是一個有效的十六進制", 429 | }, 430 | { 431 | ns: "Test.HexColorString", 432 | expected: "HexColorString必須是一個有效的十六進制顏色", 433 | }, 434 | { 435 | ns: "Test.NumberString", 436 | expected: "NumberString必須是一個有效的數字", 437 | }, 438 | { 439 | ns: "Test.NumericString", 440 | expected: "NumericString必須是一個有效的數值", 441 | }, 442 | { 443 | ns: "Test.AlphanumString", 444 | expected: "AlphanumString只能包含字母和數字", 445 | }, 446 | { 447 | ns: "Test.AlphaString", 448 | expected: "AlphaString只能包含字母", 449 | }, 450 | { 451 | ns: "Test.LtFieldString", 452 | expected: "LtFieldString必須小於MaxString", 453 | }, 454 | { 455 | ns: "Test.LteFieldString", 456 | expected: "LteFieldString必須小於或等於MaxString", 457 | }, 458 | { 459 | ns: "Test.GtFieldString", 460 | expected: "GtFieldString必須大於MaxString", 461 | }, 462 | { 463 | ns: "Test.GteFieldString", 464 | expected: "GteFieldString必須大於或等於MaxString", 465 | }, 466 | { 467 | ns: "Test.NeFieldString", 468 | expected: "NeFieldString不能等於EqFieldString", 469 | }, 470 | { 471 | ns: "Test.LtCSFieldString", 472 | expected: "LtCSFieldString必須小於Inner.LtCSFieldString", 473 | }, 474 | { 475 | ns: "Test.LteCSFieldString", 476 | expected: "LteCSFieldString必須小於或等於Inner.LteCSFieldString", 477 | }, 478 | { 479 | ns: "Test.GtCSFieldString", 480 | expected: "GtCSFieldString必須大於Inner.GtCSFieldString", 481 | }, 482 | { 483 | ns: "Test.GteCSFieldString", 484 | expected: "GteCSFieldString必須大於或等於Inner.GteCSFieldString", 485 | }, 486 | { 487 | ns: "Test.NeCSFieldString", 488 | expected: "NeCSFieldString不能等於Inner.NeCSFieldString", 489 | }, 490 | { 491 | ns: "Test.EqCSFieldString", 492 | expected: "EqCSFieldString必須等於Inner.EqCSFieldString", 493 | }, 494 | { 495 | ns: "Test.EqFieldString", 496 | expected: "EqFieldString必須等於MaxString", 497 | }, 498 | { 499 | ns: "Test.GteString", 500 | expected: "GteString長度必須至少為3個字元", 501 | }, 502 | { 503 | ns: "Test.GteNumber", 504 | expected: "GteNumber必須大於或等於5.56", 505 | }, 506 | { 507 | ns: "Test.GteMultiple", 508 | expected: "GteMultiple必須至少包含2項", 509 | }, 510 | { 511 | ns: "Test.GteTime", 512 | expected: "GteTime必須大於或等於目前日期和時間", 513 | }, 514 | { 515 | ns: "Test.GtString", 516 | expected: "GtString長度必須大於3個字元", 517 | }, 518 | { 519 | ns: "Test.GtNumber", 520 | expected: "GtNumber必須大於5.56", 521 | }, 522 | { 523 | ns: "Test.GtMultiple", 524 | expected: "GtMultiple必須大於2項", 525 | }, 526 | { 527 | ns: "Test.GtTime", 528 | expected: "GtTime必須大於目前日期和時間", 529 | }, 530 | { 531 | ns: "Test.LteString", 532 | expected: "LteString長度不能超過3個字元", 533 | }, 534 | { 535 | ns: "Test.LteNumber", 536 | expected: "LteNumber必須小於或等於5.56", 537 | }, 538 | { 539 | ns: "Test.LteMultiple", 540 | expected: "LteMultiple最多只能包含2項", 541 | }, 542 | { 543 | ns: "Test.LteTime", 544 | expected: "LteTime必須小於或等於目前日期和時間", 545 | }, 546 | { 547 | ns: "Test.LtString", 548 | expected: "LtString長度必須小於3個字元", 549 | }, 550 | { 551 | ns: "Test.LtNumber", 552 | expected: "LtNumber必須小於5.56", 553 | }, 554 | { 555 | ns: "Test.LtMultiple", 556 | expected: "LtMultiple必須包含少於2項", 557 | }, 558 | { 559 | ns: "Test.LtTime", 560 | expected: "LtTime必須小於目前日期和時間", 561 | }, 562 | { 563 | ns: "Test.NeString", 564 | expected: "NeString不能等於", 565 | }, 566 | { 567 | ns: "Test.NeNumber", 568 | expected: "NeNumber不能等於0.00", 569 | }, 570 | { 571 | ns: "Test.NeMultiple", 572 | expected: "NeMultiple不能等於0", 573 | }, 574 | { 575 | ns: "Test.EqString", 576 | expected: "EqString不等於3", 577 | }, 578 | { 579 | ns: "Test.EqNumber", 580 | expected: "EqNumber不等於2.33", 581 | }, 582 | { 583 | ns: "Test.EqMultiple", 584 | expected: "EqMultiple不等於7", 585 | }, 586 | { 587 | ns: "Test.MaxString", 588 | expected: "MaxString長度不能超過3個字元", 589 | }, 590 | { 591 | ns: "Test.MaxNumber", 592 | expected: "MaxNumber必須小於或等於1,113.00", 593 | }, 594 | { 595 | ns: "Test.MaxMultiple", 596 | expected: "MaxMultiple最多只能包含7項", 597 | }, 598 | { 599 | ns: "Test.MinString", 600 | expected: "MinString長度必須至少為1個字元", 601 | }, 602 | { 603 | ns: "Test.MinNumber", 604 | expected: "MinNumber最小只能為1,113.00", 605 | }, 606 | { 607 | ns: "Test.MinMultiple", 608 | expected: "MinMultiple必須至少包含7項", 609 | }, 610 | { 611 | ns: "Test.LenString", 612 | expected: "LenString長度必須為1個字元", 613 | }, 614 | { 615 | ns: "Test.LenNumber", 616 | expected: "LenNumber必須等於1,113.00", 617 | }, 618 | { 619 | ns: "Test.LenMultiple", 620 | expected: "LenMultiple必須包含7項", 621 | }, 622 | { 623 | ns: "Test.RequiredString", 624 | expected: "RequiredString為必填欄位", 625 | }, 626 | { 627 | ns: "Test.RequiredNumber", 628 | expected: "RequiredNumber為必填欄位", 629 | }, 630 | { 631 | ns: "Test.RequiredMultiple", 632 | expected: "RequiredMultiple為必填欄位", 633 | }, 634 | { 635 | ns: "Test.RequiredUnless", 636 | expected: "RequiredUnless為必填欄位", 637 | }, 638 | { 639 | ns: "Test.RequiredWith", 640 | expected: "RequiredWith為必填欄位", 641 | }, 642 | { 643 | ns: "Test.RequiredWithAll", 644 | expected: "RequiredWithAll為必填欄位", 645 | }, 646 | { 647 | ns: "Test.RequiredWithout", 648 | expected: "RequiredWithout為必填欄位", 649 | }, 650 | { 651 | ns: "Test.RequiredWithoutAll", 652 | expected: "RequiredWithoutAll為必填欄位", 653 | }, 654 | { 655 | ns: "Test.ExcludedIf", 656 | expected: "ExcludedIf為禁填欄位", 657 | }, 658 | { 659 | ns: "Test.ExcludedUnless", 660 | expected: "ExcludedUnless為禁填欄位", 661 | }, 662 | { 663 | ns: "Test.ExcludedWith", 664 | expected: "ExcludedWith為禁填欄位", 665 | }, 666 | { 667 | ns: "Test.ExcludedWithAll", 668 | expected: "ExcludedWithAll為禁填欄位", 669 | }, 670 | { 671 | ns: "Test.ExcludedWithout", 672 | expected: "ExcludedWithout為禁填欄位", 673 | }, 674 | { 675 | ns: "Test.ExcludedWithoutAll", 676 | expected: "ExcludedWithoutAll為禁填欄位", 677 | }, 678 | { 679 | ns: "Test.StrPtrMinLen", 680 | expected: "StrPtrMinLen長度必須至少為10個字元", 681 | }, 682 | { 683 | ns: "Test.StrPtrMaxLen", 684 | expected: "StrPtrMaxLen長度不能超過1個字元", 685 | }, 686 | { 687 | ns: "Test.StrPtrLen", 688 | expected: "StrPtrLen長度必須為2個字元", 689 | }, 690 | { 691 | ns: "Test.StrPtrLt", 692 | expected: "StrPtrLt長度必須小於1個字元", 693 | }, 694 | { 695 | ns: "Test.StrPtrLte", 696 | expected: "StrPtrLte長度不能超過1個字元", 697 | }, 698 | { 699 | ns: "Test.StrPtrGt", 700 | expected: "StrPtrGt長度必須大於10個字元", 701 | }, 702 | { 703 | ns: "Test.StrPtrGte", 704 | expected: "StrPtrGte長度必須至少為10個字元", 705 | }, 706 | { 707 | ns: "Test.OneOfString", 708 | expected: "OneOfString必須是[red green]中的一個", 709 | }, 710 | { 711 | ns: "Test.OneOfInt", 712 | expected: "OneOfInt必須是[5 63]中的一個", 713 | }, 714 | { 715 | ns: "Test.Datetime", 716 | expected: "Datetime與2006-01-02格式不匹配", 717 | }, 718 | { 719 | ns: "Test.Image", 720 | expected: "Image 必須是有效圖像", 721 | }, 722 | } 723 | 724 | for _, tt := range tests { 725 | var fe validator.FieldError 726 | 727 | for _, e := range errs { 728 | if tt.ns == e.Namespace() { 729 | fe = e 730 | break 731 | } 732 | } 733 | 734 | NotEqual(t, fe, nil) 735 | Equal(t, tt.expected, fe.Translate(trans)) 736 | } 737 | } 738 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // extractTypeInternal gets the actual underlying type of field value. 13 | // It will dive into pointers, customTypes and return you the 14 | // underlying value and it's kind. 15 | func (v *validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) { 16 | BEGIN: 17 | switch current.Kind() { 18 | case reflect.Ptr: 19 | 20 | nullable = true 21 | 22 | if current.IsNil() { 23 | return current, reflect.Ptr, nullable 24 | } 25 | 26 | current = current.Elem() 27 | goto BEGIN 28 | 29 | case reflect.Interface: 30 | 31 | nullable = true 32 | 33 | if current.IsNil() { 34 | return current, reflect.Interface, nullable 35 | } 36 | 37 | current = current.Elem() 38 | goto BEGIN 39 | 40 | case reflect.Invalid: 41 | return current, reflect.Invalid, nullable 42 | 43 | default: 44 | 45 | if v.v.hasCustomFuncs { 46 | if fn, ok := v.v.customFuncs[current.Type()]; ok { 47 | current = reflect.ValueOf(fn(current)) 48 | goto BEGIN 49 | } 50 | } 51 | 52 | return current, current.Kind(), nullable 53 | } 54 | } 55 | 56 | // getStructFieldOKInternal traverses a struct to retrieve a specific field denoted by the provided namespace and 57 | // returns the field, field kind and whether is was successful in retrieving the field at all. 58 | // 59 | // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field 60 | // could not be retrieved because it didn't exist. 61 | func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) { 62 | BEGIN: 63 | current, kind, nullable = v.ExtractType(val) 64 | if kind == reflect.Invalid { 65 | return 66 | } 67 | 68 | if namespace == "" { 69 | found = true 70 | return 71 | } 72 | 73 | switch kind { 74 | case reflect.Ptr, reflect.Interface: 75 | return 76 | 77 | case reflect.Struct: 78 | 79 | typ := current.Type() 80 | fld := namespace 81 | var ns string 82 | 83 | if !typ.ConvertibleTo(timeType) { 84 | idx := strings.Index(namespace, namespaceSeparator) 85 | 86 | if idx != -1 { 87 | fld = namespace[:idx] 88 | ns = namespace[idx+1:] 89 | } else { 90 | ns = "" 91 | } 92 | 93 | bracketIdx := strings.Index(fld, leftBracket) 94 | if bracketIdx != -1 { 95 | fld = fld[:bracketIdx] 96 | 97 | ns = namespace[bracketIdx:] 98 | } 99 | 100 | val = current.FieldByName(fld) 101 | namespace = ns 102 | goto BEGIN 103 | } 104 | 105 | case reflect.Array, reflect.Slice: 106 | idx := strings.Index(namespace, leftBracket) 107 | idx2 := strings.Index(namespace, rightBracket) 108 | 109 | arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2]) 110 | 111 | if arrIdx >= current.Len() { 112 | return 113 | } 114 | 115 | startIdx := idx2 + 1 116 | 117 | if startIdx < len(namespace) { 118 | if namespace[startIdx:startIdx+1] == namespaceSeparator { 119 | startIdx++ 120 | } 121 | } 122 | 123 | val = current.Index(arrIdx) 124 | namespace = namespace[startIdx:] 125 | goto BEGIN 126 | 127 | case reflect.Map: 128 | idx := strings.Index(namespace, leftBracket) + 1 129 | idx2 := strings.Index(namespace, rightBracket) 130 | 131 | endIdx := idx2 132 | 133 | if endIdx+1 < len(namespace) { 134 | if namespace[endIdx+1:endIdx+2] == namespaceSeparator { 135 | endIdx++ 136 | } 137 | } 138 | 139 | key := namespace[idx:idx2] 140 | 141 | switch current.Type().Key().Kind() { 142 | case reflect.Int: 143 | i, _ := strconv.Atoi(key) 144 | val = current.MapIndex(reflect.ValueOf(i)) 145 | namespace = namespace[endIdx+1:] 146 | 147 | case reflect.Int8: 148 | i, _ := strconv.ParseInt(key, 10, 8) 149 | val = current.MapIndex(reflect.ValueOf(int8(i))) 150 | namespace = namespace[endIdx+1:] 151 | 152 | case reflect.Int16: 153 | i, _ := strconv.ParseInt(key, 10, 16) 154 | val = current.MapIndex(reflect.ValueOf(int16(i))) 155 | namespace = namespace[endIdx+1:] 156 | 157 | case reflect.Int32: 158 | i, _ := strconv.ParseInt(key, 10, 32) 159 | val = current.MapIndex(reflect.ValueOf(int32(i))) 160 | namespace = namespace[endIdx+1:] 161 | 162 | case reflect.Int64: 163 | i, _ := strconv.ParseInt(key, 10, 64) 164 | val = current.MapIndex(reflect.ValueOf(i)) 165 | namespace = namespace[endIdx+1:] 166 | 167 | case reflect.Uint: 168 | i, _ := strconv.ParseUint(key, 10, 0) 169 | val = current.MapIndex(reflect.ValueOf(uint(i))) 170 | namespace = namespace[endIdx+1:] 171 | 172 | case reflect.Uint8: 173 | i, _ := strconv.ParseUint(key, 10, 8) 174 | val = current.MapIndex(reflect.ValueOf(uint8(i))) 175 | namespace = namespace[endIdx+1:] 176 | 177 | case reflect.Uint16: 178 | i, _ := strconv.ParseUint(key, 10, 16) 179 | val = current.MapIndex(reflect.ValueOf(uint16(i))) 180 | namespace = namespace[endIdx+1:] 181 | 182 | case reflect.Uint32: 183 | i, _ := strconv.ParseUint(key, 10, 32) 184 | val = current.MapIndex(reflect.ValueOf(uint32(i))) 185 | namespace = namespace[endIdx+1:] 186 | 187 | case reflect.Uint64: 188 | i, _ := strconv.ParseUint(key, 10, 64) 189 | val = current.MapIndex(reflect.ValueOf(i)) 190 | namespace = namespace[endIdx+1:] 191 | 192 | case reflect.Float32: 193 | f, _ := strconv.ParseFloat(key, 32) 194 | val = current.MapIndex(reflect.ValueOf(float32(f))) 195 | namespace = namespace[endIdx+1:] 196 | 197 | case reflect.Float64: 198 | f, _ := strconv.ParseFloat(key, 64) 199 | val = current.MapIndex(reflect.ValueOf(f)) 200 | namespace = namespace[endIdx+1:] 201 | 202 | case reflect.Bool: 203 | b, _ := strconv.ParseBool(key) 204 | val = current.MapIndex(reflect.ValueOf(b)) 205 | namespace = namespace[endIdx+1:] 206 | 207 | // reflect.Type = string 208 | default: 209 | val = current.MapIndex(reflect.ValueOf(key)) 210 | namespace = namespace[endIdx+1:] 211 | } 212 | 213 | goto BEGIN 214 | } 215 | 216 | // if got here there was more namespace, cannot go any deeper 217 | panic("Invalid field namespace") 218 | } 219 | 220 | // asInt returns the parameter as a int64 221 | // or panics if it can't convert 222 | func asInt(param string) int64 { 223 | i, err := strconv.ParseInt(param, 0, 64) 224 | panicIf(err) 225 | 226 | return i 227 | } 228 | 229 | // asIntFromTimeDuration parses param as time.Duration and returns it as int64 230 | // or panics on error. 231 | func asIntFromTimeDuration(param string) int64 { 232 | d, err := time.ParseDuration(param) 233 | if err != nil { 234 | // attempt parsing as an integer assuming nanosecond precision 235 | return asInt(param) 236 | } 237 | return int64(d) 238 | } 239 | 240 | // asIntFromType calls the proper function to parse param as int64, 241 | // given a field's Type t. 242 | func asIntFromType(t reflect.Type, param string) int64 { 243 | switch t { 244 | case timeDurationType: 245 | return asIntFromTimeDuration(param) 246 | default: 247 | return asInt(param) 248 | } 249 | } 250 | 251 | // asUint returns the parameter as a uint64 252 | // or panics if it can't convert 253 | func asUint(param string) uint64 { 254 | i, err := strconv.ParseUint(param, 0, 64) 255 | panicIf(err) 256 | 257 | return i 258 | } 259 | 260 | // asFloat64 returns the parameter as a float64 261 | // or panics if it can't convert 262 | func asFloat64(param string) float64 { 263 | i, err := strconv.ParseFloat(param, 64) 264 | panicIf(err) 265 | return i 266 | } 267 | 268 | // asFloat32 returns the parameter as a float32 269 | // or panics if it can't convert 270 | func asFloat32(param string) float64 { 271 | i, err := strconv.ParseFloat(param, 32) 272 | panicIf(err) 273 | return i 274 | } 275 | 276 | // asBool returns the parameter as a bool 277 | // or panics if it can't convert 278 | func asBool(param string) bool { 279 | i, err := strconv.ParseBool(param) 280 | panicIf(err) 281 | 282 | return i 283 | } 284 | 285 | func panicIf(err error) { 286 | if err != nil { 287 | panic(err.Error()) 288 | } 289 | } 290 | 291 | // Checks if field value matches regex. If fl.Field can be cast to Stringer, it uses the Stringer interfaces 292 | // String() return value. Otherwise, it uses fl.Field's String() value. 293 | func fieldMatchesRegexByStringerValOrString(regexFn func() *regexp.Regexp, fl FieldLevel) bool { 294 | regex := regexFn() 295 | switch fl.Field().Kind() { 296 | case reflect.String: 297 | return regex.MatchString(fl.Field().String()) 298 | default: 299 | if stringer, ok := getValue(fl.Field()).(fmt.Stringer); ok { 300 | return regex.MatchString(stringer.String()) 301 | } else { 302 | return regex.MatchString(fl.Field().String()) 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "unsafe" 9 | ) 10 | 11 | // per validate construct 12 | type validate struct { 13 | v *Validate 14 | top reflect.Value 15 | ns []byte 16 | actualNs []byte 17 | errs ValidationErrors 18 | includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise 19 | ffn FilterFunc 20 | slflParent reflect.Value // StructLevel & FieldLevel 21 | slCurrent reflect.Value // StructLevel & FieldLevel 22 | flField reflect.Value // StructLevel & FieldLevel 23 | cf *cField // StructLevel & FieldLevel 24 | ct *cTag // StructLevel & FieldLevel 25 | misc []byte // misc reusable 26 | str1 string // misc reusable 27 | str2 string // misc reusable 28 | fldIsPointer bool // StructLevel & FieldLevel 29 | isPartial bool 30 | hasExcludes bool 31 | } 32 | 33 | // parent and current will be the same the first run of validateStruct 34 | func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { 35 | cs, ok := v.v.structCache.Get(typ) 36 | if !ok { 37 | cs = v.v.extractStructCache(current, typ.Name()) 38 | } 39 | 40 | if len(ns) == 0 && len(cs.name) != 0 { 41 | ns = append(ns, cs.name...) 42 | ns = append(ns, '.') 43 | 44 | structNs = append(structNs, cs.name...) 45 | structNs = append(structNs, '.') 46 | } 47 | 48 | // ct is nil on top level struct, and structs as fields that have no tag info 49 | // so if nil or if not nil and the structonly tag isn't present 50 | if ct == nil || ct.typeof != typeStructOnly { 51 | var f *cField 52 | 53 | for i := 0; i < len(cs.fields); i++ { 54 | f = cs.fields[i] 55 | 56 | if v.isPartial { 57 | if v.ffn != nil { 58 | // used with StructFiltered 59 | if v.ffn(append(structNs, f.name...)) { 60 | continue 61 | } 62 | } else { 63 | // used with StructPartial & StructExcept 64 | _, ok = v.includeExclude[string(append(structNs, f.name...))] 65 | 66 | if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) { 67 | continue 68 | } 69 | } 70 | } 71 | 72 | v.traverseField(ctx, current, current.Field(f.idx), ns, structNs, f, f.cTags) 73 | } 74 | } 75 | 76 | // check if any struct level validations, after all field validations already checked. 77 | // first iteration will have no info about nostructlevel tag, and is checked prior to 78 | // calling the next iteration of validateStruct called from traverseField. 79 | if cs.fn != nil { 80 | v.slflParent = parent 81 | v.slCurrent = current 82 | v.ns = ns 83 | v.actualNs = structNs 84 | 85 | cs.fn(ctx, v) 86 | } 87 | } 88 | 89 | // traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options 90 | func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { 91 | var typ reflect.Type 92 | var kind reflect.Kind 93 | 94 | current, kind, v.fldIsPointer = v.extractTypeInternal(current, false) 95 | 96 | var isNestedStruct bool 97 | 98 | switch kind { 99 | case reflect.Ptr, reflect.Interface, reflect.Invalid: 100 | 101 | if ct == nil { 102 | return 103 | } 104 | 105 | if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault { 106 | return 107 | } 108 | 109 | if ct.typeof == typeOmitNil && (kind != reflect.Invalid && current.IsNil()) { 110 | return 111 | } 112 | 113 | if ct.typeof == typeOmitZero { 114 | return 115 | } 116 | 117 | if ct.hasTag { 118 | if kind == reflect.Invalid { 119 | v.str1 = string(append(ns, cf.altName...)) 120 | if v.v.hasTagNameFunc { 121 | v.str2 = string(append(structNs, cf.name...)) 122 | } else { 123 | v.str2 = v.str1 124 | } 125 | v.errs = append(v.errs, 126 | &fieldError{ 127 | v: v.v, 128 | tag: ct.aliasTag, 129 | actualTag: ct.tag, 130 | ns: v.str1, 131 | structNs: v.str2, 132 | fieldLen: uint8(len(cf.altName)), 133 | structfieldLen: uint8(len(cf.name)), 134 | param: ct.param, 135 | kind: kind, 136 | }, 137 | ) 138 | return 139 | } 140 | 141 | v.str1 = string(append(ns, cf.altName...)) 142 | if v.v.hasTagNameFunc { 143 | v.str2 = string(append(structNs, cf.name...)) 144 | } else { 145 | v.str2 = v.str1 146 | } 147 | if !ct.runValidationWhenNil { 148 | v.errs = append(v.errs, 149 | &fieldError{ 150 | v: v.v, 151 | tag: ct.aliasTag, 152 | actualTag: ct.tag, 153 | ns: v.str1, 154 | structNs: v.str2, 155 | fieldLen: uint8(len(cf.altName)), 156 | structfieldLen: uint8(len(cf.name)), 157 | value: getValue(current), 158 | param: ct.param, 159 | kind: kind, 160 | typ: current.Type(), 161 | }, 162 | ) 163 | return 164 | } 165 | } 166 | 167 | if kind == reflect.Invalid { 168 | return 169 | } 170 | 171 | case reflect.Struct: 172 | isNestedStruct = !current.Type().ConvertibleTo(timeType) 173 | // For backward compatibility before struct level validation tags were supported 174 | // as there were a number of projects relying on `required` not failing on non-pointer 175 | // structs. Since it's basically nonsensical to use `required` with a non-pointer struct 176 | // are explicitly skipping the required validation for it. This WILL be removed in the 177 | // next major version. 178 | if isNestedStruct && !v.v.requiredStructEnabled && ct != nil && ct.tag == requiredTag { 179 | ct = ct.next 180 | } 181 | } 182 | 183 | typ = current.Type() 184 | 185 | OUTER: 186 | for { 187 | if ct == nil || !ct.hasTag || (isNestedStruct && len(cf.name) == 0) { 188 | // isNestedStruct check here 189 | if isNestedStruct { 190 | // if len == 0 then validating using 'Var' or 'VarWithValue' 191 | // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... 192 | // VarWithField - this allows for validating against each field within the struct against a specific value 193 | // pretty handy in certain situations 194 | if len(cf.name) > 0 { 195 | ns = append(append(ns, cf.altName...), '.') 196 | structNs = append(append(structNs, cf.name...), '.') 197 | } 198 | 199 | v.validateStruct(ctx, parent, current, typ, ns, structNs, ct) 200 | } 201 | return 202 | } 203 | 204 | switch ct.typeof { 205 | case typeNoStructLevel: 206 | return 207 | 208 | case typeStructOnly: 209 | if isNestedStruct { 210 | // if len == 0 then validating using 'Var' or 'VarWithValue' 211 | // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... 212 | // VarWithField - this allows for validating against each field within the struct against a specific value 213 | // pretty handy in certain situations 214 | if len(cf.name) > 0 { 215 | ns = append(append(ns, cf.altName...), '.') 216 | structNs = append(append(structNs, cf.name...), '.') 217 | } 218 | 219 | v.validateStruct(ctx, parent, current, typ, ns, structNs, ct) 220 | } 221 | return 222 | 223 | case typeOmitEmpty: 224 | 225 | // set Field Level fields 226 | v.slflParent = parent 227 | v.flField = current 228 | v.cf = cf 229 | v.ct = ct 230 | 231 | if !hasValue(v) { 232 | return 233 | } 234 | 235 | ct = ct.next 236 | continue 237 | 238 | case typeOmitZero: 239 | v.slflParent = parent 240 | v.flField = current 241 | v.cf = cf 242 | v.ct = ct 243 | 244 | if !hasNotZeroValue(v) { 245 | return 246 | } 247 | 248 | ct = ct.next 249 | continue 250 | 251 | case typeOmitNil: 252 | v.slflParent = parent 253 | v.flField = current 254 | v.cf = cf 255 | v.ct = ct 256 | 257 | switch field := v.Field(); field.Kind() { 258 | case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: 259 | if field.IsNil() { 260 | return 261 | } 262 | default: 263 | if v.fldIsPointer && getValue(field) == nil { 264 | return 265 | } 266 | } 267 | 268 | ct = ct.next 269 | continue 270 | 271 | case typeEndKeys: 272 | return 273 | 274 | case typeDive: 275 | 276 | ct = ct.next 277 | 278 | // traverse slice or map here 279 | // or panic ;) 280 | switch kind { 281 | case reflect.Slice, reflect.Array: 282 | 283 | var i64 int64 284 | reusableCF := &cField{} 285 | 286 | for i := 0; i < current.Len(); i++ { 287 | i64 = int64(i) 288 | 289 | v.misc = append(v.misc[0:0], cf.name...) 290 | v.misc = append(v.misc, '[') 291 | v.misc = strconv.AppendInt(v.misc, i64, 10) 292 | v.misc = append(v.misc, ']') 293 | 294 | reusableCF.name = string(v.misc) 295 | 296 | if cf.namesEqual { 297 | reusableCF.altName = reusableCF.name 298 | } else { 299 | v.misc = append(v.misc[0:0], cf.altName...) 300 | v.misc = append(v.misc, '[') 301 | v.misc = strconv.AppendInt(v.misc, i64, 10) 302 | v.misc = append(v.misc, ']') 303 | 304 | reusableCF.altName = string(v.misc) 305 | } 306 | v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct) 307 | } 308 | 309 | case reflect.Map: 310 | 311 | var pv string 312 | reusableCF := &cField{} 313 | 314 | for _, key := range current.MapKeys() { 315 | pv = fmt.Sprintf("%v", key) 316 | 317 | v.misc = append(v.misc[0:0], cf.name...) 318 | v.misc = append(v.misc, '[') 319 | v.misc = append(v.misc, pv...) 320 | v.misc = append(v.misc, ']') 321 | 322 | reusableCF.name = string(v.misc) 323 | 324 | if cf.namesEqual { 325 | reusableCF.altName = reusableCF.name 326 | } else { 327 | v.misc = append(v.misc[0:0], cf.altName...) 328 | v.misc = append(v.misc, '[') 329 | v.misc = append(v.misc, pv...) 330 | v.misc = append(v.misc, ']') 331 | 332 | reusableCF.altName = string(v.misc) 333 | } 334 | 335 | if ct != nil && ct.typeof == typeKeys && ct.keys != nil { 336 | v.traverseField(ctx, parent, key, ns, structNs, reusableCF, ct.keys) 337 | // can be nil when just keys being validated 338 | if ct.next != nil { 339 | v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next) 340 | } 341 | } else { 342 | v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct) 343 | } 344 | } 345 | 346 | default: 347 | // throw error, if not a slice or map then should not have gotten here 348 | // bad dive tag 349 | panic("dive error! can't dive on a non slice or map") 350 | } 351 | 352 | return 353 | 354 | case typeOr: 355 | 356 | v.misc = v.misc[0:0] 357 | 358 | for { 359 | // set Field Level fields 360 | v.slflParent = parent 361 | v.flField = current 362 | v.cf = cf 363 | v.ct = ct 364 | 365 | if ct.fn(ctx, v) { 366 | if ct.isBlockEnd { 367 | ct = ct.next 368 | continue OUTER 369 | } 370 | 371 | // drain rest of the 'or' values, then continue or leave 372 | for { 373 | ct = ct.next 374 | 375 | if ct == nil { 376 | continue OUTER 377 | } 378 | 379 | if ct.typeof != typeOr { 380 | continue OUTER 381 | } 382 | 383 | if ct.isBlockEnd { 384 | ct = ct.next 385 | continue OUTER 386 | } 387 | } 388 | } 389 | 390 | v.misc = append(v.misc, '|') 391 | v.misc = append(v.misc, ct.tag...) 392 | 393 | if ct.hasParam { 394 | v.misc = append(v.misc, '=') 395 | v.misc = append(v.misc, ct.param...) 396 | } 397 | 398 | if ct.isBlockEnd || ct.next == nil { 399 | // if we get here, no valid 'or' value and no more tags 400 | v.str1 = string(append(ns, cf.altName...)) 401 | 402 | if v.v.hasTagNameFunc { 403 | v.str2 = string(append(structNs, cf.name...)) 404 | } else { 405 | v.str2 = v.str1 406 | } 407 | 408 | if ct.hasAlias { 409 | v.errs = append(v.errs, 410 | &fieldError{ 411 | v: v.v, 412 | tag: ct.aliasTag, 413 | actualTag: ct.actualAliasTag, 414 | ns: v.str1, 415 | structNs: v.str2, 416 | fieldLen: uint8(len(cf.altName)), 417 | structfieldLen: uint8(len(cf.name)), 418 | value: getValue(current), 419 | param: ct.param, 420 | kind: kind, 421 | typ: typ, 422 | }, 423 | ) 424 | } else { 425 | tVal := string(v.misc)[1:] 426 | 427 | v.errs = append(v.errs, 428 | &fieldError{ 429 | v: v.v, 430 | tag: tVal, 431 | actualTag: tVal, 432 | ns: v.str1, 433 | structNs: v.str2, 434 | fieldLen: uint8(len(cf.altName)), 435 | structfieldLen: uint8(len(cf.name)), 436 | value: getValue(current), 437 | param: ct.param, 438 | kind: kind, 439 | typ: typ, 440 | }, 441 | ) 442 | } 443 | 444 | return 445 | } 446 | 447 | ct = ct.next 448 | } 449 | 450 | default: 451 | 452 | // set Field Level fields 453 | v.slflParent = parent 454 | v.flField = current 455 | v.cf = cf 456 | v.ct = ct 457 | 458 | if !ct.fn(ctx, v) { 459 | v.str1 = string(append(ns, cf.altName...)) 460 | 461 | if v.v.hasTagNameFunc { 462 | v.str2 = string(append(structNs, cf.name...)) 463 | } else { 464 | v.str2 = v.str1 465 | } 466 | 467 | v.errs = append(v.errs, 468 | &fieldError{ 469 | v: v.v, 470 | tag: ct.aliasTag, 471 | actualTag: ct.tag, 472 | ns: v.str1, 473 | structNs: v.str2, 474 | fieldLen: uint8(len(cf.altName)), 475 | structfieldLen: uint8(len(cf.name)), 476 | value: getValue(current), 477 | param: ct.param, 478 | kind: kind, 479 | typ: typ, 480 | }, 481 | ) 482 | 483 | return 484 | } 485 | ct = ct.next 486 | } 487 | } 488 | } 489 | 490 | func getValue(val reflect.Value) interface{} { 491 | if val.CanInterface() { 492 | return val.Interface() 493 | } 494 | 495 | if val.CanAddr() { 496 | return reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().Interface() 497 | } 498 | 499 | switch val.Kind() { 500 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 501 | return val.Int() 502 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 503 | return val.Uint() 504 | case reflect.Complex64, reflect.Complex128: 505 | return val.Complex() 506 | case reflect.Float32, reflect.Float64: 507 | return val.Float() 508 | default: 509 | return val.String() 510 | } 511 | } 512 | --------------------------------------------------------------------------------