├── .codecov.yml ├── .github └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── base ├── common │ ├── common.go │ └── common_test.go ├── errors.go ├── map │ ├── map.go │ └── map_test.go ├── number │ ├── number.go │ └── number_test.go ├── slice │ ├── slice.go │ └── slice_test.go ├── string │ ├── external.go │ ├── external_test.go │ ├── string.go │ └── string_test.go ├── time │ ├── time.go │ └── time_test.go ├── types.go └── utils.go ├── error_formatter.go ├── error_formatter_test.go ├── errors.go ├── errors_test.go ├── go.mod ├── go.sum ├── internal └── templates │ └── en │ └── templates.go ├── locale.go ├── locale_test.go ├── transform.go ├── transform_test.go ├── utils.go ├── utils_test.go ├── validation.go ├── validation_test.go ├── validator_common.go ├── validator_common_test.go ├── validator_map.go ├── validator_map_test.go ├── validator_number.go ├── validator_number_test.go ├── validator_slice.go ├── validator_slice_test.go ├── validator_str.go ├── validator_str_test.go ├── validator_time.go ├── validator_time_test.go ├── validator_types.go └── validator_utils.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 80..100 3 | round: down 4 | precision: 2 5 | 6 | status: 7 | project: # measuring the overall project coverage 8 | default: # context, you can create multiple ones with custom titles 9 | enabled: yes # must be yes|true to enable this status 10 | target: 85% # specify the target coverage for each commit status 11 | # option: "auto" (must increase from parent commit or pull request base) 12 | # option: "X%" a static target percentage to hit 13 | if_not_found: success # if parent is not found report status as success, error, or failure 14 | if_ci_failed: error # if ci fails report status as success, error, or failure 15 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: ['*'] 6 | tags: ['v*'] 7 | pull_request: 8 | branches: ['*'] 9 | 10 | jobs: 11 | 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | go: ["1.20.x", "1.22.x", "1.23.x"] 17 | include: 18 | - go: 1.23.x 19 | latest: true 20 | 21 | steps: 22 | - name: Setup Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: ${{ matrix.go }} 26 | 27 | - name: Checkout code 28 | uses: actions/checkout@v3 29 | 30 | - name: Load cached dependencies 31 | uses: actions/cache@v3 32 | with: 33 | path: ~/go/pkg/mod 34 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 35 | restore-keys: | 36 | ${{ runner.os }}-go- 37 | 38 | - name: Download Dependencies 39 | run: make prepare 40 | 41 | - name: Lint 42 | run: make lint 43 | 44 | - name: Test 45 | run: make cover 46 | 47 | - name: Upload coverage to codecov.io 48 | uses: codecov/codecov-action@v4 49 | with: 50 | token: ${{ secrets.CODECOV_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test 9 | *.test 10 | *.out 11 | 12 | # Dependency 13 | vendor/ 14 | 15 | # Goland, vscode, OS 16 | .idea 17 | .vscode 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | funlen: 3 | lines: 100 4 | statements: 80 5 | gci: 6 | sections: 7 | - standard 8 | - default 9 | - prefix(github.com/tiendc/go-validator) 10 | gocyclo: 11 | min-complexity: 20 12 | goimports: 13 | local-prefixes: github.com/golangci/golangci-lint 14 | lll: 15 | line-length: 120 16 | misspell: 17 | locale: US 18 | 19 | linters: 20 | enable: 21 | - bodyclose 22 | - contextcheck 23 | - dogsled 24 | - errcheck 25 | - errname 26 | - errorlint 27 | - exhaustive 28 | - copyloopvar 29 | - forbidigo 30 | - forcetypeassert 31 | - funlen 32 | - gci 33 | - gocognit 34 | - goconst 35 | - gocritic 36 | - gocyclo 37 | - err113 38 | - gofmt 39 | - goimports 40 | - mnd 41 | - gosec 42 | - gosimple 43 | - govet 44 | - ineffassign 45 | - lll 46 | - misspell 47 | - nakedret 48 | - nestif 49 | - nilerr 50 | - rowserrcheck 51 | - staticcheck 52 | - stylecheck 53 | - typecheck 54 | - unconvert 55 | - unparam 56 | - unused 57 | - whitespace 58 | 59 | issues: 60 | exclude-rules: 61 | - path: _test\.go 62 | linters: 63 | - funlen 64 | - contextcheck 65 | - staticcheck 66 | - gocyclo 67 | - gocognit 68 | - err113 69 | - forcetypeassert 70 | - wrapcheck 71 | - gomnd 72 | - errorlint 73 | - unused -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dao Cong Tien 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: lint test 2 | 3 | prepare: 4 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.60.3 5 | 6 | build: 7 | @go build -v ./... 8 | 9 | test: 10 | @go test -cover -v ./... 11 | 12 | cover: 13 | @go test -race -coverprofile=coverage.txt -coverpkg=./... ./... 14 | @go tool cover -html=coverage.txt -o coverage.html 15 | 16 | lint: 17 | golangci-lint --timeout=5m0s run -v ./... 18 | 19 | bench: 20 | go test -benchmem -count 100 -bench . 21 | 22 | mod: 23 | go mod tidy && go mod vendor 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Version][gover-img]][gover] [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![GoReport][rpt-img]][rpt] 2 | 3 | # Fast and intuitive validation library for Go 4 | 5 | This lib uses the `Is...` validation functions from the [govalidator](https://github.com/asaskevich/govalidator) project. 6 | 7 | ## Installation 8 | 9 | ```shell 10 | go get github.com/tiendc/go-validator 11 | ``` 12 | 13 | ## Usage 14 | 15 | #### General usage 16 | ```go 17 | import ( 18 | vld "github.com/tiendc/go-validator" 19 | ) 20 | 21 | type Person struct { 22 | FirstName string 23 | LastName string 24 | Birthdate time.Time 25 | 26 | Unemployed bool 27 | Salary uint 28 | Rank string 29 | WorkEmail string 30 | Projects []string 31 | TaskMap map[string]Task 32 | } 33 | var p Person 34 | 35 | errs := vld.Validate( 36 | // Validate first and last names separately 37 | vld.StrLen(&p.FirstName, 3, 30).OnError( 38 | vld.SetField("first_name", nil), 39 | vld.SetCustomKey("ERR_VLD_PERSON_FIRST_NAME_INVALID"), 40 | ), 41 | vld.StrLen(&p.FirstName, 3, 30).OnError( 42 | vld.SetField("last_name", nil), 43 | vld.SetCustomKey("ERR_VLD_PERSON_LAST_NAME_INVALID"), 44 | ), 45 | 46 | // OR use this to produce only one error when one of them fails 47 | vld.Group( 48 | vld.StrLen(&p.FirstName, 3, 30), 49 | vld.StrLen(&p.LastName, 3, 30), 50 | ).OnError( 51 | vld.SetField("name", nil), 52 | vld.SetCustomKey("ERR_VLD_PERSON_NAME_INVALID"), 53 | ), 54 | 55 | // Birthdate is optional, but when it's present, it must be within 1950 and now 56 | vld.When(!p.Birthdate.IsZero()).Then( 57 | vld.TimeRange(p.Birthdate, <1950-01-01>, time.Now()).OnError(...), 58 | ) 59 | 60 | vld.When(!p.Unemployed).Then( 61 | vld.Required(&p.Salary), 62 | // Work email must be valid 63 | vld.StrIsEmail(&p.WorkEmail), 64 | 65 | // Rank must be one of the constants 66 | vld.StrIn(&p.Rank, "Employee", "Manager", "Director"), 67 | vld.Case( 68 | vld.When(p.Rank == "Manager").Then(vld.NumGT(&p.Salary, 10000)), 69 | vld.When(p.Rank == "Director").Then(vld.NumGT(&p.Salary, 30000)), 70 | ).Default( 71 | vld.NumLT(&p.Salary, 10000), 72 | ), 73 | 74 | // Projects are optional, but when they are present, they must be unique and sorted 75 | vld.When(len(p.Projects) > 0).Then( 76 | vld.SliceUnique(p.Projects).OnError(...), 77 | vld.SliceSorted(p.Projects).OnError(...), 78 | ) 79 | ).Else( 80 | // When person is unemployed 81 | vld.NumEQ(&p.Salary, 0), 82 | vld.StrEQ(&p.WorkEmail, ""), 83 | ), 84 | 85 | // Validate slice elements 86 | vld.Slice(p.Projects).ForEach(func(elem int, index int, validator ItemValidator) { 87 | validator.Validate( 88 | vld.StrLen(&elem, 10, 30).OnError( 89 | vld.SetField(fmt.Sprintf("projects[%d]", index), nil), 90 | vld.SetCustomKey("ERR_VLD_PROJECT_NAME_INVALID"), 91 | ), 92 | ) 93 | }), 94 | 95 | // Validate map entries 96 | vld.Map(p.TaskMap).ForEach(func(k string, v Task, validator ItemValidator) { 97 | validator.Validate( 98 | vld.StrLen(&v.Name, 10, 30).OnError( 99 | vld.SetField(fmt.Sprintf("taskMap[%s].name", k), nil), 100 | vld.SetCustomKey("ERR_VLD_TASK_NAME_INVALID"), 101 | ), 102 | ) 103 | }), 104 | 105 | // OTHER FUNCTIONS 106 | // Pass if at least one of the validations passes 107 | vld.OneOf( 108 | // List of validations 109 | ), 110 | 111 | // Pass if exact one of the validations passes 112 | vld.ExactOneOf( 113 | // List of validations 114 | ), 115 | 116 | // Pass if none of the validations passes 117 | vld.NotOf( 118 | // List of validations 119 | ), 120 | ) 121 | 122 | for _, e := range errs { 123 | detail, warnErr := e.BuildDetail() 124 | fmt.Printf("%+v\n", detail) 125 | } 126 | ``` 127 | 128 | #### Error message localization 129 | 130 | - Method 1: inline localization (not recommended) 131 | ```go 132 | errs := Validate( 133 | NumLTE(&p.Age, 40).OnError( 134 | // Override the default template in english 135 | SetTemplate("Tuổi nhân viên phải nhỏ hơn hoặc bằng {{.Max}}"), 136 | ), 137 | ) 138 | 139 | for _, e := range errs { 140 | detail, warnErr := e.BuildDetail() 141 | fmt.Printf("%+v\n", detail) 142 | } 143 | ``` 144 | 145 | - Method 2: using another localization lib (recommended) 146 | ```go 147 | // Supposed you have 2 files defining error messages 148 | // In `error_messages.en`: 149 | // ERR_VLD_EMPLOYEE_AGE_TOO_BIG = "Employee {{.EmployeeName}} has age bigger than {{.Max}}" 150 | // In `error_messages.vi`: 151 | // ERR_VLD_EMPLOYEE_AGE_TOO_BIG = "Nhân viên {{.EmployeeName}} có tuổi lớn hơn {{.Max}}" 152 | 153 | errs := Validate( 154 | NumLTE(&p.Age, 40).OnError( 155 | // Custom param (the default template doesn't have this one) 156 | SetParam("EmployeeName", p.Name), 157 | // Custom key to define custom template to use 158 | SetCustomKey("ERR_VLD_EMPLOYEE_AGE_TOO_BIG"), 159 | ), 160 | ) 161 | 162 | for _, e := range errs { 163 | errKey := e.CustomKey() 164 | errParams : = e.Params() // or e.ParamsWithFormatter() 165 | errorMsg := translationFunction(errKey, errParams) // You need to provide this function 166 | fmt.Printf("%+v\n", errorMsg) 167 | } 168 | ``` 169 | 170 | #### Custom error param formatter 171 | 172 | ```go 173 | errs := Validate( 174 | NumLT(&budget, 1000000).OnError( 175 | SetField("Budget", nil), 176 | ), 177 | ) 178 | 179 | // e.BuildDetail() may produce message `Budget must be less than 1000000`, 180 | // but you may want a message like: `Budget must be less than 1,000,000`. 181 | // Let's use a custom formatter 182 | 183 | errs := Validate( 184 | NumLT(&budget, 1000000).OnError( 185 | SetField("Budget", nil), 186 | SetNumParamFormatter(NewDecimalFormatFunc('.', ',', "%f")), 187 | ), 188 | ) 189 | ``` 190 | 191 | ## Contributing 192 | 193 | - You are welcome to make pull requests for new functions and bug fixes. 194 | 195 | ## License 196 | 197 | - [MIT License](LICENSE) 198 | 199 | [doc-img]: https://pkg.go.dev/badge/github.com/tiendc/go-validator 200 | [doc]: https://pkg.go.dev/github.com/tiendc/go-validator 201 | [gover-img]: https://img.shields.io/badge/Go-%3E%3D%201.20-blue 202 | [gover]: https://img.shields.io/badge/Go-%3E%3D%201.20-blue 203 | [ci-img]: https://github.com/tiendc/go-validator/actions/workflows/go.yml/badge.svg 204 | [ci]: https://github.com/tiendc/go-validator/actions/workflows/go.yml 205 | [cov-img]: https://codecov.io/gh/tiendc/go-validator/branch/main/graph/badge.svg 206 | [cov]: https://codecov.io/gh/tiendc/go-validator 207 | [rpt-img]: https://goreportcard.com/badge/github.com/tiendc/go-validator 208 | [rpt]: https://goreportcard.com/report/github.com/tiendc/go-validator -------------------------------------------------------------------------------- /base/common/common.go: -------------------------------------------------------------------------------- 1 | package commonvalidation 2 | 3 | import ( 4 | "github.com/tiendc/gofn" 5 | 6 | "github.com/tiendc/go-validator/base" 7 | ) 8 | 9 | // Nil checks input must be `nil` 10 | func Nil[T any](v *T) (bool, []base.ErrorParam) { 11 | if v == nil { 12 | return true, nil 13 | } 14 | return false, nil 15 | } 16 | 17 | // NotNil checks input must be not `nil` 18 | func NotNil[T any](v *T) (bool, []base.ErrorParam) { 19 | if v != nil { 20 | return true, nil 21 | } 22 | return false, nil 23 | } 24 | 25 | // Required checks input must be a valid value. 26 | // A required value must be not: 27 | // - zero value (0, "", nil, false) 28 | // - empty slice, array, map, channel 29 | // - pointer points to zero value 30 | func Required(v any) (bool, []base.ErrorParam) { 31 | if v == nil || gofn.FirstNonEmpty(nil, v) == nil { 32 | return false, nil 33 | } 34 | return true, nil 35 | } 36 | -------------------------------------------------------------------------------- /base/common/common_test.go: -------------------------------------------------------------------------------- 1 | package commonvalidation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | func Test_Nil_NotNil(t *testing.T) { 11 | // Success cases 12 | isNil, _ := Nil[any](nil) 13 | isNotNil, _ := NotNil[any](nil) 14 | assert.True(t, isNil && !isNotNil) 15 | 16 | var pStr *string 17 | isNil, _ = Nil(pStr) 18 | isNotNil, _ = NotNil(pStr) 19 | assert.True(t, isNil && !isNotNil) 20 | 21 | var pSlice *[]string 22 | isNil, _ = Nil(pSlice) 23 | isNotNil, _ = NotNil(pSlice) 24 | assert.True(t, isNil && !isNotNil) 25 | 26 | var pMap *map[int]bool 27 | isNil, _ = Nil(pMap) 28 | isNotNil, _ = NotNil(pMap) 29 | assert.True(t, isNil && !isNotNil) 30 | 31 | // Failure cases 32 | isNil, _ = Nil(gofn.ToPtr[any]("")) 33 | isNotNil, _ = NotNil(gofn.ToPtr[any]("")) 34 | assert.False(t, isNil && !isNotNil) 35 | 36 | isNil, _ = Nil(gofn.ToPtr[int](0)) 37 | isNotNil, _ = NotNil(gofn.ToPtr[int](0)) 38 | assert.False(t, isNil && !isNotNil) 39 | 40 | var aSlice []string 41 | isNil, _ = Nil(&aSlice) 42 | isNotNil, _ = NotNil(&aSlice) 43 | assert.False(t, isNil && !isNotNil) 44 | 45 | var aMap map[int]bool 46 | isNil, _ = Nil(&aMap) 47 | isNotNil, _ = NotNil(&aMap) 48 | assert.False(t, isNil && !isNotNil) 49 | } 50 | 51 | func Test_Required(t *testing.T) { 52 | // Failure cases 53 | assert.False(t, gofn.Head(Required(nil))) 54 | 55 | aInt := int32(0) 56 | assert.False(t, gofn.Head(Required(&aInt))) 57 | 58 | aStr := "" 59 | assert.False(t, gofn.Head(Required(&aStr))) 60 | 61 | aSlice := []string{} 62 | assert.False(t, gofn.Head(Required(&aSlice))) 63 | 64 | aMap := map[int]bool{} 65 | assert.False(t, gofn.Head(Required(&aMap))) 66 | 67 | var aAny any 68 | aAny = aMap 69 | assert.False(t, gofn.Head(Required(&aAny))) 70 | 71 | // Success cases 72 | aInt = 1 73 | assert.True(t, gofn.Head(Required(&aInt))) 74 | 75 | aStr = "a" 76 | assert.True(t, gofn.Head(Required(&aStr))) 77 | 78 | aSlice = []string{"a", "b"} 79 | assert.True(t, gofn.Head(Required(&aSlice))) 80 | 81 | aMap = map[int]bool{0: false} 82 | assert.True(t, gofn.Head(Required(&aMap))) 83 | 84 | aAny = aSlice 85 | assert.True(t, gofn.Head(Required(&aAny))) 86 | } 87 | -------------------------------------------------------------------------------- /base/errors.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | // ErrorParam data structure for an error param 4 | type ErrorParam struct { 5 | Key string 6 | Value any 7 | } 8 | -------------------------------------------------------------------------------- /base/map/map.go: -------------------------------------------------------------------------------- 1 | package mapvalidation 2 | 3 | import ( 4 | "github.com/tiendc/gofn" 5 | 6 | "github.com/tiendc/go-validator/base" 7 | ) 8 | 9 | const ( 10 | kLen = "Len" 11 | kItemKey = "ItemKey" 12 | kItemValue = "ItemValue" 13 | ) 14 | 15 | // Len checks map size must be in a range 16 | func Len[K comparable, V any, M ~map[K]V](m M, min, max int) (bool, []base.ErrorParam) { 17 | l := len(m) 18 | if min <= l && l <= max { 19 | return true, nil 20 | } 21 | return false, []base.ErrorParam{{Key: kLen, Value: l}} 22 | } 23 | 24 | // HasKey checks map contains one or multiple keys 25 | func HasKey[K comparable, V any, M ~map[K]V](m M, keys ...K) (bool, []base.ErrorParam) { 26 | for _, k := range keys { 27 | if _, exists := m[k]; !exists { 28 | return false, []base.ErrorParam{{Key: kItemKey, Value: k}} 29 | } 30 | } 31 | return true, nil 32 | } 33 | 34 | // NotHaveKey checks map not contain one or multiple keys 35 | func NotHaveKey[K comparable, V any, M ~map[K]V](m M, keys ...K) (bool, []base.ErrorParam) { 36 | for _, k := range keys { 37 | if _, exists := m[k]; exists { 38 | return false, []base.ErrorParam{{Key: kItemKey, Value: k}} 39 | } 40 | } 41 | return true, nil 42 | } 43 | 44 | // KeyIn checks map keys must be in a list 45 | func KeyIn[K comparable, V any, M ~map[K]V](m M, vals ...K) (bool, []base.ErrorParam) { 46 | keys := gofn.MapKeys(m) 47 | errIdx := base.IsIn(keys, vals) 48 | if errIdx == -1 { 49 | return true, nil 50 | } 51 | return false, []base.ErrorParam{{Key: kItemKey, Value: keys[errIdx]}} 52 | } 53 | 54 | // KeyNotIn checks map keys must be not in a list 55 | func KeyNotIn[K comparable, V any, M ~map[K]V](m M, vals ...K) (bool, []base.ErrorParam) { 56 | keys := gofn.MapKeys(m) 57 | errIdx := base.IsNotIn(keys, vals) 58 | if errIdx == -1 { 59 | return true, nil 60 | } 61 | return false, []base.ErrorParam{{Key: kItemKey, Value: keys[errIdx]}} 62 | } 63 | 64 | // KeyRange checks map keys must be in a range (applies to key type string and number only) 65 | func KeyRange[K base.Number | base.String, V any, M ~map[K]V](m M, min, max K) (bool, []base.ErrorParam) { 66 | for k := range m { 67 | if k < min || k > max { 68 | return false, []base.ErrorParam{{Key: kItemKey, Value: k}} 69 | } 70 | } 71 | return true, nil 72 | } 73 | 74 | // ValueIn checks map values must be in a list 75 | func ValueIn[K comparable, V comparable, M ~map[K]V](m M, vals ...V) (bool, []base.ErrorParam) { 76 | keys, values := getKeysAndValues(m) 77 | errIdx := base.IsIn(values, vals) 78 | if errIdx == -1 { 79 | return true, nil 80 | } 81 | return false, []base.ErrorParam{{Key: kItemKey, Value: keys[errIdx]}, {Key: kItemValue, Value: values[errIdx]}} 82 | } 83 | 84 | // ValueNotIn checks map values must be not in a list 85 | func ValueNotIn[K comparable, V comparable, M ~map[K]V](m M, vals ...V) (bool, []base.ErrorParam) { 86 | keys, values := getKeysAndValues(m) 87 | errIdx := base.IsNotIn(values, vals) 88 | if errIdx == -1 { 89 | return true, nil 90 | } 91 | return false, []base.ErrorParam{{Key: kItemKey, Value: keys[errIdx]}, {Key: kItemValue, Value: values[errIdx]}} 92 | } 93 | 94 | // ValueRange checks map values must be in a range (applies to value type string and number only) 95 | func ValueRange[K comparable, V base.Number | base.String, M ~map[K]V](m M, min, max V) (bool, []base.ErrorParam) { 96 | for k, v := range m { 97 | if v < min || v > max { 98 | return false, []base.ErrorParam{{Key: kItemKey, Value: k}, {Key: kItemValue, Value: v}} 99 | } 100 | } 101 | return true, nil 102 | } 103 | 104 | // ValueUnique checks map values must be unique 105 | func ValueUnique[K comparable, V comparable, M ~map[K]V](m M) (bool, []base.ErrorParam) { 106 | keys, values := getKeysAndValues(m) 107 | errIdx := base.IsUnique(values) 108 | if errIdx == -1 { 109 | return true, nil 110 | } 111 | return false, []base.ErrorParam{{Key: kItemKey, Value: keys[errIdx]}, {Key: kItemValue, Value: values[errIdx]}} 112 | } 113 | 114 | func getKeysAndValues[K comparable, V any, M ~map[K]V](m M) ([]K, []V) { 115 | keys := make([]K, 0, len(m)) 116 | values := make([]V, 0, len(m)) 117 | for k, v := range m { 118 | keys = append(keys, k) 119 | values = append(values, v) 120 | } 121 | return keys, values 122 | } 123 | -------------------------------------------------------------------------------- /base/map/map_test.go: -------------------------------------------------------------------------------- 1 | package mapvalidation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | func Test_Len(t *testing.T) { 11 | assert.True(t, gofn.Head(Len[int, int](map[int]int(nil), 0, 10))) 12 | assert.True(t, gofn.Head(Len(map[int]int{}, 0, 10))) 13 | assert.True(t, gofn.Head(Len(map[int]int{1: 1, 2: 2}, 0, 2))) 14 | 15 | ok, params := Len(map[int]int{1: 1, 2: 2}, 3, 10) 16 | assert.False(t, ok) 17 | assert.True(t, params[0].Key == kLen && params[0].Value == 2) 18 | } 19 | 20 | func Test_HasKey(t *testing.T) { 21 | assert.True(t, gofn.Head(HasKey[int, int](map[int]int(nil)))) 22 | assert.True(t, gofn.Head(HasKey(map[int]int{}))) 23 | assert.True(t, gofn.Head(HasKey(map[int]int{1: 1, 2: 2}, 1, 2, 2, 1))) 24 | 25 | ok, params := HasKey(map[int]int{1: 1, 2: 2}, 3) 26 | assert.False(t, ok) 27 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 3) 28 | 29 | ok, params = HasKey(map[int]int{1: 1, 2: 2}, 1, 2, 3) 30 | assert.False(t, ok) 31 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 3) 32 | } 33 | 34 | func Test_NotHaveKey(t *testing.T) { 35 | assert.True(t, gofn.Head(NotHaveKey[int, int](map[int]int(nil)))) 36 | assert.True(t, gofn.Head(NotHaveKey(map[int]int{}))) 37 | assert.True(t, gofn.Head(NotHaveKey(map[int]int{1: 1, 2: 2}, 0, 3, 4))) 38 | 39 | ok, params := NotHaveKey(map[int]int{1: 1, 2: 2}, 3, 1) 40 | assert.False(t, ok) 41 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 1) 42 | } 43 | 44 | func Test_KeyIn(t *testing.T) { 45 | assert.True(t, gofn.Head(KeyIn[int, int](map[int]int(nil), 0, 1, 2))) 46 | assert.True(t, gofn.Head(KeyIn[int, int](map[int]int(nil)))) 47 | assert.True(t, gofn.Head(KeyIn(map[int]int{}, 0, 1, 2))) 48 | assert.True(t, gofn.Head(KeyIn(map[int]int{1: 1, 2: 2}, 0, 1, 2))) 49 | 50 | ok, params := KeyIn(map[int]int{1: 1, 2: 2}, 0, 1) 51 | assert.False(t, ok) 52 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 2) 53 | } 54 | 55 | func Test_KeyNotIn(t *testing.T) { 56 | assert.True(t, gofn.Head(KeyNotIn[int, int](map[int]int(nil), 0, 1, 2))) 57 | assert.True(t, gofn.Head(KeyNotIn[int, int](map[int]int(nil)))) 58 | assert.True(t, gofn.Head(KeyNotIn(map[int]int{}, 0, 1, 2))) 59 | assert.True(t, gofn.Head(KeyNotIn(map[int]int{1: 1, 2: 2}, 3, 4, 5))) 60 | 61 | ok, params := KeyNotIn(map[int]int{1: 1, 2: 2}, 0, 1) 62 | assert.False(t, ok) 63 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 1) 64 | } 65 | 66 | func Test_KeyRange(t *testing.T) { 67 | assert.True(t, gofn.Head(KeyRange[int, int](map[int]int(nil), 0, 10))) 68 | assert.True(t, gofn.Head(KeyRange[int, int](map[int]int(nil), 0, 10))) 69 | assert.True(t, gofn.Head(KeyRange(map[int]int{}, 0, 10))) 70 | assert.True(t, gofn.Head(KeyRange(map[int]int{1: 1, 2: 2}, 0, 10))) 71 | 72 | ok, params := KeyRange(map[int]int{1: 1, 2: 2}, 2, 10) 73 | assert.False(t, ok) 74 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 1) 75 | } 76 | 77 | func Test_ValueIn(t *testing.T) { 78 | assert.True(t, gofn.Head(ValueIn[int, int](map[int]int(nil), 0, 1, 2))) 79 | assert.True(t, gofn.Head(ValueIn[int, int](map[int]int(nil)))) 80 | assert.True(t, gofn.Head(ValueIn(map[int]int{}, 0, 1, 2))) 81 | assert.True(t, gofn.Head(ValueIn(map[int]int{1: 1, 2: 2}, 0, 1, 2))) 82 | 83 | ok, params := ValueIn(map[int]int{1: 1, 2: 2}, 0, 1) 84 | assert.False(t, ok) 85 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 2 && 86 | params[1].Key == kItemValue && params[1].Value == 2) 87 | } 88 | 89 | func Test_ValueNotIn(t *testing.T) { 90 | assert.True(t, gofn.Head(ValueNotIn[int, int](map[int]int(nil), 0, 1, 2))) 91 | assert.True(t, gofn.Head(ValueNotIn[int, int](map[int]int(nil)))) 92 | assert.True(t, gofn.Head(ValueNotIn(map[int]int{}, 0, 1, 2))) 93 | assert.True(t, gofn.Head(ValueNotIn(map[int]int{1: 1, 2: 2}, 3, 4, 5))) 94 | 95 | ok, params := ValueNotIn(map[int]int{1: 1, 2: 2}, 0, 1) 96 | assert.False(t, ok) 97 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 1 && 98 | params[1].Key == kItemValue && params[1].Value == 1) 99 | } 100 | 101 | func Test_ValueRange(t *testing.T) { 102 | assert.True(t, gofn.Head(ValueRange[int, int](map[int]int(nil), 0, 10))) 103 | assert.True(t, gofn.Head(ValueRange[int, int](map[int]int(nil), 0, 10))) 104 | assert.True(t, gofn.Head(ValueRange(map[int]int{}, 0, 10))) 105 | assert.True(t, gofn.Head(ValueRange(map[int]int{1: 1, 2: 2}, 0, 10))) 106 | 107 | ok, params := ValueRange(map[int]int{1: 1, 2: 2}, 2, 10) 108 | assert.False(t, ok) 109 | assert.True(t, params[0].Key == kItemKey && params[0].Value == 1 && 110 | params[1].Key == kItemValue && params[1].Value == 1) 111 | } 112 | 113 | func Test_ValueUnique(t *testing.T) { 114 | assert.True(t, gofn.Head(ValueUnique[int, int](map[int]int(nil)))) 115 | assert.True(t, gofn.Head(ValueUnique[int, int](map[int]int(nil)))) 116 | assert.True(t, gofn.Head(ValueUnique(map[int]int{}))) 117 | assert.True(t, gofn.Head(ValueUnique(map[int]int{1: 1, 2: 2}))) 118 | 119 | ok, params := ValueUnique(map[int]int{1: 1, 2: 1}) 120 | assert.False(t, ok) 121 | assert.True(t, params[1].Key == kItemValue && params[1].Value == 1) 122 | } 123 | -------------------------------------------------------------------------------- /base/number/number.go: -------------------------------------------------------------------------------- 1 | package numbervalidation 2 | 3 | import ( 4 | "github.com/tiendc/go-validator/base" 5 | ) 6 | 7 | const ( 8 | JsMaxSafeInt = 9007199254740991 9 | JsMinSafeInt = -9007199254740991 10 | ) 11 | 12 | // EQ checks number must equal to another value 13 | func EQ[T base.Number](v T, val T) (bool, []base.ErrorParam) { 14 | return v == val, nil 15 | } 16 | 17 | // GT checks number must be greater than another value 18 | func GT[T base.Number](v T, min T) (bool, []base.ErrorParam) { 19 | return v > min, nil 20 | } 21 | 22 | // GTE checks number must be greater than or equal to another value 23 | func GTE[T base.Number](v T, min T) (bool, []base.ErrorParam) { 24 | return v >= min, nil 25 | } 26 | 27 | // LT checks number must be less than another value 28 | func LT[T base.Number](v T, max T) (bool, []base.ErrorParam) { 29 | return v < max, nil 30 | } 31 | 32 | // LTE checks number must be less than or equal to another value 33 | func LTE[T base.Number](v T, max T) (bool, []base.ErrorParam) { 34 | return v <= max, nil 35 | } 36 | 37 | // Range checks number must be in a range 38 | func Range[T base.Number](v T, min, max T) (bool, []base.ErrorParam) { 39 | return min <= v && v <= max, nil 40 | } 41 | 42 | // In checks number must be in a list 43 | func In[T base.Number](v T, s ...T) (bool, []base.ErrorParam) { 44 | for i := range s { 45 | if v == s[i] { 46 | return true, nil 47 | } 48 | } 49 | return false, nil 50 | } 51 | 52 | // NotIn checks number must be not in a list 53 | func NotIn[T base.Number](v T, s ...T) (bool, []base.ErrorParam) { 54 | for i := range s { 55 | if v == s[i] { 56 | return false, nil 57 | } 58 | } 59 | return true, nil 60 | } 61 | 62 | // DivisibleBy checks number must be divisible by a value 63 | func DivisibleBy[T base.Int | base.UInt](v T, div T) (bool, []base.ErrorParam) { 64 | if div == 0 { 65 | return false, nil 66 | } 67 | return v%div == 0, nil 68 | } 69 | 70 | // JsSafeInt checks number must be a Javascript safe integer (max 2^53-1) 71 | func JsSafeInt[T base.Int | base.UInt](v T) (bool, []base.ErrorParam) { 72 | vv := int64(v) 73 | return JsMinSafeInt <= vv && vv <= JsMaxSafeInt, nil 74 | } 75 | -------------------------------------------------------------------------------- /base/number/number_test.go: -------------------------------------------------------------------------------- 1 | package numbervalidation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | func Test_EQ(t *testing.T) { 11 | assert.True(t, gofn.Head(EQ(123, 123))) 12 | assert.True(t, gofn.Head(EQ(-1.1, -1.1))) 13 | assert.False(t, gofn.Head(EQ(100, 101))) 14 | assert.False(t, gofn.Head(EQ(1.123, 1.1234))) 15 | } 16 | 17 | func Test_GT(t *testing.T) { 18 | assert.True(t, gofn.Head(GT(123, 122))) 19 | assert.True(t, gofn.Head(GT(0, -1))) 20 | assert.False(t, gofn.Head(GT(100, 100))) 21 | assert.True(t, gofn.Head(GTE(0, -1))) 22 | assert.True(t, gofn.Head(GTE(100, 100))) 23 | assert.True(t, gofn.Head(GTE(-1, -1))) 24 | } 25 | 26 | func Test_LT(t *testing.T) { 27 | assert.True(t, gofn.Head(LT(123, 124))) 28 | assert.True(t, gofn.Head(LT(-1, 0))) 29 | assert.False(t, gofn.Head(LT(100, 100))) 30 | assert.True(t, gofn.Head(LTE(-1, -1))) 31 | assert.True(t, gofn.Head(LTE(100, 100))) 32 | assert.True(t, gofn.Head(LTE(-1, 0))) 33 | } 34 | 35 | func Test_Range(t *testing.T) { 36 | assert.True(t, gofn.Head(Range(200, 100, 200))) 37 | assert.True(t, gofn.Head(Range(100, 100, 200))) 38 | assert.True(t, gofn.Head(Range(150, 100, 200))) 39 | assert.False(t, gofn.Head(Range(99, 100, 200))) 40 | } 41 | 42 | func Test_In(t *testing.T) { 43 | assert.True(t, gofn.Head(In(1, 1))) 44 | assert.True(t, gofn.Head(In(1, 0, 1, 2))) 45 | assert.False(t, gofn.Head(In(0, 1, 2))) 46 | } 47 | 48 | func Test_NotIn(t *testing.T) { 49 | assert.True(t, gofn.Head(NotIn(1, 0))) 50 | assert.True(t, gofn.Head(NotIn(1, 2, 3))) 51 | assert.False(t, gofn.Head(NotIn(0, 1, 0, 2))) 52 | } 53 | 54 | func Test_DivisibleBy(t *testing.T) { 55 | assert.True(t, gofn.Head(DivisibleBy(10, 5))) 56 | assert.True(t, gofn.Head(DivisibleBy(22, 11))) 57 | assert.True(t, gofn.Head(DivisibleBy(10, 1))) 58 | assert.True(t, gofn.Head(DivisibleBy(0, 123))) 59 | assert.False(t, gofn.Head(DivisibleBy(8, 3))) 60 | assert.False(t, gofn.Head(DivisibleBy(8, 0))) 61 | } 62 | 63 | func Test_JsSafeInt(t *testing.T) { 64 | assert.True(t, gofn.Head(JsSafeInt(10))) 65 | assert.True(t, gofn.Head(JsSafeInt(9007199254740991))) 66 | assert.True(t, gofn.Head(JsSafeInt(-9007199254740991))) 67 | 68 | assert.False(t, gofn.Head(JsSafeInt(9007199254740992))) 69 | assert.False(t, gofn.Head(JsSafeInt(-9007199254740992))) 70 | } 71 | -------------------------------------------------------------------------------- /base/slice/slice.go: -------------------------------------------------------------------------------- 1 | package slicevalidation 2 | 3 | import ( 4 | "github.com/tiendc/go-validator/base" 5 | ) 6 | 7 | const ( 8 | kLen = "Len" 9 | kItemValue = "ItemValue" 10 | kItemIndex = "ItemIndex" 11 | ) 12 | 13 | // EQ checks a slice must equal to a slice 14 | func EQ[T comparable, S ~[]T](s S, s2 S) (bool, []base.ErrorParam) { 15 | if len(s) != len(s2) { 16 | return false, nil 17 | } 18 | for i, v := range s { 19 | if s[i] != s2[i] { 20 | return false, []base.ErrorParam{{Key: kItemValue, Value: v}, {Key: kItemIndex, Value: i}} 21 | } 22 | } 23 | return true, nil 24 | } 25 | 26 | // Len checks slice length must be in a range 27 | func Len[T any, S ~[]T](s S, min, max int) (bool, []base.ErrorParam) { 28 | l := len(s) 29 | if min <= l && l <= max { 30 | return true, nil 31 | } 32 | return false, []base.ErrorParam{{Key: kLen, Value: l}} 33 | } 34 | 35 | // Unique checks slice items must be unique 36 | func Unique[T comparable, S ~[]T](s S) (bool, []base.ErrorParam) { 37 | errIdx := base.IsUnique(s) 38 | if errIdx == -1 { 39 | return true, nil 40 | } 41 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[errIdx]}, {Key: kItemIndex, Value: errIdx}} 42 | } 43 | 44 | // UniqueBy checks slice items must be unique 45 | func UniqueBy[T any, U comparable, S ~[]T](s S, keyFn func(T) U) (bool, []base.ErrorParam) { 46 | errIdx := base.IsUniqueBy(s, keyFn) 47 | if errIdx == -1 { 48 | return true, nil 49 | } 50 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[errIdx]}, {Key: kItemIndex, Value: errIdx}} 51 | } 52 | 53 | // Sorted checks slice items must be in ascending order 54 | func Sorted[T base.Number | base.String, S ~[]T](s S) (bool, []base.ErrorParam) { 55 | for i := 1; i < len(s); i++ { 56 | if s[i-1] > s[i] { 57 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[i]}, {Key: kItemIndex, Value: i}} 58 | } 59 | } 60 | return true, nil 61 | } 62 | 63 | // SortedBy checks slice items must be in ascending order defined by the function 64 | func SortedBy[T any, U base.Number | base.String, S ~[]T](s S, keyFn func(T) U) (bool, []base.ErrorParam) { 65 | length := len(s) 66 | if length <= 1 { 67 | return true, nil 68 | } 69 | prevVal := keyFn(s[0]) 70 | for i := 1; i < length; i++ { 71 | currVal := keyFn(s[i]) 72 | if prevVal > currVal { 73 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[i]}, {Key: kItemIndex, Value: i}} 74 | } 75 | prevVal = currVal 76 | } 77 | return true, nil 78 | } 79 | 80 | // SortedDesc checks slice items must be in descending order 81 | func SortedDesc[T base.Number | base.String, S ~[]T](s S) (bool, []base.ErrorParam) { 82 | for i := 1; i < len(s); i++ { 83 | if s[i-1] < s[i] { 84 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[i]}, {Key: kItemIndex, Value: i}} 85 | } 86 | } 87 | return true, nil 88 | } 89 | 90 | // SortedDescBy checks slice items must be in descending order defined by the function 91 | func SortedDescBy[T any, U base.Number | base.String, S ~[]T](s S, keyFn func(T) U) (bool, []base.ErrorParam) { 92 | length := len(s) 93 | if length <= 1 { 94 | return true, nil 95 | } 96 | prevVal := keyFn(s[0]) 97 | for i := 1; i < length; i++ { 98 | currVal := keyFn(s[i]) 99 | if prevVal < currVal { 100 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[i]}, {Key: kItemIndex, Value: i}} 101 | } 102 | prevVal = currVal 103 | } 104 | return true, nil 105 | } 106 | 107 | // HasElem checks slice must contain the specified values 108 | func HasElem[T comparable, S ~[]T](s S, values ...T) (bool, []base.ErrorParam) { 109 | elemMap := base.ToMap(s) 110 | for _, v := range values { 111 | if _, exists := elemMap[v]; !exists { 112 | return false, []base.ErrorParam{{Key: kItemValue, Value: v}} 113 | } 114 | } 115 | return true, nil 116 | } 117 | 118 | // HasElemBy checks slice must contain values using custom function 119 | func HasElemBy[T any, S ~[]T](s S, isExistFn func(T) bool) (bool, []base.ErrorParam) { 120 | for i := range s { 121 | if !isExistFn(s[i]) { 122 | return false, []base.ErrorParam{{Key: kItemIndex, Value: i}} 123 | } 124 | } 125 | return true, nil 126 | } 127 | 128 | // NotHaveElem checks slice must not contain the specified values 129 | func NotHaveElem[T comparable, S ~[]T](s S, values ...T) (bool, []base.ErrorParam) { 130 | elemMap := base.ToMap(s) 131 | for _, v := range values { 132 | if _, exists := elemMap[v]; exists { 133 | return false, []base.ErrorParam{{Key: kItemValue, Value: v}} 134 | } 135 | } 136 | return true, nil 137 | } 138 | 139 | // NotHaveElemBy checks slice must not contain values using custom function 140 | func NotHaveElemBy[T any, S ~[]T](s S, isExistFn func(T) bool) (bool, []base.ErrorParam) { 141 | for i := range s { 142 | if isExistFn(s[i]) { 143 | return false, []base.ErrorParam{{Key: kItemIndex, Value: i}} 144 | } 145 | } 146 | return true, nil 147 | } 148 | 149 | // ElemIn checks slice items must be in a list 150 | func ElemIn[T comparable, S ~[]T](s S, values ...T) (bool, []base.ErrorParam) { 151 | errIdx := base.IsIn(s, values) 152 | if errIdx == -1 { 153 | return true, nil 154 | } 155 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[errIdx]}, {Key: kItemIndex, Value: errIdx}} 156 | } 157 | 158 | // ElemNotIn checks slice items must be not in a list 159 | func ElemNotIn[T comparable, S ~[]T](s S, values ...T) (bool, []base.ErrorParam) { 160 | errIdx := base.IsNotIn(s, values) 161 | if errIdx == -1 { 162 | return true, nil 163 | } 164 | return false, []base.ErrorParam{{Key: kItemValue, Value: s[errIdx]}, {Key: kItemIndex, Value: errIdx}} 165 | } 166 | 167 | // ElemRange checks slice items must be in a range (applies to item type string or number only) 168 | func ElemRange[T base.Number | base.String, S ~[]T](s S, min, max T) (bool, []base.ErrorParam) { 169 | for i, v := range s { 170 | if v < min || v > max { 171 | return false, []base.ErrorParam{{Key: kItemValue, Value: v}, {Key: kItemIndex, Value: i}} 172 | } 173 | } 174 | return true, nil 175 | } 176 | -------------------------------------------------------------------------------- /base/slice/slice_test.go: -------------------------------------------------------------------------------- 1 | package slicevalidation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | func Test_EQ(t *testing.T) { 11 | assert.True(t, gofn.Head(EQ[int]([]int(nil), nil))) 12 | assert.True(t, gofn.Head(EQ([]int{}, []int{}))) 13 | assert.True(t, gofn.Head(EQ([]int{1, 2}, []int{1, 2}))) 14 | 15 | ok, params := EQ([]int{1, 2}, []int{1, 2, 3}) 16 | assert.False(t, ok) 17 | assert.Nil(t, params) 18 | ok, params = EQ([]int{1, 2}, []int{1, 3}) 19 | assert.False(t, ok) 20 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 2 && 21 | params[1].Key == kItemIndex && params[1].Value == 1) 22 | } 23 | 24 | func Test_Len(t *testing.T) { 25 | assert.True(t, gofn.Head(Len[int]([]int(nil), 0, 10))) 26 | assert.True(t, gofn.Head(Len([]int{}, 0, 10))) 27 | assert.True(t, gofn.Head(Len([]int{1, 2}, 0, 2))) 28 | 29 | ok, params := Len([]int{1, 2}, 3, 10) 30 | assert.False(t, ok) 31 | assert.True(t, params[0].Key == kLen && params[0].Value == 2) 32 | } 33 | 34 | func Test_Unique(t *testing.T) { 35 | assert.True(t, gofn.Head(Unique[int]([]int(nil)))) 36 | assert.True(t, gofn.Head(Unique([]int{}))) 37 | assert.True(t, gofn.Head(Unique([]int{1, 2, 3}))) 38 | 39 | ok, params := Unique([]int{0, 1, 2, 0}) 40 | assert.False(t, ok) 41 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 0 && 42 | params[1].Key == kItemIndex && params[1].Value == 3) 43 | } 44 | 45 | func Test_UniqueBy(t *testing.T) { 46 | assert.True(t, gofn.Head(UniqueBy[int]([]int(nil), func(v int) int { return v }))) 47 | assert.True(t, gofn.Head(UniqueBy([]int{}, func(v int) int { return v }))) 48 | assert.True(t, gofn.Head(UniqueBy([]int{1, 2, 3}, func(v int) int { return v }))) 49 | 50 | ok, params := UniqueBy([]int{0, 1, 2, 0}, func(v int) int { return v }) 51 | assert.False(t, ok) 52 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 0 && 53 | params[1].Key == kItemIndex && params[1].Value == 3) 54 | 55 | // Custom type 56 | type St struct { 57 | Key int 58 | Val string 59 | } 60 | 61 | assert.True(t, gofn.Head(UniqueBy([]St{{1, "1"}, {2, "2"}, {3, "3"}}, 62 | func(v St) int { return v.Key }))) 63 | 64 | ok, params = UniqueBy([]St{{1, "1"}, {2, "2"}, {1, "1"}}, 65 | func(v St) string { return v.Val }) 66 | assert.False(t, ok) 67 | assert.True(t, params[0].Key == kItemValue && params[0].Value == St{1, "1"} && 68 | params[1].Key == kItemIndex && params[1].Value == 2) 69 | } 70 | 71 | func Test_Sorted(t *testing.T) { 72 | assert.True(t, gofn.Head(Sorted[int]([]int(nil)))) 73 | assert.True(t, gofn.Head(Sorted([]int{}))) 74 | assert.True(t, gofn.Head(Sorted([]int{1, 2, 3}))) 75 | 76 | ok, params := Sorted([]int{0, 1, 2, -1}) 77 | assert.False(t, ok) 78 | assert.True(t, params[0].Key == kItemValue && params[0].Value == -1 && 79 | params[1].Key == kItemIndex && params[1].Value == 3) 80 | } 81 | 82 | func Test_SortedBy(t *testing.T) { 83 | assert.True(t, gofn.Head(SortedBy[any]([]any(nil), func(v any) int { return v.(int) }))) 84 | assert.True(t, gofn.Head(SortedBy([]any{}, func(v any) int { return v.(int) }))) 85 | assert.True(t, gofn.Head(SortedBy([]any{1, 2, 3}, func(v any) int { return v.(int) }))) 86 | 87 | ok, params := SortedBy([]any{0, 1, 2, -1}, func(v any) int { return v.(int) }) 88 | assert.False(t, ok) 89 | assert.True(t, params[0].Key == kItemValue && params[0].Value == -1 && 90 | params[1].Key == kItemIndex && params[1].Value == 3) 91 | 92 | type St struct { 93 | Key int 94 | Val string 95 | } 96 | assert.True(t, gofn.Head(SortedBy([]St{{1, "1"}, {2, "2"}, {3, "3"}}, 97 | func(v St) int { return v.Key }))) 98 | 99 | ok, params = SortedBy([]St{{1, "1"}, {2, "2"}, {3, "1"}}, 100 | func(v St) string { return v.Val }) 101 | assert.False(t, ok) 102 | assert.True(t, params[0].Key == kItemValue && params[0].Value == St{3, "1"} && 103 | params[1].Key == kItemIndex && params[1].Value == 2) 104 | } 105 | 106 | func Test_SortedDesc(t *testing.T) { 107 | assert.True(t, gofn.Head(SortedDesc[int]([]int(nil)))) 108 | assert.True(t, gofn.Head(SortedDesc([]int{}))) 109 | assert.True(t, gofn.Head(SortedDesc([]int{3, 2, 0, -1}))) 110 | 111 | ok, params := SortedDesc([]int{2, -1, 1, 0}) 112 | assert.False(t, ok) 113 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 1 && 114 | params[1].Key == kItemIndex && params[1].Value == 2) 115 | } 116 | 117 | func Test_SortedDescBy(t *testing.T) { 118 | assert.True(t, gofn.Head(SortedDescBy[any]([]any(nil), func(v any) int { return v.(int) }))) 119 | assert.True(t, gofn.Head(SortedDescBy([]any{}, func(v any) int { return v.(int) }))) 120 | assert.True(t, gofn.Head(SortedDescBy([]any{3, 2, 0, -1}, func(v any) int { return v.(int) }))) 121 | 122 | ok, params := SortedDescBy([]any{2, -1, 1, 0}, func(v any) int { return v.(int) }) 123 | assert.False(t, ok) 124 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 1 && 125 | params[1].Key == kItemIndex && params[1].Value == 2) 126 | 127 | type St struct { 128 | Key int 129 | Val string 130 | } 131 | assert.True(t, gofn.Head(SortedDescBy([]St{{3, "3"}, {2, "2"}, {1, "1"}}, 132 | func(v St) int { return v.Key }))) 133 | 134 | ok, params = SortedDescBy([]St{{3, "3"}, {2, "2"}, {1, "3"}}, 135 | func(v St) string { return v.Val }) 136 | assert.False(t, ok) 137 | assert.True(t, params[0].Key == kItemValue && params[0].Value == St{1, "3"} && 138 | params[1].Key == kItemIndex && params[1].Value == 2) 139 | } 140 | 141 | func Test_HasElem(t *testing.T) { 142 | assert.True(t, gofn.Head(HasElem[int]([]int(nil)))) 143 | assert.True(t, gofn.Head(HasElem([]int{}))) 144 | assert.True(t, gofn.Head(HasElem([]int{1, 2}, 1, 2))) 145 | 146 | ok, params := HasElem([]int{2, 0, 1, 2}, 1, 2, 3) 147 | assert.False(t, ok) 148 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 3) 149 | } 150 | 151 | func Test_HasElemBy(t *testing.T) { 152 | assert.True(t, gofn.Head(HasElemBy[any]([]any(nil), func(v any) bool { return v == 1 }))) 153 | assert.True(t, gofn.Head(HasElemBy([]any{}, func(v any) bool { return v == 1 }))) 154 | assert.True(t, gofn.Head(HasElemBy([]any{1, 2}, func(v any) bool { return v == 1 || v == 2 }))) 155 | 156 | ok, params := HasElemBy([]any{2, 0, 1, 2}, func(v any) bool { return v == 1 || v == 2 }) 157 | assert.False(t, ok) 158 | assert.True(t, params[0].Key == kItemIndex && params[0].Value == 1) 159 | } 160 | 161 | func Test_NotHaveElem(t *testing.T) { 162 | assert.True(t, gofn.Head(NotHaveElem[int]([]int(nil)))) 163 | assert.True(t, gofn.Head(NotHaveElem([]int{}))) 164 | assert.True(t, gofn.Head(NotHaveElem([]int{1, 2}, 0, 3, 4))) 165 | 166 | ok, params := NotHaveElem([]int{2, 0, 1, 2}, 3, 4, 0) 167 | assert.False(t, ok) 168 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 0) 169 | } 170 | 171 | func Test_NotHaveElemBy(t *testing.T) { 172 | assert.True(t, gofn.Head(NotHaveElemBy[any]([]any(nil), func(v any) bool { return v == 1 }))) 173 | assert.True(t, gofn.Head(NotHaveElemBy([]any{}, func(v any) bool { return v == 1 }))) 174 | assert.True(t, gofn.Head(NotHaveElemBy([]any{1, 2}, func(v any) bool { return v == 3 || v == 4 }))) 175 | 176 | ok, params := NotHaveElemBy([]any{2, 0, 1, 2}, func(v any) bool { return v == 1 || v == 2 }) 177 | assert.False(t, ok) 178 | assert.True(t, params[0].Key == kItemIndex && params[0].Value == 0) 179 | } 180 | 181 | func Test_ElemIn(t *testing.T) { 182 | assert.True(t, gofn.Head(ElemIn[int]([]int(nil), 0, 1, 2))) 183 | assert.True(t, gofn.Head(ElemIn([]int{}, 0, 1, 2))) 184 | assert.True(t, gofn.Head(ElemIn([]int{1, 2}, 0, 1, 2))) 185 | 186 | ok, params := ElemIn([]int{2, 0, 1, 2}, 1, 2) 187 | assert.False(t, ok) 188 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 0 && 189 | params[1].Key == kItemIndex && params[1].Value == 1) 190 | } 191 | 192 | func Test_ElemNotIn(t *testing.T) { 193 | assert.True(t, gofn.Head(ElemNotIn[int]([]int(nil), 0, 1, 2))) 194 | assert.True(t, gofn.Head(ElemNotIn([]int{}, 0, 1, 2))) 195 | assert.True(t, gofn.Head(ElemNotIn([]int{1, 2}))) 196 | assert.True(t, gofn.Head(ElemNotIn([]int{1, 2}, 0, 3, 4, 5))) 197 | 198 | ok, params := ElemNotIn([]int{0, 1, 2}, 1, 2, 3) 199 | assert.False(t, ok) 200 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 1 && 201 | params[1].Key == kItemIndex && params[1].Value == 1) 202 | } 203 | 204 | func Test_ElemRange(t *testing.T) { 205 | assert.True(t, gofn.Head(ElemRange[int]([]int(nil), 0, 10))) 206 | assert.True(t, gofn.Head(ElemRange([]int{}, 0, 10))) 207 | assert.True(t, gofn.Head(ElemRange([]int{0, 1, 2, 10}, 0, 10))) 208 | 209 | ok, params := ElemRange([]int{0, 1, 11, 13}, 0, 10) 210 | assert.False(t, ok) 211 | assert.True(t, params[0].Key == kItemValue && params[0].Value == 11 && 212 | params[1].Key == kItemIndex && params[1].Value == 2) 213 | } 214 | -------------------------------------------------------------------------------- /base/string/external.go: -------------------------------------------------------------------------------- 1 | package stringvalidation 2 | 3 | import ( 4 | extVld "github.com/asaskevich/govalidator" 5 | 6 | "github.com/tiendc/go-validator/base" 7 | ) 8 | 9 | func IsEmail[T base.String](s T) (bool, []base.ErrorParam) { 10 | return extVld.IsEmail(string(s)), nil 11 | } 12 | func IsExistingEmail[T base.String](s T) (bool, []base.ErrorParam) { 13 | return extVld.IsExistingEmail(string(s)), nil 14 | } 15 | func IsURL[T base.String](s T) (bool, []base.ErrorParam) { 16 | return extVld.IsURL(string(s)), nil 17 | } 18 | func IsRequestURL[T base.String](s T) (bool, []base.ErrorParam) { 19 | return extVld.IsRequestURL(string(s)), nil 20 | } 21 | func IsRequestURI[T base.String](s T) (bool, []base.ErrorParam) { 22 | return extVld.IsRequestURI(string(s)), nil 23 | } 24 | func IsAlpha[T base.String](s T) (bool, []base.ErrorParam) { 25 | return extVld.IsAlpha(string(s)), nil 26 | } 27 | func IsUTFLetter[T base.String](s T) (bool, []base.ErrorParam) { 28 | return extVld.IsUTFLetter(string(s)), nil 29 | } 30 | func IsAlphanumeric[T base.String](s T) (bool, []base.ErrorParam) { 31 | return extVld.IsAlphanumeric(string(s)), nil 32 | } 33 | func IsUTFLetterNumeric[T base.String](s T) (bool, []base.ErrorParam) { 34 | return extVld.IsUTFLetterNumeric(string(s)), nil 35 | } 36 | func IsNumeric[T base.String](s T) (bool, []base.ErrorParam) { 37 | return extVld.IsNumeric(string(s)), nil 38 | } 39 | func IsUTFNumeric[T base.String](s T) (bool, []base.ErrorParam) { 40 | return extVld.IsUTFNumeric(string(s)), nil 41 | } 42 | func IsUTFDigit[T base.String](s T) (bool, []base.ErrorParam) { 43 | return extVld.IsUTFDigit(string(s)), nil 44 | } 45 | func IsHexadecimal[T base.String](s T) (bool, []base.ErrorParam) { 46 | return extVld.IsHexadecimal(string(s)), nil 47 | } 48 | func IsHexcolor[T base.String](s T) (bool, []base.ErrorParam) { 49 | return extVld.IsHexcolor(string(s)), nil 50 | } 51 | func IsRGBcolor[T base.String](s T) (bool, []base.ErrorParam) { 52 | return extVld.IsRGBcolor(string(s)), nil 53 | } 54 | func IsLowerCase[T base.String](s T) (bool, []base.ErrorParam) { 55 | return extVld.IsLowerCase(string(s)), nil 56 | } 57 | func IsUpperCase[T base.String](s T) (bool, []base.ErrorParam) { 58 | return extVld.IsUpperCase(string(s)), nil 59 | } 60 | func HasLowerCase[T base.String](s T) (bool, []base.ErrorParam) { 61 | return extVld.HasLowerCase(string(s)), nil 62 | } 63 | func HasUpperCase[T base.String](s T) (bool, []base.ErrorParam) { 64 | return extVld.HasUpperCase(string(s)), nil 65 | } 66 | func IsInt[T base.String](s T) (bool, []base.ErrorParam) { 67 | return extVld.IsInt(string(s)), nil 68 | } 69 | func IsFloat[T base.String](s T) (bool, []base.ErrorParam) { 70 | return extVld.IsFloat(string(s)), nil 71 | } 72 | func HasWhitespaceOnly[T base.String](s T) (bool, []base.ErrorParam) { 73 | return extVld.HasWhitespaceOnly(string(s)), nil 74 | } 75 | func HasWhitespace[T base.String](s T) (bool, []base.ErrorParam) { 76 | return extVld.HasWhitespace(string(s)), nil 77 | } 78 | func IsUUIDv3[T base.String](s T) (bool, []base.ErrorParam) { 79 | return extVld.IsUUIDv3(string(s)), nil 80 | } 81 | func IsUUIDv4[T base.String](s T) (bool, []base.ErrorParam) { 82 | return extVld.IsUUIDv4(string(s)), nil 83 | } 84 | func IsUUIDv5[T base.String](s T) (bool, []base.ErrorParam) { 85 | return extVld.IsUUIDv5(string(s)), nil 86 | } 87 | func IsUUID[T base.String](s T) (bool, []base.ErrorParam) { 88 | return extVld.IsUUID(string(s)), nil 89 | } 90 | func IsULID[T base.String](s T) (bool, []base.ErrorParam) { 91 | return extVld.IsULID(string(s)), nil 92 | } 93 | func IsCreditCard[T base.String](s T) (bool, []base.ErrorParam) { 94 | return extVld.IsCreditCard(string(s)), nil 95 | } 96 | func IsISBN13[T base.String](s T) (bool, []base.ErrorParam) { 97 | return extVld.IsISBN13(string(s)), nil 98 | } 99 | func IsISBN10[T base.String](s T) (bool, []base.ErrorParam) { 100 | return extVld.IsISBN10(string(s)), nil 101 | } 102 | func IsISBN[T base.String](s T) (bool, []base.ErrorParam) { 103 | success, _ := IsISBN10(s) 104 | if success { 105 | return true, nil 106 | } 107 | return IsISBN13(s) 108 | } 109 | func IsJSON[T base.String](s T) (bool, []base.ErrorParam) { 110 | return extVld.IsJSON(string(s)), nil 111 | } 112 | func IsMultibyte[T base.String](s T) (bool, []base.ErrorParam) { 113 | return extVld.IsMultibyte(string(s)), nil 114 | } 115 | func IsASCII[T base.String](s T) (bool, []base.ErrorParam) { 116 | return extVld.IsASCII(string(s)), nil 117 | } 118 | func IsPrintableASCII[T base.String](s T) (bool, []base.ErrorParam) { 119 | return extVld.IsPrintableASCII(string(s)), nil 120 | } 121 | func IsFullWidth[T base.String](s T) (bool, []base.ErrorParam) { 122 | return extVld.IsFullWidth(string(s)), nil 123 | } 124 | func IsHalfWidth[T base.String](s T) (bool, []base.ErrorParam) { 125 | return extVld.IsHalfWidth(string(s)), nil 126 | } 127 | func IsVariableWidth[T base.String](s T) (bool, []base.ErrorParam) { 128 | return extVld.IsVariableWidth(string(s)), nil 129 | } 130 | func IsBase64[T base.String](s T) (bool, []base.ErrorParam) { 131 | return extVld.IsBase64(string(s)), nil 132 | } 133 | func IsFilePath[T base.String](s T) (bool, []base.ErrorParam) { 134 | success, _ := IsWinFilePath(s) 135 | if success { 136 | return true, nil 137 | } 138 | return IsUnixFilePath(s) 139 | } 140 | func IsWinFilePath[T base.String](s T) (bool, []base.ErrorParam) { 141 | return extVld.IsWinFilePath(string(s)), nil 142 | } 143 | func IsUnixFilePath[T base.String](s T) (bool, []base.ErrorParam) { 144 | return extVld.IsUnixFilePath(string(s)), nil 145 | } 146 | func IsDataURI[T base.String](s T) (bool, []base.ErrorParam) { 147 | return extVld.IsDataURI(string(s)), nil 148 | } 149 | func IsMagnetURI[T base.String](s T) (bool, []base.ErrorParam) { 150 | return extVld.IsMagnetURI(string(s)), nil 151 | } 152 | func IsISO3166Alpha2[T base.String](s T) (bool, []base.ErrorParam) { 153 | return extVld.IsISO3166Alpha2(string(s)), nil 154 | } 155 | func IsISO3166Alpha3[T base.String](s T) (bool, []base.ErrorParam) { 156 | return extVld.IsISO3166Alpha3(string(s)), nil 157 | } 158 | func IsISO639Alpha2[T base.String](s T) (bool, []base.ErrorParam) { 159 | return extVld.IsISO693Alpha2(string(s)), nil // FIXME: IsISO693 is a wrong name? 160 | } 161 | func IsISO639Alpha3b[T base.String](s T) (bool, []base.ErrorParam) { 162 | return extVld.IsISO693Alpha3b(string(s)), nil // FIXME: IsISO693 is a wrong name? 163 | } 164 | func IsDNSName[T base.String](s T) (bool, []base.ErrorParam) { 165 | return extVld.IsDNSName(string(s)), nil 166 | } 167 | func IsSHA3224[T base.String](s T) (bool, []base.ErrorParam) { 168 | return extVld.IsSHA3224(string(s)), nil 169 | } 170 | func IsSHA3256[T base.String](s T) (bool, []base.ErrorParam) { 171 | return extVld.IsSHA3256(string(s)), nil 172 | } 173 | func IsSHA3384[T base.String](s T) (bool, []base.ErrorParam) { 174 | return extVld.IsSHA3384(string(s)), nil 175 | } 176 | func IsSHA3512[T base.String](s T) (bool, []base.ErrorParam) { 177 | return extVld.IsSHA3512(string(s)), nil 178 | } 179 | func IsSHA512[T base.String](s T) (bool, []base.ErrorParam) { 180 | return extVld.IsSHA512(string(s)), nil 181 | } 182 | func IsSHA384[T base.String](s T) (bool, []base.ErrorParam) { 183 | return extVld.IsSHA384(string(s)), nil 184 | } 185 | func IsSHA256[T base.String](s T) (bool, []base.ErrorParam) { 186 | return extVld.IsSHA256(string(s)), nil 187 | } 188 | func IsSHA1[T base.String](s T) (bool, []base.ErrorParam) { 189 | return extVld.IsSHA1(string(s)), nil 190 | } 191 | func IsTiger192[T base.String](s T) (bool, []base.ErrorParam) { 192 | return extVld.IsTiger192(string(s)), nil 193 | } 194 | func IsTiger160[T base.String](s T) (bool, []base.ErrorParam) { 195 | return extVld.IsTiger160(string(s)), nil 196 | } 197 | func IsTiger128[T base.String](s T) (bool, []base.ErrorParam) { 198 | return extVld.IsTiger128(string(s)), nil 199 | } 200 | func IsRipeMD160[T base.String](s T) (bool, []base.ErrorParam) { 201 | return extVld.IsRipeMD160(string(s)), nil 202 | } 203 | func IsRipeMD128[T base.String](s T) (bool, []base.ErrorParam) { 204 | return extVld.IsRipeMD128(string(s)), nil 205 | } 206 | func IsCRC32[T base.String](s T) (bool, []base.ErrorParam) { 207 | return extVld.IsCRC32(string(s)), nil 208 | } 209 | func IsCRC32b[T base.String](s T) (bool, []base.ErrorParam) { 210 | return extVld.IsCRC32b(string(s)), nil 211 | } 212 | func IsMD5[T base.String](s T) (bool, []base.ErrorParam) { 213 | return extVld.IsMD5(string(s)), nil 214 | } 215 | func IsMD4[T base.String](s T) (bool, []base.ErrorParam) { 216 | return extVld.IsMD4(string(s)), nil 217 | } 218 | func IsDialString[T base.String](s T) (bool, []base.ErrorParam) { 219 | return extVld.IsDialString(string(s)), nil 220 | } 221 | func IsIP[T base.String](s T) (bool, []base.ErrorParam) { 222 | return extVld.IsIP(string(s)), nil 223 | } 224 | func IsPort[T base.String](s T) (bool, []base.ErrorParam) { 225 | return extVld.IsPort(string(s)), nil 226 | } 227 | func IsIPv4[T base.String](s T) (bool, []base.ErrorParam) { 228 | return extVld.IsIPv4(string(s)), nil 229 | } 230 | func IsIPv6[T base.String](s T) (bool, []base.ErrorParam) { 231 | return extVld.IsIPv6(string(s)), nil 232 | } 233 | func IsCIDR[T base.String](s T) (bool, []base.ErrorParam) { 234 | return extVld.IsCIDR(string(s)), nil 235 | } 236 | func IsMAC[T base.String](s T) (bool, []base.ErrorParam) { 237 | return extVld.IsMAC(string(s)), nil 238 | } 239 | func IsHost[T base.String](s T) (bool, []base.ErrorParam) { 240 | return extVld.IsHost(string(s)), nil 241 | } 242 | func IsMongoID[T base.String](s T) (bool, []base.ErrorParam) { 243 | return extVld.IsMongoID(string(s)), nil 244 | } 245 | func IsLatitude[T base.String](s T) (bool, []base.ErrorParam) { 246 | return extVld.IsLatitude(string(s)), nil 247 | } 248 | func IsLongitude[T base.String](s T) (bool, []base.ErrorParam) { 249 | return extVld.IsLongitude(string(s)), nil 250 | } 251 | func IsIMEI[T base.String](s T) (bool, []base.ErrorParam) { 252 | return extVld.IsIMEI(string(s)), nil 253 | } 254 | func IsIMSI[T base.String](s T) (bool, []base.ErrorParam) { 255 | return extVld.IsIMSI(string(s)), nil 256 | } 257 | func IsRsaPublicKey[T base.String](s T, keyLen int) (bool, []base.ErrorParam) { 258 | return extVld.IsRsaPublicKey(string(s), keyLen), nil 259 | } 260 | func IsRegex[T base.String](s T) (bool, []base.ErrorParam) { 261 | return extVld.IsRegex(string(s)), nil 262 | } 263 | func IsSSN[T base.String](s T) (bool, []base.ErrorParam) { 264 | return extVld.IsSSN(string(s)), nil 265 | } 266 | func IsSemver[T base.String](s T) (bool, []base.ErrorParam) { 267 | return extVld.IsSemver(string(s)), nil 268 | } 269 | func IsTime[T base.String](s T, layout string) (bool, []base.ErrorParam) { 270 | return extVld.IsTime(string(s), layout), nil 271 | } 272 | func IsUnixTime[T base.String](s T) (bool, []base.ErrorParam) { 273 | return extVld.IsUnixTime(string(s)), nil 274 | } 275 | func IsRFC3339[T base.String](s T) (bool, []base.ErrorParam) { 276 | return extVld.IsRFC3339(string(s)), nil 277 | } 278 | func IsRFC3339WithoutZone[T base.String](s T) (bool, []base.ErrorParam) { 279 | return extVld.IsRFC3339WithoutZone(string(s)), nil 280 | } 281 | func IsISO4217[T base.String](s T) (bool, []base.ErrorParam) { 282 | return extVld.IsISO4217(string(s)), nil 283 | } 284 | func IsE164[T base.String](s T) (bool, []base.ErrorParam) { 285 | return extVld.IsE164(string(s)), nil 286 | } 287 | -------------------------------------------------------------------------------- /base/string/external_test.go: -------------------------------------------------------------------------------- 1 | package stringvalidation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | func Test_IsEmail(t *testing.T) { 11 | assert.True(t, gofn.Head(IsEmail("test@example.com"))) 12 | assert.True(t, gofn.Head(IsEmail("Test@eXample.com"))) 13 | assert.False(t, gofn.Head(IsEmail("Test@eXample"))) 14 | } 15 | 16 | func Test_IsExistingEmail(t *testing.T) { 17 | assert.True(t, gofn.Head(IsExistingEmail("Test@eXample.com"))) 18 | assert.False(t, gofn.Head(IsExistingEmail("Test@eXample"))) 19 | } 20 | 21 | func Test_IsURL(t *testing.T) { 22 | assert.True(t, gofn.Head(IsURL("http://host.com"))) 23 | assert.True(t, gofn.Head(IsURL("localhost:10000"))) 24 | assert.False(t, gofn.Head(IsURL("host"))) 25 | } 26 | 27 | func Test_IsRequestURL(t *testing.T) { 28 | assert.True(t, gofn.Head(IsRequestURL("http://host.com:3000"))) 29 | assert.False(t, gofn.Head(IsRequestURL("abc3000"))) 30 | } 31 | 32 | func Test_IsRequestURI(t *testing.T) { 33 | assert.True(t, gofn.Head(IsRequestURI("http://host.com:3000"))) 34 | assert.False(t, gofn.Head(IsRequestURI("abc3000"))) 35 | } 36 | 37 | func Test_IsAlpha(t *testing.T) { 38 | assert.True(t, gofn.Head(IsAlpha("abc"))) 39 | assert.False(t, gofn.Head(IsAlpha("abc123"))) 40 | } 41 | 42 | func Test_IsUTFLetter(t *testing.T) { 43 | assert.True(t, gofn.Head(IsUTFLetter("Tiến"))) 44 | assert.False(t, gofn.Head(IsUTFLetter("abc123"))) 45 | } 46 | 47 | func Test_IsAlphanumeric(t *testing.T) { 48 | assert.True(t, gofn.Head(IsAlphanumeric("abc012"))) 49 | assert.False(t, gofn.Head(IsAlphanumeric("abc-123"))) 50 | } 51 | 52 | func Test_IsUTFLetterNumeric(t *testing.T) { 53 | assert.True(t, gofn.Head(IsUTFLetterNumeric("abc012"))) 54 | assert.False(t, gofn.Head(IsUTFLetterNumeric("abc+123"))) 55 | } 56 | 57 | func Test_IsNumeric(t *testing.T) { 58 | assert.True(t, gofn.Head(IsNumeric("012"))) 59 | assert.False(t, gofn.Head(IsNumeric("-123"))) 60 | } 61 | 62 | func Test_IsUTFNumeric(t *testing.T) { 63 | assert.True(t, gofn.Head(IsUTFNumeric("012"))) 64 | assert.False(t, gofn.Head(IsUTFNumeric("abc123"))) 65 | } 66 | 67 | func Test_IsUTFDigit(t *testing.T) { 68 | assert.True(t, gofn.Head(IsUTFDigit("123456"))) 69 | assert.False(t, gofn.Head(IsUTFDigit("abc123"))) 70 | } 71 | 72 | func Test_IsHexadecimal(t *testing.T) { 73 | assert.True(t, gofn.Head(IsHexadecimal("abdcef"))) 74 | assert.False(t, gofn.Head(IsHexadecimal("abdcefg"))) 75 | } 76 | 77 | func Test_IsHexcolor(t *testing.T) { 78 | assert.True(t, gofn.Head(IsHexcolor("#ffffff"))) 79 | assert.False(t, gofn.Head(IsHexcolor("#fffffg"))) 80 | } 81 | 82 | func Test_IsRGBcolor(t *testing.T) { 83 | assert.True(t, gofn.Head(IsRGBcolor("rgb(1,1,1)"))) 84 | assert.False(t, gofn.Head(IsRGBcolor("rgb(1,1,a)"))) 85 | } 86 | 87 | func Test_IsLowerCase(t *testing.T) { 88 | assert.True(t, gofn.Head(IsLowerCase("abc123"))) 89 | assert.False(t, gofn.Head(IsLowerCase("aBc123"))) 90 | } 91 | 92 | func Test_IsUpperCase(t *testing.T) { 93 | assert.True(t, gofn.Head(IsUpperCase("ABC123"))) 94 | assert.False(t, gofn.Head(IsUpperCase("aBc123"))) 95 | } 96 | 97 | func Test_HasLowerCase(t *testing.T) { 98 | assert.True(t, gofn.Head(HasLowerCase("aBC123"))) 99 | assert.False(t, gofn.Head(HasLowerCase("ABC123"))) 100 | } 101 | 102 | func Test_HasUpperCase(t *testing.T) { 103 | assert.True(t, gofn.Head(HasUpperCase("aBc123"))) 104 | assert.False(t, gofn.Head(HasUpperCase("abc123"))) 105 | } 106 | 107 | func Test_IsInt(t *testing.T) { 108 | assert.True(t, gofn.Head(IsInt("-123"))) 109 | assert.False(t, gofn.Head(IsInt("123a"))) 110 | } 111 | 112 | func Test_IsFloat(t *testing.T) { 113 | assert.True(t, gofn.Head(IsFloat("-123.123"))) 114 | assert.False(t, gofn.Head(IsFloat("123.123f"))) 115 | } 116 | 117 | func Test_HasWhitespaceOnly(t *testing.T) { 118 | assert.True(t, gofn.Head(HasWhitespaceOnly(" "))) 119 | assert.False(t, gofn.Head(HasWhitespaceOnly("ab c"))) 120 | } 121 | 122 | func Test_HasWhitespace(t *testing.T) { 123 | assert.True(t, gofn.Head(HasWhitespace(" b c"))) 124 | assert.False(t, gofn.Head(HasWhitespace("abc"))) 125 | } 126 | 127 | func Test_IsUUIDv3(t *testing.T) { 128 | assert.True(t, gofn.Head(IsUUIDv3("2037da5d-a759-3ba8-bfeb-84519bb669c6"))) 129 | assert.False(t, gofn.Head(IsUUIDv3("2037da5d-a759-3ba8-bfeb-84519bb669g6"))) 130 | } 131 | 132 | func Test_IsUUIDv4(t *testing.T) { 133 | assert.True(t, gofn.Head(IsUUIDv4("fb3e2e7c-e478-4d76-aa84-9880d6eb67f4"))) 134 | assert.False(t, gofn.Head(IsUUIDv4("fb3e2e7c-e478-4d76-aa84-9880d6eb67g4"))) 135 | } 136 | 137 | func Test_IsUUIDv5(t *testing.T) { 138 | assert.True(t, gofn.Head(IsUUIDv5("aa43954a-ced3-5b84-9931-d3516b2e1867"))) 139 | assert.False(t, gofn.Head(IsUUIDv5("aa43954a-ced3-5b84-9931-d3516b2e186g"))) 140 | } 141 | 142 | func Test_IsUUID(t *testing.T) { 143 | assert.True(t, gofn.Head(IsUUID("2037da5d-a759-3ba8-bfeb-84519bb669c6"))) 144 | assert.True(t, gofn.Head(IsUUID("fb3e2e7c-e478-4d76-aa84-9880d6eb67f4"))) 145 | assert.True(t, gofn.Head(IsUUID("aa43954a-ced3-5b84-9931-d3516b2e1867"))) 146 | assert.False(t, gofn.Head(IsUUID("fb3e2e7c-e478-4d76-aa84-9880d6eb67g4"))) 147 | } 148 | 149 | func Test_IsULID(t *testing.T) { 150 | assert.True(t, gofn.Head(IsULID("01G65Z755AFWAKHE12NY0CQ9FH"))) 151 | assert.False(t, gofn.Head(IsULID("01G65Z755AFWAKHE12NY0CQ9FHx"))) 152 | } 153 | 154 | func Test_IsCreditCard(t *testing.T) { 155 | assert.True(t, gofn.Head(IsCreditCard("4242-8888-8888-8888"))) 156 | assert.False(t, gofn.Head(IsCreditCard("5242-8888-8888-8888"))) 157 | } 158 | 159 | func Test_IsISBN13(t *testing.T) { 160 | assert.True(t, gofn.Head(IsISBN13("978-3-16-148410-0"))) 161 | assert.False(t, gofn.Head(IsISBN13("0-306-40615-2"))) 162 | } 163 | 164 | func Test_IsISBN10(t *testing.T) { 165 | assert.True(t, gofn.Head(IsISBN10("0-306-40615-2"))) 166 | assert.False(t, gofn.Head(IsISBN10("978-3-16-148410-0"))) 167 | } 168 | 169 | func Test_IsISBN(t *testing.T) { 170 | assert.True(t, gofn.Head(IsISBN("978-3-16-148410-0"))) 171 | assert.True(t, gofn.Head(IsISBN("0-306-40615-2"))) 172 | } 173 | 174 | func Test_IsJSON(t *testing.T) { 175 | assert.True(t, gofn.Head(IsJSON("123"))) 176 | assert.True(t, gofn.Head(IsJSON(`{"k":123}`))) 177 | assert.False(t, gofn.Head(IsJSON(`{"k":v}`))) 178 | } 179 | 180 | func Test_IsMultibyte(t *testing.T) { 181 | assert.True(t, gofn.Head(IsMultibyte("Chào buổi sáng"))) 182 | assert.False(t, gofn.Head(IsMultibyte("abc 123"))) 183 | } 184 | 185 | func Test_IsASCII(t *testing.T) { 186 | assert.True(t, gofn.Head(IsASCII("hello"))) 187 | assert.False(t, gofn.Head(IsASCII("hello Tiến"))) 188 | } 189 | 190 | func Test_IsPrintableASCII(t *testing.T) { 191 | assert.True(t, gofn.Head(IsPrintableASCII("hello"))) 192 | assert.False(t, gofn.Head(IsPrintableASCII("hello\t"))) 193 | } 194 | 195 | func Test_IsFullWidth(t *testing.T) { 196 | assert.True(t, gofn.Head(IsFullWidth("Type or Paste"))) 197 | assert.False(t, gofn.Head(IsFullWidth("abc123"))) 198 | } 199 | 200 | func Test_IsHalfWidth(t *testing.T) { 201 | assert.True(t, gofn.Head(IsHalfWidth("abc123"))) 202 | assert.False(t, gofn.Head(IsHalfWidth("Type or Paste"))) 203 | } 204 | 205 | func Test_IsVariableWidth(t *testing.T) { 206 | assert.True(t, gofn.Head(IsVariableWidth("Type or Paste"))) 207 | assert.False(t, gofn.Head(IsVariableWidth("Type"))) 208 | assert.False(t, gofn.Head(IsVariableWidth("Type"))) 209 | } 210 | 211 | func Test_IsBase64(t *testing.T) { 212 | assert.True(t, gofn.Head(IsBase64("aGVsbG8gd29ybGQ="))) 213 | assert.False(t, gofn.Head(IsBase64("hello"))) 214 | } 215 | 216 | func Test_IsFilePath(t *testing.T) { 217 | assert.True(t, gofn.Head(IsFilePath("C:\\abc"))) 218 | assert.True(t, gofn.Head(IsFilePath("/root"))) 219 | } 220 | 221 | func Test_IsWinFilePath(t *testing.T) { 222 | assert.True(t, gofn.Head(IsWinFilePath("C:\\abc"))) 223 | assert.False(t, gofn.Head(IsWinFilePath("/root"))) 224 | } 225 | 226 | func Test_IsUnixFilePath(t *testing.T) { 227 | assert.True(t, gofn.Head(IsUnixFilePath("/root/path"))) 228 | } 229 | 230 | func Test_IsDataURI(t *testing.T) { 231 | assert.True(t, gofn.Head(IsDataURI("data:image/png;base64,iVBORw0OHwAAAABJRU5ErkJggg=="))) 232 | assert.False(t, gofn.Head(IsDataURI("example.com"))) 233 | } 234 | 235 | func Test_IsMagnetURI(t *testing.T) { 236 | assert.True(t, gofn.Head(IsMagnetURI("magnet:?xt=urn:xxxxxx:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&dn=bbb&tr=ccc"))) 237 | assert.False(t, gofn.Head(IsMagnetURI("example.com"))) 238 | } 239 | 240 | func Test_IsISO3166Alpha2(t *testing.T) { 241 | assert.True(t, gofn.Head(IsISO3166Alpha2("VN"))) 242 | assert.False(t, gofn.Head(IsISO3166Alpha2("VND"))) 243 | } 244 | 245 | func Test_IsISO3166Alpha3(t *testing.T) { 246 | assert.True(t, gofn.Head(IsISO3166Alpha3("USA"))) 247 | assert.False(t, gofn.Head(IsISO3166Alpha3("USD"))) 248 | } 249 | 250 | func Test_IsISO639Alpha2(t *testing.T) { 251 | assert.True(t, gofn.Head(IsISO639Alpha2("vi"))) 252 | assert.False(t, gofn.Head(IsISO639Alpha2("vie"))) 253 | } 254 | 255 | func Test_IsISO639Alpha3b(t *testing.T) { 256 | assert.True(t, gofn.Head(IsISO639Alpha3b("vie"))) 257 | assert.False(t, gofn.Head(IsISO639Alpha3b("vi"))) 258 | } 259 | 260 | func Test_IsDNSName(t *testing.T) { 261 | assert.True(t, gofn.Head(IsDNSName("MX"))) 262 | assert.False(t, gofn.Head(IsDNSName(""))) 263 | } 264 | 265 | func Test_IsSHA3224(t *testing.T) { 266 | assert.True(t, gofn.Head(IsSHA3224("d9cf77bff9d00e47ad2d4841539bf72b0cfeff5e106819625e4f99f4"))) 267 | assert.False(t, gofn.Head(IsSHA3224("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606e"+ 268 | "fb61c8cb4f173e3a526761dd"))) 269 | } 270 | 271 | func Test_IsSHA3256(t *testing.T) { 272 | assert.True(t, gofn.Head(IsSHA3256("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8"))) 273 | assert.False(t, gofn.Head(IsSHA3256("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606e"+ 274 | "fb61c8cb4f173e3a526761dd"))) 275 | } 276 | 277 | // nolint: goconst 278 | func Test_IsSHA3384(t *testing.T) { 279 | assert.True(t, gofn.Head(IsSHA3384("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606ef"+ 280 | "b61c8cb4f173e3a526761dd"))) 281 | assert.False(t, gofn.Head(IsSHA3384("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8"))) 282 | } 283 | 284 | func Test_IsSHA3512(t *testing.T) { 285 | assert.True(t, gofn.Head(IsSHA3512("44a94f581e500c84b59bb38b990f499b4619c6774fd51e0e8534f89ee18dc3c6ea6ed096b"+ 286 | "37ff5a38517cec3ceb46bba2bd6989aef708da76fa6345810f9b1c4"))) 287 | assert.False(t, gofn.Head(IsSHA3512("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606ef"+ 288 | "b61c8cb4f173e3a526761dd"))) 289 | } 290 | 291 | func Test_IsSHA512(t *testing.T) { 292 | assert.True(t, gofn.Head(IsSHA512("44a94f581e500c84b59bb38b990f499b4619c6774fd51e0e8534f89ee18dc3c6ea6ed096b3"+ 293 | "7ff5a38517cec3ceb46bba2bd6989aef708da76fa6345810f9b1c4"))) 294 | assert.False(t, gofn.Head(IsSHA512("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb"+ 295 | "61c8cb4f173e3a526761dd"))) 296 | } 297 | 298 | func Test_IsSHA384(t *testing.T) { 299 | assert.True(t, gofn.Head(IsSHA384("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb"+ 300 | "61c8cb4f173e3a526761dd"))) 301 | assert.False(t, gofn.Head(IsSHA384("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8"))) 302 | } 303 | 304 | func Test_IsSHA256(t *testing.T) { 305 | assert.True(t, gofn.Head(IsSHA256("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8"))) 306 | assert.False(t, gofn.Head(IsSHA256("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606ef"+ 307 | "b61c8cb4f173e3a526761dd"))) 308 | } 309 | 310 | func Test_IsSHA1(t *testing.T) { 311 | assert.True(t, gofn.Head(IsSHA1("4b2b79b6f371ca18f1216461cffeaddf6848a50e"))) 312 | assert.False(t, gofn.Head(IsSHA1("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8"))) 313 | } 314 | 315 | func Test_IsTiger192(t *testing.T) { 316 | assert.True(t, gofn.Head(IsTiger192("8d0484881b88804442e390b0784003a1981db0b31b5bf7af"))) 317 | assert.False(t, gofn.Head(IsTiger192("8d0484881b88804442e390b0784003a1981db0b3"))) 318 | } 319 | 320 | func Test_IsTiger160(t *testing.T) { 321 | assert.True(t, gofn.Head(IsTiger160("8d0484881b88804442e390b0784003a1981db0b3"))) 322 | assert.False(t, gofn.Head(IsTiger160("8d0484881b88804442e390b0784003a1981db0b31b5bf7af"))) 323 | } 324 | 325 | func Test_IsTiger128(t *testing.T) { 326 | assert.True(t, gofn.Head(IsTiger128("b8d02ffdd4d054b5327eed23b20e8716"))) 327 | assert.False(t, gofn.Head(IsTiger128("8d0484881b88804442e390b0784003a1981db0b3"))) 328 | } 329 | 330 | func Test_IsRipeMD160(t *testing.T) { 331 | assert.True(t, gofn.Head(IsRipeMD160("2330032576b1cc1df37b92abec140368c7396745"))) 332 | assert.False(t, gofn.Head(IsRipeMD160("afbed790a7824ade4a3ae531285f8fe8"))) 333 | } 334 | 335 | func Test_IsRipeMD128(t *testing.T) { 336 | assert.True(t, gofn.Head(IsRipeMD128("afbed790a7824ade4a3ae531285f8fe8"))) 337 | assert.False(t, gofn.Head(IsRipeMD128("2330032576b1cc1df37b92abec140368c7396745"))) 338 | } 339 | 340 | func Test_IsCRC32(t *testing.T) { 341 | assert.True(t, gofn.Head(IsCRC32("cd71f992"))) 342 | assert.False(t, gofn.Head(IsCRC32("abc123"))) 343 | } 344 | 345 | func Test_IsCRC32b(t *testing.T) { 346 | assert.True(t, gofn.Head(IsCRC32b("94d6f306"))) 347 | assert.False(t, gofn.Head(IsCRC32b("abc123"))) 348 | } 349 | 350 | func Test_IsMD5(t *testing.T) { 351 | assert.True(t, gofn.Head(IsMD5("ec02c59dee6faaca3189bace969c22d3"))) 352 | assert.False(t, gofn.Head(IsMD5("abc"))) 353 | } 354 | 355 | func Test_IsMD4(t *testing.T) { 356 | assert.True(t, gofn.Head(IsMD4("538a2a786ca1bc47694c0bc3f3ac3228"))) 357 | assert.False(t, gofn.Head(IsMD4("abc"))) 358 | } 359 | 360 | func Test_IsDialString(t *testing.T) { 361 | assert.True(t, gofn.Head(IsDialString("golang.org:3000"))) 362 | assert.False(t, gofn.Head(IsDialString("abc"))) 363 | } 364 | 365 | func Test_IsIP(t *testing.T) { 366 | assert.True(t, gofn.Head(IsIP("1.1.1.1"))) 367 | assert.True(t, gofn.Head(IsIP("FEDC:BA98:768A:0C98:FEBA:CB87:7678:1111"))) 368 | } 369 | 370 | func Test_IsPort(t *testing.T) { 371 | assert.True(t, gofn.Head(IsPort("1234"))) 372 | assert.False(t, gofn.Head(IsPort("70000"))) 373 | } 374 | 375 | func Test_IsIPv4(t *testing.T) { 376 | assert.True(t, gofn.Head(IsIPv4("1.1.1.1"))) 377 | assert.False(t, gofn.Head(IsIPv4("FEDC:BA98:768A:0C98:FEBA:CB87:7678:1111"))) 378 | } 379 | 380 | func Test_IsIPv6(t *testing.T) { 381 | assert.True(t, gofn.Head(IsIPv6("FEDC:BA98:768A:0C98:FEBA:CB87:7678:1111"))) 382 | assert.False(t, gofn.Head(IsIPv6("1.1.1.1"))) 383 | } 384 | 385 | func Test_IsCIDR(t *testing.T) { 386 | assert.True(t, gofn.Head(IsCIDR("255.255.255.0/32"))) 387 | assert.False(t, gofn.Head(IsCIDR("255.255.255.255"))) 388 | } 389 | 390 | func Test_IsMAC(t *testing.T) { 391 | assert.True(t, gofn.Head(IsMAC("01-80-C2-FF-FF-FF"))) 392 | assert.False(t, gofn.Head(IsMAC("1.1.1.1"))) 393 | } 394 | 395 | func Test_IsHost(t *testing.T) { 396 | assert.True(t, gofn.Head(IsHost("example.com"))) 397 | assert.True(t, gofn.Head(IsHost("11.22.33.33"))) 398 | assert.False(t, gofn.Head(IsHost("abc/123"))) 399 | } 400 | 401 | func Test_IsMongoID(t *testing.T) { 402 | assert.True(t, gofn.Head(IsMongoID("507f1f77bcf86cd799439011"))) 403 | assert.False(t, gofn.Head(IsMongoID("abc123"))) 404 | } 405 | 406 | func Test_IsLatitude(t *testing.T) { 407 | assert.True(t, gofn.Head(IsLatitude("38.8951"))) 408 | assert.False(t, gofn.Head(IsLatitude("abc123"))) 409 | } 410 | 411 | func Test_IsLongitude(t *testing.T) { 412 | assert.True(t, gofn.Head(IsLongitude("-77.0364"))) 413 | assert.False(t, gofn.Head(IsLongitude("abc123"))) 414 | } 415 | 416 | func Test_IsIMEI(t *testing.T) { 417 | assert.True(t, gofn.Head(IsIMEI("11222222333333"))) 418 | assert.False(t, gofn.Head(IsIMEI("abc123"))) 419 | } 420 | 421 | func Test_IsIMSI(t *testing.T) { 422 | assert.True(t, gofn.Head(IsIMSI("313460000000001"))) 423 | assert.False(t, gofn.Head(IsIMSI("11222222333333"))) 424 | } 425 | 426 | func Test_IsRsaPublicKey(t *testing.T) { 427 | assert.True(t, gofn.Head(IsRsaPublicKey("MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ1oCVw+tgIf52KApencj1hHW/KtvqwfnmQsF"+ 428 | "Cmb4IhywfFAPbJ5qx1jX1HPDb+v/yMXzGbvlcE2kFzjYFy/LUsCAwEAAQ==", 512))) 429 | assert.False(t, gofn.Head(IsRsaPublicKey("MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ1oCVw+tgIf52KApencj1hHW/KtvqwfnmQsF"+ 430 | "Cmb4IhywfFAPbJ5qx1jX1HPDb+v/yMXzGbvlcE2kFzjYFy/LUsCAwEAAQ==", 1024))) 431 | } 432 | 433 | func Test_IsRegex(t *testing.T) { 434 | assert.True(t, gofn.Head(IsRegex("[0-9a-z]+"))) 435 | } 436 | 437 | func Test_IsSSN(t *testing.T) { 438 | assert.True(t, gofn.Head(IsSSN("111-22-3333"))) 439 | assert.False(t, gofn.Head(IsSSN("111.22.3333"))) 440 | } 441 | 442 | func Test_IsSemver(t *testing.T) { 443 | assert.True(t, gofn.Head(IsSemver("1.2.3"))) 444 | assert.False(t, gofn.Head(IsSemver("1.2"))) 445 | } 446 | 447 | func Test_IsTime(t *testing.T) { 448 | assert.True(t, gofn.Head(IsTime("2023-10-01", "2006-02-01"))) 449 | assert.False(t, gofn.Head(IsTime("2023/10/01", "2006-02-01"))) 450 | } 451 | 452 | func Test_IsUnixTime(t *testing.T) { 453 | assert.True(t, gofn.Head(IsUnixTime("123456789"))) 454 | assert.False(t, gofn.Head(IsUnixTime("12345678912345678912345"))) 455 | } 456 | 457 | func Test_IsRFC3339(t *testing.T) { 458 | assert.True(t, gofn.Head(IsRFC3339("2020-12-09T16:09:53-00:00"))) 459 | assert.False(t, gofn.Head(IsRFC3339("2020-12-09 16:09:53+00:00"))) 460 | } 461 | 462 | func Test_IsRFC3339WithoutZone(t *testing.T) { 463 | assert.True(t, gofn.Head(IsRFC3339WithoutZone("2020-12-09T16:09:53"))) 464 | assert.False(t, gofn.Head(IsRFC3339WithoutZone("2020-12-09T16:09:53-00:00"))) 465 | } 466 | 467 | func Test_IsISO4217(t *testing.T) { 468 | assert.True(t, gofn.Head(IsISO4217("USD"))) 469 | assert.False(t, gofn.Head(IsISO4217("usd"))) 470 | } 471 | 472 | func Test_IsE164(t *testing.T) { 473 | assert.True(t, gofn.Head(IsE164("+442071838750"))) 474 | assert.False(t, gofn.Head(IsE164("abc123"))) 475 | } 476 | -------------------------------------------------------------------------------- /base/string/string.go: -------------------------------------------------------------------------------- 1 | package stringvalidation 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "unicode/utf8" 7 | 8 | "github.com/tiendc/go-validator/base" 9 | ) 10 | 11 | var ( 12 | runeLen = utf8.RuneCountInString 13 | ) 14 | 15 | // RuneLen checks rune length of a string must be in a range 16 | func RuneLen[T base.String](v T, min, max int) (bool, []base.ErrorParam) { 17 | l := runeLen(string(v)) 18 | return min <= l && l <= max, nil 19 | } 20 | 21 | // ByteLen checks byte length of a string must be in a range 22 | func ByteLen[T base.String](v T, min, max int) (bool, []base.ErrorParam) { 23 | l := len(v) 24 | return min <= l && l <= max, nil 25 | } 26 | 27 | // EQ checks a string must equal to a string 28 | func EQ[T base.String](v T, s T) (bool, []base.ErrorParam) { 29 | return v == s, nil 30 | } 31 | 32 | // In checks a string must be in a list 33 | func In[T base.String](v T, s ...T) (bool, []base.ErrorParam) { 34 | for i := range s { 35 | if v == s[i] { 36 | return true, nil 37 | } 38 | } 39 | return false, nil 40 | } 41 | 42 | // NotIn checks a string must be not in a list 43 | func NotIn[T base.String](v T, s ...T) (bool, []base.ErrorParam) { 44 | for i := range s { 45 | if v == s[i] { 46 | return false, nil 47 | } 48 | } 49 | return true, nil 50 | } 51 | 52 | // RuneMatch checks a string must be matching a regex on runes 53 | func RuneMatch[T base.String](v T, re *regexp.Regexp) (bool, []base.ErrorParam) { 54 | return re.MatchReader(strings.NewReader(string(v))), nil 55 | } 56 | 57 | // ByteMatch checks a string must be matching a regex on bytes 58 | func ByteMatch[T base.String](v T, re *regexp.Regexp) (bool, []base.ErrorParam) { 59 | return re.MatchString(string(v)), nil 60 | } 61 | -------------------------------------------------------------------------------- /base/string/string_test.go: -------------------------------------------------------------------------------- 1 | package stringvalidation 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/tiendc/gofn" 9 | ) 10 | 11 | func Test_RuneLen_ByteLen(t *testing.T) { 12 | assert.True(t, gofn.Head(RuneLen("chào", 1, 4))) 13 | assert.True(t, gofn.Head(RuneLen("abc", 1, 3))) 14 | assert.False(t, gofn.Head(RuneLen("chào", 1, 3))) 15 | assert.False(t, gofn.Head(RuneLen("abc", 1, 2))) 16 | 17 | assert.True(t, gofn.Head(ByteLen("abc", 1, 5))) 18 | assert.True(t, gofn.Head(ByteLen("chào", 1, 5))) 19 | assert.False(t, gofn.Head(ByteLen("abc", 1, 2))) 20 | assert.False(t, gofn.Head(ByteLen("chào", 1, 4))) 21 | } 22 | 23 | func Test_EQ(t *testing.T) { 24 | assert.True(t, gofn.Head(EQ("", ""))) 25 | assert.True(t, gofn.Head(EQ("abc", "abc"))) 26 | assert.True(t, gofn.Head(EQ("chào", "chào"))) 27 | assert.False(t, gofn.Head(EQ("abc", "aBc"))) 28 | assert.False(t, gofn.Head(EQ("abc", ""))) 29 | assert.False(t, gofn.Head(EQ("chào", "chao"))) 30 | } 31 | 32 | func Test_In(t *testing.T) { 33 | assert.True(t, gofn.Head(In("abc", "abc"))) 34 | assert.True(t, gofn.Head(In("abc", "", "abc", "123"))) 35 | assert.True(t, gofn.Head(In("", "aBc", ""))) 36 | assert.False(t, gofn.Head(In("abc", "aBc", "123", ""))) 37 | assert.False(t, gofn.Head(In("", "aBc", "123"))) 38 | } 39 | 40 | func Test_NotIn(t *testing.T) { 41 | assert.True(t, gofn.Head(NotIn("abc", "abC"))) 42 | assert.True(t, gofn.Head(NotIn("", "aBc", "123"))) 43 | assert.False(t, gofn.Head(NotIn("abc", "abc", "123", ""))) 44 | assert.False(t, gofn.Head(NotIn("", "aBc", "123", ""))) 45 | } 46 | 47 | func Test_Match(t *testing.T) { 48 | re := regexp.MustCompile("[0-9]+") 49 | assert.True(t, gofn.Head(RuneMatch("1234", re))) 50 | assert.False(t, gofn.Head(RuneMatch("abc", re))) 51 | assert.True(t, gofn.Head(ByteMatch("1234", re))) 52 | assert.False(t, gofn.Head(ByteMatch("abc", re))) 53 | } 54 | -------------------------------------------------------------------------------- /base/time/time.go: -------------------------------------------------------------------------------- 1 | package timevalidation 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tiendc/go-validator/base" 7 | ) 8 | 9 | // EQ checks a time must equal to a time value 10 | func EQ[T base.Time](v T, val time.Time) (bool, []base.ErrorParam) { 11 | return v.Compare(val) == 0, nil 12 | } 13 | 14 | // GT checks a time must be greater than a time value 15 | func GT[T base.Time](v T, min time.Time) (bool, []base.ErrorParam) { 16 | return v.Compare(min) > 0, nil 17 | } 18 | 19 | // GTE checks a time must be greater than or equal to a time value 20 | func GTE[T base.Time](v T, min time.Time) (bool, []base.ErrorParam) { 21 | return v.Compare(min) >= 0, nil 22 | } 23 | 24 | // LT checks a time must be less than a time value 25 | func LT[T base.Time](v T, min time.Time) (bool, []base.ErrorParam) { 26 | return v.Compare(min) < 0, nil 27 | } 28 | 29 | // LTE checks a time must be less than or equal to a time value 30 | func LTE[T base.Time](v T, min time.Time) (bool, []base.ErrorParam) { 31 | return v.Compare(min) <= 0, nil 32 | } 33 | 34 | // Valid checks a time must be non-zero value 35 | func Valid[T base.Time](v T) (bool, []base.ErrorParam) { 36 | return !v.IsZero(), nil 37 | } 38 | 39 | // Range checks a time must be in a range 40 | func Range[T base.Time](v T, min, max time.Time) (bool, []base.ErrorParam) { 41 | return v.Compare(min) >= 0 && v.Compare(max) <= 0, nil 42 | } 43 | 44 | // In checks a time must be in a list 45 | func In[T base.Time](v T, s ...time.Time) (bool, []base.ErrorParam) { 46 | for i := range s { 47 | if v.Compare(s[i]) == 0 { 48 | return true, nil 49 | } 50 | } 51 | return false, nil 52 | } 53 | 54 | // NotIn checks a time must be not in a list 55 | func NotIn[T base.Time](v T, s ...time.Time) (bool, []base.ErrorParam) { 56 | for i := range s { 57 | if v.Compare(s[i]) == 0 { 58 | return false, nil 59 | } 60 | } 61 | return true, nil 62 | } 63 | -------------------------------------------------------------------------------- /base/time/time_test.go: -------------------------------------------------------------------------------- 1 | package timevalidation 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/tiendc/gofn" 9 | ) 10 | 11 | func parse(s, layout string) time.Time { 12 | t, e := time.Parse(layout, s) 13 | if e != nil { 14 | panic(e) 15 | } 16 | return t 17 | } 18 | 19 | func Test_EQ(t *testing.T) { 20 | l1 := time.DateTime 21 | l2 := time.DateOnly 22 | assert.True(t, gofn.Head(EQ(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 23 | assert.True(t, gofn.Head(EQ(parse("2023-10-01", l2), parse("2023-10-01", l2)))) 24 | 25 | assert.False(t, gofn.Head(EQ(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)))) 26 | assert.False(t, gofn.Head(EQ(parse("2023-10-01", l2), parse("2023-10-02", l2)))) 27 | } 28 | 29 | func Test_GT_GTE(t *testing.T) { 30 | l1 := time.DateTime 31 | l2 := time.DateOnly 32 | assert.True(t, gofn.Head(GT(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:09", l1)))) 33 | assert.True(t, gofn.Head(GT(parse("2023-10-02 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 34 | assert.True(t, gofn.Head(GT(parse("2023-10-02", l2), parse("2023-10-01", l2)))) 35 | assert.True(t, gofn.Head(GTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 36 | assert.True(t, gofn.Head(GTE(parse("2023-10-01", l2), parse("2023-10-01", l2)))) 37 | 38 | assert.False(t, gofn.Head(GT(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 39 | assert.False(t, gofn.Head(GT(parse("2023-10-01", l2), parse("2023-10-01", l2)))) 40 | assert.False(t, gofn.Head(GTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)))) 41 | assert.False(t, gofn.Head(GTE(parse("2023-10-01", l2), parse("2023-10-02", l2)))) 42 | } 43 | 44 | func Test_LT_LTE(t *testing.T) { 45 | l1 := time.DateTime 46 | l2 := time.DateOnly 47 | assert.True(t, gofn.Head(LT(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)))) 48 | assert.True(t, gofn.Head(LT(parse("2023-10-01 10:10:10", l1), parse("2023-10-02 10:10:10", l1)))) 49 | assert.True(t, gofn.Head(LT(parse("2023-10-01", l2), parse("2023-10-02", l2)))) 50 | assert.True(t, gofn.Head(LTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 51 | assert.True(t, gofn.Head(LTE(parse("2023-10-01", l2), parse("2023-10-01", l2)))) 52 | 53 | assert.False(t, gofn.Head(LT(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 54 | assert.False(t, gofn.Head(LT(parse("2023-10-01", l2), parse("2023-10-01", l2)))) 55 | assert.False(t, gofn.Head(LTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:09", l1)))) 56 | assert.False(t, gofn.Head(LTE(parse("2023-10-02", l2), parse("2023-10-01", l2)))) 57 | } 58 | 59 | func Test_Valid(t *testing.T) { 60 | l1 := time.DateTime 61 | l2 := time.DateOnly 62 | assert.True(t, gofn.Head(Valid(parse("2023-10-01 10:10:10", l1)))) 63 | assert.True(t, gofn.Head(Valid(parse("2023-10-01", l2)))) 64 | assert.False(t, gofn.Head(Valid(time.Time{}))) 65 | } 66 | 67 | func Test_Range(t *testing.T) { 68 | l1 := time.DateTime 69 | l2 := time.DateOnly 70 | assert.True(t, gofn.Head(Range(parse("2023-10-01 10:10:10", l1), 71 | parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 72 | assert.True(t, gofn.Head(Range(parse("2023-10-01 10:10:10", l1), 73 | parse("2023-10-01 10:10:09", l1), parse("2023-10-01 10:10:11", l1)))) 74 | assert.True(t, gofn.Head(Range(parse("2023-10-02 10:10:10", l1), 75 | parse("2023-10-01 10:10:10", l1), parse("2023-10-03 10:10:10", l1)))) 76 | assert.True(t, gofn.Head(Range(parse("2023-10-01", l2), 77 | parse("2023-10-01", l2), parse("2023-10-01", l2)))) 78 | assert.True(t, gofn.Head(Range(parse("2023-10-02", l2), 79 | parse("2023-10-01", l2), parse("2023-10-03", l2)))) 80 | 81 | assert.False(t, gofn.Head(Range(parse("2023-10-01 10:10:10", l1), 82 | parse("2023-10-01 10:10:11", l1), parse("2023-10-01 10:10:12", l1)))) 83 | assert.False(t, gofn.Head(Range(parse("2023-10-01 10:10:10", l1), 84 | parse("2023-10-01 10:10:08", l1), parse("2023-10-01 10:10:09", l1)))) 85 | assert.False(t, gofn.Head(Range(parse("2023-10-01", l2), 86 | parse("2023-10-02", l2), parse("2023-10-03", l2)))) 87 | assert.False(t, gofn.Head(Range(parse("2023-10-03", l2), 88 | parse("2023-10-01", l2), parse("2023-10-02", l2)))) 89 | } 90 | 91 | func Test_In(t *testing.T) { 92 | l1 := time.DateTime 93 | l2 := time.DateOnly 94 | assert.True(t, gofn.Head(In(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 95 | assert.True(t, gofn.Head(In(parse("2023-10-01 10:10:10", l1), 96 | parse("2023-10-01 10:10:09", l1), parse("2023-10-01 10:10:10", l1)))) 97 | assert.True(t, gofn.Head(In(parse("2023-10-02 10:10:10", l1), 98 | parse("2023-10-02 10:10:10", l1), parse("2023-10-03 10:10:10", l1)))) 99 | assert.True(t, gofn.Head(In(parse("2023-10-01", l2), parse("2023-10-01", l2)))) 100 | assert.True(t, gofn.Head(In(parse("2023-10-02", l2), 101 | parse("2023-10-01", l2), parse("2023-10-02", l2)))) 102 | 103 | assert.False(t, gofn.Head(In(parse("2023-10-01 10:10:10", l1)))) 104 | assert.False(t, gofn.Head(In(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)))) 105 | assert.False(t, gofn.Head(In(parse("2023-10-01", l2), parse("2023-10-02", l2)))) 106 | assert.False(t, gofn.Head(In(parse("2023-10-01", l2), 107 | parse("2023-10-02", l2), parse("2023-10-03", l2)))) 108 | } 109 | 110 | func Test_NotIn(t *testing.T) { 111 | l1 := time.DateTime 112 | l2 := time.DateOnly 113 | assert.True(t, gofn.Head(NotIn(parse("2023-10-01 10:10:10", l1)))) 114 | assert.True(t, gofn.Head(NotIn(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)))) 115 | assert.True(t, gofn.Head(NotIn(parse("2023-10-01 10:10:10", l1), 116 | parse("2023-10-01 10:10:11", l1), parse("2023-10-01 10:10:12", l1)))) 117 | assert.True(t, gofn.Head(NotIn(parse("2023-10-01", l2), parse("2023-10-02", l2)))) 118 | assert.True(t, gofn.Head(NotIn(parse("2023-10-01", l2), 119 | parse("2023-10-02", l2), parse("2023-10-03", l2)))) 120 | 121 | assert.False(t, gofn.Head(NotIn(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)))) 122 | assert.False(t, gofn.Head(NotIn(parse("2023-10-01", l2), parse("2023-10-01", l2)))) 123 | assert.False(t, gofn.Head(NotIn(parse("2023-10-01", l2), 124 | parse("2023-10-02", l2), parse("2023-10-01", l2)))) 125 | } 126 | -------------------------------------------------------------------------------- /base/types.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tiendc/gofn" 7 | ) 8 | 9 | // Int interface of int types and int-derived types 10 | type Int gofn.IntExt 11 | 12 | // UInt interface of uint types and uint-derived types 13 | type UInt gofn.UIntExt 14 | 15 | // Float interface of float types and float-derived types 16 | type Float gofn.FloatExt 17 | 18 | // Number interface of combined type of Int, UInt, and Float 19 | type Number interface { 20 | Int | UInt | Float 21 | } 22 | 23 | // String interface of string type and string-derived types 24 | type String gofn.StringExt 25 | 26 | // Time interface of time type 27 | type Time interface { 28 | Compare(time.Time) int 29 | IsZero() bool 30 | } 31 | -------------------------------------------------------------------------------- /base/utils.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | // ToMap transforms a slice to a map with slice items as map keys 4 | func ToMap[T comparable, S ~[]T](s S) map[T]struct{} { 5 | result := make(map[T]struct{}, len(s)) 6 | for _, v := range s { 7 | result[v] = struct{}{} 8 | } 9 | return result 10 | } 11 | 12 | // IsIn returns -1 if every item of a slice is in another slice. 13 | // Returns index of the first item if it is not in the target slice. 14 | func IsIn[T comparable, S1 ~[]T, S2 ~[]T](s S1, list S2) int { 15 | if len(s) == 0 { 16 | return -1 17 | } 18 | if len(list) == 0 { 19 | return 0 20 | } 21 | m := ToMap(list) 22 | for i, v := range s { 23 | if _, ok := m[v]; !ok { 24 | return i 25 | } 26 | } 27 | return -1 28 | } 29 | 30 | // IsNotIn returns -1 if any item of a slice is not in another slice. 31 | // Returns index of the first item if it is in the target slice. 32 | func IsNotIn[T comparable, S1 ~[]T, S2 ~[]T](s S1, list S2) int { 33 | if len(s) == 0 || len(list) == 0 { 34 | return -1 35 | } 36 | m := ToMap(list) 37 | for i, v := range s { 38 | if _, ok := m[v]; ok { 39 | return i 40 | } 41 | } 42 | return -1 43 | } 44 | 45 | // IsUnique returns -1 if every item of a slice is unique. 46 | // Returns index of the first item if it is a duplication of another. 47 | func IsUnique[T comparable, S ~[]T](s S) int { 48 | length := len(s) 49 | if length <= 1 { 50 | return -1 51 | } 52 | seen := make(map[T]struct{}, length) 53 | for i := 0; i < length; i++ { 54 | v := s[i] 55 | if _, ok := seen[v]; ok { 56 | return i 57 | } 58 | seen[v] = struct{}{} 59 | } 60 | return -1 61 | } 62 | 63 | // IsUniqueBy returns -1 if every value returned by the key function is unique. 64 | // Returns index of the first item if it is a duplication of another. 65 | func IsUniqueBy[T any, U comparable, S ~[]T](s S, keyFn func(T) U) int { 66 | length := len(s) 67 | if length <= 1 { 68 | return -1 69 | } 70 | seen := make(map[U]struct{}, length) 71 | for i := 0; i < length; i++ { 72 | v := keyFn(s[i]) 73 | if _, ok := seen[v]; ok { 74 | return i 75 | } 76 | seen[v] = struct{}{} 77 | } 78 | return -1 79 | } 80 | -------------------------------------------------------------------------------- /error_formatter.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | "strings" 9 | "text/template" 10 | 11 | "github.com/hashicorp/go-multierror" 12 | "github.com/iancoleman/strcase" 13 | "github.com/tiendc/gofn" 14 | ) 15 | 16 | type ( 17 | // ErrorParamFormatter interface represents a formatter of error params 18 | ErrorParamFormatter interface { 19 | Format(k string, v any) string 20 | } 21 | 22 | errorParamFormatter struct { 23 | k string 24 | v any 25 | formatter ErrorParamFormatter 26 | } 27 | 28 | // FormatFunc type of format function which produces string from input value 29 | FormatFunc func(reflect.Value) string 30 | 31 | // TypedParamFormatter interface of param formatter which consists of a set of format functions 32 | // for common Go value types. 33 | TypedParamFormatter interface { 34 | ErrorParamFormatter 35 | 36 | // SetNumFormatFunc set format function for numbers 37 | SetNumFormatFunc(FormatFunc) 38 | // SetStrFormatFunc set format function for strings 39 | SetStrFormatFunc(FormatFunc) 40 | // SetBoolFormatFunc set format function for bools 41 | SetBoolFormatFunc(FormatFunc) 42 | // SetSliceFormatFunc set format function for slices 43 | SetSliceFormatFunc(FormatFunc) 44 | // SetMapFormatFunc set format function for maps 45 | SetMapFormatFunc(FormatFunc) 46 | // SetStructFormatFunc set format function for structs 47 | SetStructFormatFunc(FormatFunc) 48 | // SetPtrFormatFunc set format function for pointers 49 | SetPtrFormatFunc(FormatFunc) 50 | // SetCustomFormatFunc set custom format function 51 | SetCustomFormatFunc(FormatFunc) 52 | } 53 | 54 | // typedParamFormatter default implementation of TypedParamFormatter 55 | typedParamFormatter struct { 56 | numFormatFunc FormatFunc 57 | strFormatFunc FormatFunc 58 | boolFormatFunc FormatFunc 59 | sliceFormatFunc FormatFunc 60 | mapFormatFunc FormatFunc 61 | structFormatFunc FormatFunc 62 | ptrFormatFunc FormatFunc 63 | customFormatFunc FormatFunc 64 | } 65 | ) 66 | 67 | func (f *errorParamFormatter) String() string { 68 | if f.formatter != nil { 69 | return f.formatter.Format(f.k, f.v) 70 | } 71 | return fmt.Sprintf("%v", f.v) 72 | } 73 | 74 | func (f *typedParamFormatter) SetNumFormatFunc(fn FormatFunc) { 75 | f.numFormatFunc = fn 76 | } 77 | func (f *typedParamFormatter) SetStrFormatFunc(fn FormatFunc) { 78 | f.strFormatFunc = fn 79 | } 80 | func (f *typedParamFormatter) SetBoolFormatFunc(fn FormatFunc) { 81 | f.boolFormatFunc = fn 82 | } 83 | func (f *typedParamFormatter) SetSliceFormatFunc(fn FormatFunc) { 84 | f.sliceFormatFunc = fn 85 | } 86 | func (f *typedParamFormatter) SetMapFormatFunc(fn FormatFunc) { 87 | f.mapFormatFunc = fn 88 | } 89 | func (f *typedParamFormatter) SetStructFormatFunc(fn FormatFunc) { 90 | f.structFormatFunc = fn 91 | } 92 | func (f *typedParamFormatter) SetPtrFormatFunc(fn FormatFunc) { 93 | f.ptrFormatFunc = fn 94 | } 95 | func (f *typedParamFormatter) SetCustomFormatFunc(fn FormatFunc) { 96 | f.customFormatFunc = fn 97 | } 98 | 99 | func (f *typedParamFormatter) Format(_ string, v any) string { 100 | if f.numFormatFunc == nil && f.strFormatFunc == nil && f.boolFormatFunc == nil && 101 | f.sliceFormatFunc == nil && f.mapFormatFunc == nil && f.structFormatFunc == nil && 102 | f.ptrFormatFunc == nil && f.customFormatFunc == nil { 103 | return fmt.Sprintf("%v", v) 104 | } 105 | return f.format(reflect.ValueOf(v)) 106 | } 107 | 108 | func (f *typedParamFormatter) format(v reflect.Value) string { 109 | if !v.IsValid() { 110 | return f.customFormat(v) 111 | } 112 | // nolint: exhaustive 113 | switch v.Kind() { 114 | case reflect.String: 115 | return f.strFormat(v) 116 | case reflect.Int, reflect.Uint, reflect.Int64, reflect.Uint64, reflect.Float32, reflect.Float64, 117 | reflect.Int32, reflect.Uint32, reflect.Int16, reflect.Uint16, reflect.Int8, reflect.Uint8: 118 | return f.numFormat(v) 119 | case reflect.Bool: 120 | return f.boolFormat(v) 121 | case reflect.Slice, reflect.Array: 122 | return f.sliceFormat(v) 123 | case reflect.Map: 124 | return f.mapFormat(v) 125 | case reflect.Struct: 126 | return f.structFormat(v) 127 | case reflect.Pointer: 128 | return f.ptrFormat(v) 129 | default: 130 | return f.customFormat(v) 131 | } 132 | } 133 | 134 | func (f *typedParamFormatter) strFormat(v reflect.Value) string { 135 | if f.strFormatFunc == nil { 136 | return f.customFormat(v) 137 | } 138 | return f.strFormatFunc(v) 139 | } 140 | 141 | func (f *typedParamFormatter) numFormat(v reflect.Value) string { 142 | if f.numFormatFunc == nil { 143 | return f.customFormat(v) 144 | } 145 | return f.numFormatFunc(v) 146 | } 147 | 148 | func (f *typedParamFormatter) boolFormat(v reflect.Value) string { 149 | if f.boolFormatFunc == nil { 150 | return f.customFormat(v) 151 | } 152 | return f.boolFormatFunc(v) 153 | } 154 | 155 | func (f *typedParamFormatter) sliceFormat(v reflect.Value) string { 156 | if f.sliceFormatFunc == nil { 157 | return f.customFormat(v) 158 | } 159 | return f.sliceFormatFunc(v) 160 | } 161 | 162 | func (f *typedParamFormatter) mapFormat(v reflect.Value) string { 163 | if f.mapFormatFunc == nil { 164 | return f.customFormat(v) 165 | } 166 | return f.mapFormatFunc(v) 167 | } 168 | 169 | func (f *typedParamFormatter) structFormat(v reflect.Value) string { 170 | if f.structFormatFunc == nil { 171 | return f.customFormat(v) 172 | } 173 | return f.structFormatFunc(v) 174 | } 175 | 176 | func (f *typedParamFormatter) ptrFormat(v reflect.Value) string { 177 | if f.ptrFormatFunc == nil { 178 | return f.format(v.Elem()) 179 | } 180 | return f.ptrFormatFunc(v) 181 | } 182 | 183 | func (f *typedParamFormatter) customFormat(v reflect.Value) string { 184 | if f.customFormatFunc == nil { 185 | if !v.IsValid() { 186 | return "nil" 187 | } 188 | return fmt.Sprintf("%v", v.Interface()) 189 | } 190 | return f.customFormatFunc(v) 191 | } 192 | 193 | // NewTypedParamFormatter creates a new TypedParamFormatter with using default format functions 194 | func NewTypedParamFormatter() TypedParamFormatter { 195 | return &typedParamFormatter{} 196 | } 197 | 198 | // NewDecimalNumFormatFunc returns a FormatFunc which groups digits of decimal. 199 | // 200 | // For example: '12345' -> '12,345', '12345.6789' -> '12,345.6789' 201 | // To attach this formatter to Error object: 202 | // - err.TypedParamFormatter().SetNumFormatFunc(NewDecimalNumFormatFunc()) 203 | // - err.TypedParamFormatter().SetNumFormatFunc(NewDecimalNumFormatFunc("%.5f")) 204 | // 205 | // Deprecated: use NewDecimalFormatFunc instead 206 | func NewDecimalNumFormatFunc(floatFmt ...string) FormatFunc { 207 | return func(v reflect.Value) string { 208 | var s string 209 | // nolint: exhaustive 210 | switch v.Kind() { 211 | case reflect.Float64, reflect.Float32: 212 | fmtStr := "%f" 213 | if len(floatFmt) > 0 { 214 | fmtStr = floatFmt[len(floatFmt)-1] 215 | } 216 | s = fmt.Sprintf(fmtStr, v.Interface()) 217 | default: 218 | s = fmt.Sprintf("%v", v.Interface()) 219 | } 220 | return gofn.NumberFmtGroup(s, '.', ',') 221 | } 222 | } 223 | 224 | // NewDecimalFormatFunc returns a FormatFunc which can format and group digits of decimal or integer. 225 | // 226 | // For example: '12345' -> '12,345', '12345.6789' -> '12,345.6789' 227 | // To attach this formatter to Error object: 228 | // - err.TypedParamFormatter().SetNumFormatFunc(NewDecimalFormatFunc('.', ',', "%.2f")) 229 | func NewDecimalFormatFunc(fractionSep, groupSep byte, floatFmt string) FormatFunc { 230 | return func(v reflect.Value) string { 231 | var s string 232 | // nolint: exhaustive 233 | switch v.Kind() { 234 | case reflect.Float64, reflect.Float32: 235 | fmtStr := floatFmt 236 | if fmtStr == "" { 237 | fmtStr = "%f" 238 | } 239 | s = fmt.Sprintf(fmtStr, v.Interface()) 240 | default: 241 | s = fmt.Sprintf("%v", v.Interface()) 242 | } 243 | return gofn.NumberFmtGroup(s, fractionSep, groupSep) 244 | } 245 | } 246 | 247 | // NewSliceFormatFunc create a new func for formatting a slice. 248 | // Sample arguments: leftWrap "[", rightWrap "]", elemSep ", " 249 | func NewSliceFormatFunc( 250 | elemFormatFunc FormatFunc, 251 | leftWrap, rightWrap string, elemSep string, 252 | ) FormatFunc { 253 | return func(v reflect.Value) string { 254 | var sb strings.Builder 255 | sb.WriteString(leftWrap) 256 | for i := 0; i < v.Len(); i++ { 257 | if i != 0 { 258 | sb.WriteString(elemSep) 259 | } 260 | sb.WriteString(elemFormatFunc(v.Index(i))) 261 | } 262 | sb.WriteString(rightWrap) 263 | return sb.String() 264 | } 265 | } 266 | 267 | // NewMapFormatFunc create a new func for formatting a map. 268 | // Sample arguments: leftWrap "{", rightWrap "}", kvSep ":", elemSep ", " 269 | func NewMapFormatFunc( 270 | keyFormatFunc, valueFormatFunc FormatFunc, 271 | leftWrap, rightWrap string, kvSep, entrySep string, 272 | ) FormatFunc { 273 | return func(v reflect.Value) string { 274 | var sb strings.Builder 275 | sb.WriteString(leftWrap) 276 | iter := v.MapRange() 277 | isFirstItem := true 278 | for iter.Next() { 279 | if isFirstItem { 280 | isFirstItem = false 281 | } else { 282 | sb.WriteString(entrySep) 283 | } 284 | sb.WriteString(keyFormatFunc(iter.Key())) 285 | sb.WriteString(kvSep) 286 | sb.WriteString(valueFormatFunc(iter.Value())) 287 | } 288 | sb.WriteString(rightWrap) 289 | return sb.String() 290 | } 291 | } 292 | 293 | // NewJSONFormatFunc create a format func to format input as JSON output 294 | func NewJSONFormatFunc() FormatFunc { 295 | return func(v reflect.Value) string { 296 | s, err := json.Marshal(v.Interface()) 297 | if err != nil { 298 | panic(err) 299 | } 300 | return string(s) 301 | } 302 | } 303 | 304 | // errorBuildDetail builds detail string of error using the error template string. 305 | // In case error happens, this function still returns the result string before error happens 306 | func errorBuildDetail(e Error) (detail string, retErr error) { 307 | detail = e.Template() 308 | t, err := template.New("error").Parse(detail) 309 | if err != nil { 310 | retErr = multierror.Append(retErr, err) 311 | return 312 | } 313 | 314 | params, err := errorBuildParams(e, e.ParamFormatter()) 315 | if err != nil { 316 | retErr = multierror.Append(retErr, err) 317 | } 318 | 319 | buf := bytes.NewBuffer(make([]byte, 0, 100)) //nolint:mnd 320 | err = t.Execute(buf, params) 321 | if err != nil { 322 | retErr = multierror.Append(retErr, err) 323 | } else { 324 | detail = buf.String() 325 | } 326 | 327 | return 328 | } 329 | 330 | // errorBuildParams builds params of error with inner errors' params handled 331 | func errorBuildParams(e Error, formatter ErrorParamFormatter) (params ErrorParams, err error) { 332 | params = make(ErrorParams, 10) //nolint:mnd 333 | 334 | // If there are inner errors, collect all params of them 335 | for _, inErr := range e.UnwrapAsErrors() { 336 | prefix := strcase.ToCamel(inErr.Type()) 337 | if prefix != "" { 338 | prefix += "_" 339 | } 340 | pErr := singleErrorBuildParams(inErr, inErr.ParamFormatter(), prefix, params) 341 | if pErr != nil { 342 | err = multierror.Append(err, pErr) 343 | } 344 | } 345 | 346 | // Build params for the current error 347 | pErr := singleErrorBuildParams(e, formatter, "", params) 348 | if pErr != nil { 349 | err = multierror.Append(err, pErr) 350 | } 351 | 352 | return params, err 353 | } 354 | 355 | // singleErrorBuildParams build params for the specific error 356 | func singleErrorBuildParams(e Error, formatter ErrorParamFormatter, prefix string, params ErrorParams) (err error) { 357 | key := prefix + "Type" 358 | params[key] = &errorParamFormatter{k: key, v: e.Type(), formatter: formatter} 359 | key = prefix + "Value" 360 | params[key] = &errorParamFormatter{k: key, v: e.Value(), formatter: formatter} 361 | key = prefix + "ValueType" 362 | params[key] = &errorParamFormatter{k: key, v: e.ValueType(), formatter: formatter} 363 | field := e.Field() 364 | if field != nil { 365 | key = prefix + "Field" 366 | params[key] = &errorParamFormatter{k: key, v: field.Name, formatter: formatter} 367 | key = prefix + "FieldPath" 368 | params[key] = &errorParamFormatter{k: key, v: field.PathString(false, "."), formatter: formatter} 369 | } else { 370 | err = multierror.Append(err, ErrFieldMissing) 371 | key = prefix + "Field" 372 | params[key] = &errorParamFormatter{k: key, v: "", formatter: formatter} 373 | key = prefix + "FieldPath" 374 | params[key] = &errorParamFormatter{k: key, v: "", formatter: formatter} 375 | } 376 | for k, v := range e.Params() { 377 | key = prefix + k 378 | params[key] = &errorParamFormatter{k: key, v: v, formatter: formatter} 379 | } 380 | return err 381 | } 382 | -------------------------------------------------------------------------------- /error_formatter_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/tiendc/gofn" 10 | ) 11 | 12 | func Test_TypedParamFormatter(t *testing.T) { 13 | formatter := NewTypedParamFormatter() 14 | assert.Equal(t, "123", formatter.Format("", 123)) 15 | assert.Equal(t, "abc", formatter.Format("", "abc")) 16 | assert.Equal(t, "true", formatter.Format("", true)) 17 | assert.True(t, strings.HasPrefix(formatter.Format("", gofn.ToPtr("abc")), "0x")) 18 | assert.Equal(t, "[abc 123]", formatter.Format("", []any{"abc", 123})) 19 | assert.Equal(t, "map[1:abc 2:123]", formatter.Format("", map[int]any{1: "abc", 2: 123})) 20 | 21 | type S struct { 22 | I int 23 | U uint 24 | } 25 | assert.Equal(t, "{111 222}", formatter.Format("", S{I: 111, U: 222})) 26 | 27 | jsonFormatFunc := NewJSONFormatFunc() 28 | // Use custom format function only 29 | formatter.SetCustomFormatFunc(jsonFormatFunc) 30 | 31 | assert.Equal(t, "123", formatter.Format("", 123)) 32 | assert.Equal(t, `"abc"`, formatter.Format("", "abc")) 33 | assert.Equal(t, "true", formatter.Format("", true)) 34 | assert.Equal(t, `"abc"`, formatter.Format("", gofn.ToPtr("abc"))) 35 | assert.Equal(t, `["abc",123]`, formatter.Format("", []any{"abc", 123})) 36 | v := formatter.Format("", map[int]any{1: "abc", 2: 123}) 37 | assert.True(t, v == `{"1":"abc","2":123}` || v == `{"2":123,"1":"abc"}`) 38 | 39 | formatter.SetNumFormatFunc(jsonFormatFunc) 40 | formatter.SetStrFormatFunc(jsonFormatFunc) 41 | formatter.SetBoolFormatFunc(jsonFormatFunc) 42 | formatter.SetSliceFormatFunc(jsonFormatFunc) 43 | formatter.SetMapFormatFunc(jsonFormatFunc) 44 | formatter.SetStructFormatFunc(jsonFormatFunc) 45 | formatter.SetPtrFormatFunc(jsonFormatFunc) 46 | formatter.SetCustomFormatFunc(jsonFormatFunc) 47 | 48 | assert.Equal(t, "123", formatter.Format("", 123)) 49 | assert.Equal(t, `"abc"`, formatter.Format("", "abc")) 50 | assert.Equal(t, "true", formatter.Format("", true)) 51 | assert.Equal(t, `"abc"`, formatter.Format("", gofn.ToPtr("abc"))) 52 | assert.Equal(t, `["abc",123]`, formatter.Format("", []any{"abc", 123})) 53 | v = formatter.Format("", map[int]any{1: "abc", 2: 123}) 54 | assert.True(t, v == `{"1":"abc","2":123}` || v == `{"2":123,"1":"abc"}`) 55 | } 56 | 57 | func Test_NewJSONFormatFunc(t *testing.T) { 58 | fmtFunc := NewJSONFormatFunc() 59 | assert.Equal(t, `"abc"`, fmtFunc(reflect.ValueOf("abc"))) 60 | assert.Equal(t, "123", fmtFunc(reflect.ValueOf(123))) 61 | assert.Equal(t, `["abc",123]`, fmtFunc(reflect.ValueOf([]any{"abc", 123}))) 62 | assert.Equal(t, `{"1":"abc","2":123}`, fmtFunc(reflect.ValueOf(map[int]any{1: "abc", 2: 123}))) 63 | } 64 | 65 | func Test_NewSliceFormatFunc(t *testing.T) { 66 | itemFmtFunc := NewJSONFormatFunc() 67 | fmtFunc := NewSliceFormatFunc(itemFmtFunc, "[", "]", ",") 68 | assert.Equal(t, `["abc",123]`, fmtFunc(reflect.ValueOf([]any{"abc", 123}))) 69 | } 70 | 71 | func Test_NewMapFormatFunc(t *testing.T) { 72 | kvFmtFunc := NewJSONFormatFunc() 73 | fmtFunc := NewMapFormatFunc(kvFmtFunc, kvFmtFunc, "{", "}", ":", ",") 74 | v := fmtFunc(reflect.ValueOf(map[int]any{1: "abc", 2: 123})) 75 | assert.True(t, v == `{1:"abc",2:123}` || v == `{2:123,1:"abc"}`) 76 | } 77 | 78 | func Test_NewDecimalNumFormatFunc(t *testing.T) { 79 | fmtFunc := NewDecimalNumFormatFunc() 80 | assert.Equal(t, "12,345", fmtFunc(reflect.ValueOf(12345))) 81 | assert.Equal(t, "1,234,567.123457", fmtFunc(reflect.ValueOf(1234567.1234567))) 82 | fmtFunc = NewDecimalNumFormatFunc("%.5f") 83 | assert.Equal(t, "12,345", fmtFunc(reflect.ValueOf(12345))) 84 | assert.Equal(t, "1,234,567.12346", fmtFunc(reflect.ValueOf(1234567.1234567))) 85 | } 86 | 87 | func Test_NewDecimalFormatFunc(t *testing.T) { 88 | fmtFunc := NewDecimalFormatFunc('.', ',', "") 89 | assert.Equal(t, "12,345", fmtFunc(reflect.ValueOf(12345))) 90 | assert.Equal(t, "1,234,567.123457", fmtFunc(reflect.ValueOf(1234567.1234567))) 91 | fmtFunc = NewDecimalFormatFunc('.', ',', "%.5f") 92 | assert.Equal(t, "12,345", fmtFunc(reflect.ValueOf(12345))) 93 | assert.Equal(t, "1,234,567.12346", fmtFunc(reflect.ValueOf(1234567.1234567))) 94 | } 95 | 96 | func Test_errorBuildDetail(t *testing.T) { 97 | err := NewError(). 98 | SetCustomKey("customKey"). 99 | SetTemplate("'{{.Value}}': {{.Field}} is invalid"). 100 | SetParam("k", "v"). 101 | SetValue("value2"). 102 | SetField(NewField("field2", NewField("field1", nil))) 103 | 104 | detail, buildErr := errorBuildDetail(err) 105 | assert.Nil(t, buildErr) 106 | assert.Equal(t, "'value2': field2 is invalid", detail) 107 | } 108 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/iancoleman/strcase" 8 | "github.com/tiendc/gofn" 9 | ) 10 | 11 | // Errors can be returned from the lib 12 | var ( 13 | ErrTypeUnsupported = errors.New("type unsupported") 14 | ErrFieldMissing = errors.New("field missing") 15 | ) 16 | 17 | const ( 18 | rootField = "(root)" 19 | ) 20 | 21 | type ( 22 | // ErrorParams is a map of params specific to each error 23 | ErrorParams map[string]any 24 | 25 | // Field represents a validating field 26 | Field struct { 27 | Name string 28 | Parent *Field 29 | } 30 | 31 | // Error is the interface for all validation errors in this lib 32 | Error interface { 33 | // Type gets type of error 34 | Type() string 35 | // SetType sets type of error 36 | SetType(string) Error 37 | 38 | // Field gets validating field 39 | Field() *Field 40 | // SetField sets validating field 41 | SetField(*Field) Error 42 | 43 | // Value gets validating value 44 | Value() any 45 | // SetValue gets validating value 46 | SetValue(any) Error 47 | 48 | // ValueType gets type of validating value 49 | ValueType() string 50 | // SetValueType sets type of validating value 51 | SetValueType(string) Error 52 | 53 | // Template gets template used to generating error message 54 | Template() string 55 | // SetTemplate sets template of error 56 | SetTemplate(string) Error 57 | 58 | // Params gets params of error 59 | Params() ErrorParams 60 | // SetParam sets params of error 61 | SetParam(k string, v any) Error 62 | 63 | // ParamFormatter formatter is used to format the error params 64 | // By default it is TypedParamFormatter 65 | ParamFormatter() ErrorParamFormatter 66 | // TypedParamFormatter get TypedParamFormatter attached to the error 67 | // This will return nil when the attached formatter is not a TypedParamFormatter 68 | TypedParamFormatter() TypedParamFormatter 69 | // SetParamFormatter sets params formatter of error 70 | SetParamFormatter(ErrorParamFormatter) Error 71 | 72 | // CustomKey gets custom key of error 73 | CustomKey() any 74 | // SetCustomKey sets custom key of error 75 | SetCustomKey(any) Error 76 | 77 | // BuildDetail builds error detailed message 78 | BuildDetail() (string, error) 79 | // ParamsWithFormatter gets params with wrapping by the formatter of the error 80 | ParamsWithFormatter() ErrorParams 81 | 82 | // String implements fmt.Stringer interface 83 | // This function calls BuildDetail() without raising error 84 | // Should use BuildDetail() for more controls on error 85 | String() string 86 | // Error implement error interface 87 | // See String() string 88 | Error() string 89 | 90 | // Unwrap implements errors.Unwrap 91 | Unwrap() []error 92 | // UnwrapAsErrors unwraps the error as `Errors` type 93 | UnwrapAsErrors() Errors 94 | } 95 | 96 | // Errors slice type for `Error` objects 97 | Errors []Error 98 | 99 | // ErrorMod function used to modify an `Error` object 100 | ErrorMod func(Error) 101 | 102 | // errorImpl implementation of Error type 103 | // nolint: errname 104 | errorImpl struct { 105 | errorType string 106 | field *Field 107 | value any 108 | valueType string 109 | template string 110 | params ErrorParams 111 | paramsFormatter ErrorParamFormatter 112 | customKey any 113 | 114 | wrappedErrors []Error 115 | } 116 | ) 117 | 118 | // NewField creates a new Field object 119 | func NewField(name string, parent *Field) *Field { 120 | return &Field{name, parent} 121 | } 122 | 123 | func (c *Field) Path(skipRoot bool) []string { 124 | path := []string{c.Name} 125 | t := c.Parent 126 | for t != nil { 127 | path = append(path, t.Name) 128 | t = t.Parent 129 | } 130 | if !skipRoot { 131 | path = append(path, rootField) 132 | } 133 | return gofn.Reverse(path) 134 | } 135 | 136 | func (c *Field) PathString(skipRoot bool, sep string) string { 137 | return strings.Join(c.Path(skipRoot), sep) 138 | } 139 | 140 | // NewError creates a new Error object 141 | func NewError() Error { 142 | return &errorImpl{ 143 | params: ErrorParams{}, 144 | paramsFormatter: NewTypedParamFormatter(), 145 | } 146 | } 147 | 148 | func (e *errorImpl) Type() string { 149 | return e.errorType 150 | } 151 | 152 | func (e *errorImpl) SetType(errorType string) Error { 153 | e.errorType = errorType 154 | return e 155 | } 156 | 157 | func (e *errorImpl) Field() *Field { 158 | return e.field 159 | } 160 | 161 | func (e *errorImpl) SetField(field *Field) Error { 162 | e.field = field 163 | return e 164 | } 165 | 166 | func (e *errorImpl) Value() any { 167 | return e.value 168 | } 169 | 170 | func (e *errorImpl) SetValue(value any) Error { 171 | e.value = value 172 | return e 173 | } 174 | 175 | func (e *errorImpl) ValueType() string { 176 | return e.valueType 177 | } 178 | 179 | func (e *errorImpl) SetValueType(valueType string) Error { 180 | e.valueType = valueType 181 | return e 182 | } 183 | 184 | func (e *errorImpl) Template() string { 185 | return e.template 186 | } 187 | 188 | func (e *errorImpl) SetTemplate(template string) Error { 189 | e.template = template 190 | return e 191 | } 192 | 193 | func (e *errorImpl) Params() ErrorParams { 194 | params := gofn.MapUpdate(make(ErrorParams, len(e.params)), e.params) 195 | // Collect all inner errors' params 196 | for _, inErr := range e.wrappedErrors { 197 | prefix := strcase.ToCamel(inErr.Type()) 198 | if prefix != "" { 199 | prefix += "_" 200 | } 201 | for k, v := range inErr.Params() { 202 | params[prefix+k] = v 203 | } 204 | } 205 | return params 206 | } 207 | 208 | func (e *errorImpl) SetParam(key string, val any) Error { 209 | if e.params == nil { 210 | e.params = ErrorParams{} 211 | } 212 | e.params[key] = val 213 | return e 214 | } 215 | 216 | func (e *errorImpl) ParamFormatter() ErrorParamFormatter { 217 | return e.paramsFormatter 218 | } 219 | 220 | func (e *errorImpl) TypedParamFormatter() TypedParamFormatter { 221 | if e.paramsFormatter == nil { 222 | return nil 223 | } 224 | typedFmt, _ := e.paramsFormatter.(TypedParamFormatter) 225 | return typedFmt 226 | } 227 | 228 | func (e *errorImpl) SetParamFormatter(formatter ErrorParamFormatter) Error { 229 | e.paramsFormatter = formatter 230 | return e 231 | } 232 | 233 | func (e *errorImpl) CustomKey() any { 234 | return e.customKey 235 | } 236 | 237 | func (e *errorImpl) SetCustomKey(key any) Error { 238 | e.customKey = key 239 | return e 240 | } 241 | 242 | func (e *errorImpl) BuildDetail() (string, error) { 243 | return errorBuildDetail(e) 244 | } 245 | 246 | func (e *errorImpl) ParamsWithFormatter() ErrorParams { 247 | params, _ := errorBuildParams(e, e.paramsFormatter) 248 | return params 249 | } 250 | 251 | func (e *errorImpl) String() string { 252 | str, _ := errorBuildDetail(e) 253 | return str 254 | } 255 | 256 | func (e *errorImpl) Error() string { 257 | return e.String() 258 | } 259 | 260 | func (e *errorImpl) Unwrap() []error { 261 | errs := make([]error, 0, len(e.wrappedErrors)) 262 | for _, err := range e.wrappedErrors { 263 | errs = append(errs, err) 264 | } 265 | return errs 266 | } 267 | 268 | func (e *errorImpl) UnwrapAsErrors() Errors { 269 | return e.wrappedErrors 270 | } 271 | 272 | func (e Errors) Error() string { 273 | if len(e) == 0 { 274 | return "" 275 | } 276 | var sb strings.Builder 277 | for i, err := range e { 278 | if i > 0 { 279 | sb.WriteString("\n") 280 | } 281 | sb.WriteString(err.Error()) 282 | } 283 | return sb.String() 284 | } 285 | 286 | // SetField returns a ErrorMod function to set field of error 287 | func SetField(name string, parent *Field) ErrorMod { 288 | return func(err Error) { 289 | _ = err.SetField(NewField(name, parent)) 290 | } 291 | } 292 | 293 | // SetCustomKey returns a ErrorMod function to set custom key of error 294 | func SetCustomKey(key any) ErrorMod { 295 | return func(err Error) { 296 | _ = err.SetCustomKey(key) 297 | } 298 | } 299 | 300 | // SetTemplate returns a ErrorMod function to set template of error 301 | func SetTemplate(template string) ErrorMod { 302 | return func(err Error) { 303 | _ = err.SetTemplate(template) 304 | } 305 | } 306 | 307 | // SetParam returns a ErrorMod function to set a param of error 308 | func SetParam(key string, val any) ErrorMod { 309 | return func(err Error) { 310 | _ = err.SetParam(key, val) 311 | } 312 | } 313 | 314 | // SetParamFormatter returns a ErrorMod function to set params formatter of error 315 | func SetParamFormatter(formatter ErrorParamFormatter) ErrorMod { 316 | return func(err Error) { 317 | _ = err.SetParamFormatter(formatter) 318 | } 319 | } 320 | 321 | // SetNumParamFormatter returns a ErrorMod function to set format function for numbers 322 | func SetNumParamFormatter(formatFunc FormatFunc) ErrorMod { 323 | return func(err Error) { 324 | getTypedParamFormatterOrPanic(err).SetNumFormatFunc(formatFunc) 325 | } 326 | } 327 | 328 | // SetStrParamFormatter returns a ErrorMod function to set format function for strings 329 | func SetStrParamFormatter(formatFunc FormatFunc) ErrorMod { 330 | return func(err Error) { 331 | getTypedParamFormatterOrPanic(err).SetStrFormatFunc(formatFunc) 332 | } 333 | } 334 | 335 | // SetBoolParamFormatter returns a ErrorMod function to set format function for bools 336 | func SetBoolParamFormatter(formatFunc FormatFunc) ErrorMod { 337 | return func(err Error) { 338 | getTypedParamFormatterOrPanic(err).SetBoolFormatFunc(formatFunc) 339 | } 340 | } 341 | 342 | // SetSliceParamFormatter returns a ErrorMod function to set format function for slices 343 | func SetSliceParamFormatter(formatFunc FormatFunc) ErrorMod { 344 | return func(err Error) { 345 | getTypedParamFormatterOrPanic(err).SetSliceFormatFunc(formatFunc) 346 | } 347 | } 348 | 349 | // SetMapParamFormatter returns a ErrorMod function to set format function for maps 350 | func SetMapParamFormatter(formatFunc FormatFunc) ErrorMod { 351 | return func(err Error) { 352 | getTypedParamFormatterOrPanic(err).SetMapFormatFunc(formatFunc) 353 | } 354 | } 355 | 356 | // SetStructParamFormatter returns a ErrorMod function to set format function for structs 357 | func SetStructParamFormatter(formatFunc FormatFunc) ErrorMod { 358 | return func(err Error) { 359 | getTypedParamFormatterOrPanic(err).SetStructFormatFunc(formatFunc) 360 | } 361 | } 362 | 363 | // SetPtrParamFormatter returns a ErrorMod function to set format function for pointers 364 | func SetPtrParamFormatter(formatFunc FormatFunc) ErrorMod { 365 | return func(err Error) { 366 | getTypedParamFormatterOrPanic(err).SetPtrFormatFunc(formatFunc) 367 | } 368 | } 369 | 370 | // SetCustomParamFormatter returns a ErrorMod function to set custom format function 371 | func SetCustomParamFormatter(formatFunc FormatFunc) ErrorMod { 372 | return func(err Error) { 373 | getTypedParamFormatterOrPanic(err).SetCustomFormatFunc(formatFunc) 374 | } 375 | } 376 | 377 | // getTypedParamFormatterOrPanic returns the TypedParamFormatter associated with the error or panic if unset 378 | func getTypedParamFormatterOrPanic(err Error) TypedParamFormatter { 379 | formatter := err.TypedParamFormatter() 380 | if formatter == nil { 381 | panic("error does not have a TypedParamFormatter attached") 382 | } 383 | return formatter 384 | } 385 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/tiendc/gofn" 9 | ) 10 | 11 | func Test_ErrorMod(t *testing.T) { 12 | err := NewError().SetCustomKey("abc").SetTemplate("template").SetParam("k", "v") 13 | 14 | SetCustomKey("xyz")(err) 15 | assert.Equal(t, "xyz", err.CustomKey()) 16 | 17 | SetTemplate("template {{.Xyz}}")(err) 18 | assert.Equal(t, "template {{.Xyz}}", err.Template()) 19 | 20 | SetParam("k", "vvv")(err) 21 | assert.Equal(t, "vvv", err.Params()["k"]) 22 | 23 | SetField("fieldX", nil)(err) 24 | assert.Equal(t, "fieldX", err.Field().Name) 25 | assert.Nil(t, err.Field().Parent) 26 | 27 | SetParamFormatter(nil)(err) 28 | assert.Nil(t, err.ParamFormatter()) 29 | assert.Nil(t, err.TypedParamFormatter()) 30 | SetParamFormatter(NewTypedParamFormatter())(err) 31 | assert.NotNil(t, err.ParamFormatter()) 32 | assert.NotNil(t, err.TypedParamFormatter()) 33 | 34 | SetNumParamFormatter(func(reflect.Value) string { return "num" })(err) 35 | SetStrParamFormatter(func(reflect.Value) string { return "str" })(err) 36 | SetBoolParamFormatter(func(reflect.Value) string { return "bool" })(err) 37 | SetSliceParamFormatter(func(reflect.Value) string { return "slice" })(err) 38 | SetMapParamFormatter(func(reflect.Value) string { return "map" })(err) 39 | SetStructParamFormatter(func(reflect.Value) string { return "struct" })(err) 40 | SetPtrParamFormatter(func(reflect.Value) string { return "ptr" })(err) 41 | SetCustomParamFormatter(func(reflect.Value) string { return "custom" })(err) 42 | assert.Equal(t, "num", err.TypedParamFormatter().Format("k", 123)) 43 | assert.Equal(t, "str", err.TypedParamFormatter().Format("k", "123")) 44 | assert.Equal(t, "bool", err.TypedParamFormatter().Format("k", true)) 45 | assert.Equal(t, "slice", err.TypedParamFormatter().Format("k", []int{123})) 46 | assert.Equal(t, "map", err.TypedParamFormatter().Format("k", map[string]any{})) 47 | assert.Equal(t, "struct", err.TypedParamFormatter().Format("k", struct{}{})) 48 | assert.Equal(t, "ptr", err.TypedParamFormatter().Format("k", gofn.ToPtr(123))) 49 | assert.Equal(t, "custom", err.TypedParamFormatter().Format("k", func() {})) 50 | 51 | defer func() { 52 | e := recover() 53 | assert.Equal(t, "error does not have a TypedParamFormatter attached", e) 54 | }() 55 | SetParamFormatter(nil)(err) 56 | SetNumParamFormatter(func(reflect.Value) string { return "num" })(err) 57 | } 58 | 59 | func Test_Field_Path(t *testing.T) { 60 | field1 := NewField("field1", nil) 61 | field2 := NewField("field2", field1) 62 | field3 := NewField("field3", field2) 63 | 64 | assert.Equal(t, "(root).field1.field2.field3", field3.PathString(false, ".")) 65 | assert.Equal(t, "field1.field2.field3", field3.PathString(true, ".")) 66 | 67 | assert.Equal(t, "(root)/field1/field2", field2.PathString(false, "/")) 68 | assert.Equal(t, "field1/field2", field2.PathString(true, "/")) 69 | 70 | assert.Equal(t, "(root).field1", field1.PathString(false, ".")) 71 | assert.Equal(t, "field1", field1.PathString(true, ".")) 72 | } 73 | 74 | func Test_Error_Impl(t *testing.T) { 75 | err := NewError() 76 | 77 | _ = err.SetType("type") 78 | _ = err.SetField(NewField("field", nil)) 79 | _ = err.SetValue("value") 80 | _ = err.SetValueType("value_type") 81 | _ = err.SetTemplate("template") 82 | _ = err.SetParam("k", "v") 83 | _ = err.SetCustomKey("custom_key") 84 | assert.Equal(t, "type", err.Type()) 85 | assert.Equal(t, "field", err.Field().Name) 86 | assert.Equal(t, "value", err.Value()) 87 | assert.Equal(t, "value_type", err.ValueType()) 88 | assert.Equal(t, "template", err.Template()) 89 | assert.Equal(t, "v", err.Params()["k"]) 90 | assert.Equal(t, "custom_key", err.CustomKey()) 91 | 92 | detail, e := err.BuildDetail() 93 | assert.Nil(t, e) 94 | assert.Equal(t, "template", detail) 95 | assert.Equal(t, "template", err.Error()) 96 | assert.Equal(t, "template", err.String()) 97 | assert.Equal(t, []error{}, err.Unwrap()) 98 | var errs Errors 99 | assert.Equal(t, errs, err.UnwrapAsErrors()) 100 | } 101 | 102 | func Test_Errors(t *testing.T) { 103 | errs := Errors{ 104 | NewError().SetTemplate("template_1"), 105 | NewError().SetTemplate("template_2"), 106 | } 107 | assert.Equal(t, "template_1\ntemplate_2", errs.Error()) 108 | } 109 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tiendc/go-validator 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 7 | github.com/hashicorp/go-multierror v1.1.1 8 | github.com/iancoleman/strcase v0.3.0 9 | github.com/stretchr/testify v1.10.0 10 | github.com/tiendc/gofn v1.14.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/hashicorp/errwrap v1.1.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/tiendc/go-rflutil v0.0.0-20240919184510-8a396d31868e // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 2 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 6 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 7 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 8 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 9 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 10 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= 11 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 15 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 16 | github.com/tiendc/go-rflutil v0.0.0-20240919184510-8a396d31868e h1:fEy3KtoperpwQw/L+6VX9N2qsysyhA+QDPiQdfu5LYA= 17 | github.com/tiendc/go-rflutil v0.0.0-20240919184510-8a396d31868e/go.mod h1:2nPnVtlbM4w4GOWSmFjKFKl+mhDT7hWgwky4qpRFugo= 18 | github.com/tiendc/gofn v1.14.0 h1:2djOJ5TUKCBlINiF8Nnh85dDhDNfu5TTFar6jyI0/s0= 19 | github.com/tiendc/gofn v1.14.0/go.mod h1:SfEJjjn7b4WMW2PRoxKuBXGDCWuxnD8DfxJccBlx+4o= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 22 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 23 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 24 | -------------------------------------------------------------------------------- /internal/templates/en/templates.go: -------------------------------------------------------------------------------- 1 | package en 2 | 3 | var ( 4 | FmtTemplates = map[string]string{ 5 | // General 6 | "group": `{{.Field}} must satisfy all specified validations`, 7 | "one_of": `{{.Field}} must satisfy at least one of the specified validations`, 8 | "exact_one_of": `{{.Field}} must satisfy exact one of the specified validations`, 9 | "not_of": `{{.Field}} must not satisfy any of the specified validations`, 10 | "if": `{{.Field}} must satisfy the specified condition`, // deprecated: use `must` 11 | "must": `{{.Field}} must satisfy the specified condition`, 12 | "nil": `{{.Field}} must be nil`, 13 | "not_nil": `{{.Field}} must be not nil`, 14 | "required": `{{.Field}} is required`, 15 | 16 | // Number 17 | "number_eq": `{{.Field}} must be equal to {{.TargetValue}}`, 18 | "number_gt": `{{.Field}} must be greater than {{.Min}}`, 19 | "number_gte": `{{.Field}} must be greater than or equal to {{.Min}}`, 20 | "number_lt": `{{.Field}} must be less than {{.Max}}`, 21 | "number_lte": `{{.Field}} must be less than or equal to {{.Max}}`, 22 | "number_range": `{{.Field}} must be in range {{.Min}} to {{.Max}}`, 23 | "number_in": `{{.Field}} must be one of {{.TargetValue}}`, 24 | "number_not_in": `{{.Field}} must not be one of {{.TargetValue}}`, 25 | "number_divisible_by": `{{.Field}} must be divisible by {{.TargetValue}}`, 26 | "number_js_safe_int": `{{.Field}} must be in Javascript safe int range`, 27 | 28 | // String 29 | "string_eq": `{{.Field}} must be equal to {{.TargetValue}}`, 30 | "string_len": `{{.Field}} length must be in range {{.Min}} to {{.Max}}`, 31 | "string_byte_len": `{{.Field}} byte-length must be in range {{.Min}} to {{.Max}}`, 32 | "string_in": `{{.Field}} must be one of {{.TargetValue}}`, 33 | "string_not_in": `{{.Field}} must not be one of {{.TargetValue}}`, 34 | "string_match": `{{.Field}} must match the unicode pattern {{.TargetValue}}`, 35 | "string_byte_match": `{{.Field}} must match the pattern {{.TargetValue}}`, 36 | 37 | // String "is" / "has" 38 | "string_is_email": `{{.Field}} must be a valid email`, 39 | "string_is_existing_email": `{{.Field}} must be a valid existing email`, 40 | "string_is_url": `{{.Field}} must be a valid URL`, 41 | "string_is_request_url": `{{.Field}} must be a valid request URL`, 42 | "string_is_request_uri": `{{.Field}} must be a valid request URI`, 43 | "string_is_alpha": `{{.Field}} must contain English letters only (a-zA-Z)`, 44 | "string_is_alpha_numeric": `{{.Field}} must contain English letters and digits only (a-zA-Z0-9)`, 45 | "string_is_utf_letter": `{{.Field}} must contain unicode letters only`, 46 | "string_is_utf_letter_numeric": `{{.Field}} must contain unicode letters and numbers only`, 47 | "string_is_numeric": `{{.Field}} must contain English number letters only (0-9)`, 48 | "string_is_utf_numeric": `{{.Field}} must contain unicode number letters only`, 49 | "string_is_utf_digit": `{{.Field}} must contain unicode radix-10 decimal digits only`, 50 | "string_is_hexadecimal": `{{.Field}} must be a valid hexadecimal number`, 51 | "string_is_hexcolor": `{{.Field}} must be a valid Hex color code`, 52 | "string_is_rgbcolor": `{{.Field}} must be a valid RGB color in form of rgb(R,G,B)`, 53 | "string_is_lower_case": `{{.Field}} must contain lower case letters only`, 54 | "string_is_upper_case": `{{.Field}} must contain upper case letters only`, 55 | "string_has_lower_case": `{{.Field}} must contain lower case letters`, 56 | "string_has_upper_case": `{{.Field}} must contain upper case letters`, 57 | "string_is_int": `{{.Field}} must be a valid integer number`, 58 | "string_is_float": `{{.Field}} must be a valid floating number`, 59 | "string_has_whitespace_only": `{{.Field}} must contain whitespace only`, 60 | "string_has_whitespace": `{{.Field}} must contain whitespace`, 61 | "string_is_uuid_v3": `{{.Field}} must be a valid UUID v3`, 62 | "string_is_uuid_v4": `{{.Field}} must be a valid UUID v4`, 63 | "string_is_uuid_v5": `{{.Field}} must be a valid UUID v5`, 64 | "string_is_uuid": `{{.Field}} must be a valid UUID`, 65 | "string_is_ulid": `{{.Field}} must be a valid ULID`, 66 | "string_is_credit_card": `{{.Field}} must be a valid credit card number`, 67 | "string_is_isbn10": `{{.Field}} must be a valid ISBN v10`, 68 | "string_is_isbn13": `{{.Field}} must be a valid ISBN v13`, 69 | "string_is_isbn": `{{.Field}} must be a valid ISBN (either ISBN v10 or ISBN v13)`, 70 | "string_is_json": `{{.Field}} must be in valid JSON format`, 71 | "string_is_multibyte": `{{.Field}} must contain multibyte characters`, 72 | "string_is_ascii": `{{.Field}} must contain only ASCII characters`, 73 | "string_is_printable_ascii": `{{.Field}} must contain only printable ASCII characters`, 74 | "string_is_full_width": `{{.Field}} must contain full-width characters`, 75 | "string_is_half_width": `{{.Field}} must contain half-width characters`, 76 | "string_is_variable_width": `{{.Field}} must contain both full-width and half-width characters`, 77 | "string_is_base64": `{{.Field}} must be a valid Base64 encoded string`, 78 | "string_is_file_path": `{{.Field}} must be a valid file path`, 79 | "string_is_win_file_path": `{{.Field}} must be a valid Windows file path`, 80 | "string_is_unix_file_path": `{{.Field}} must be a valid Unix file path`, 81 | "string_is_data_uri": `{{.Field}} must be a valid base64-encoded data URI`, 82 | "string_is_magnet_uri": `{{.Field}} must be a valid magnet URI`, 83 | "string_is_iso3166_alpha2": `{{.Field}} must be a valid ISO3166 Alpha2 country code`, 84 | "string_is_iso3166_alpha3": `{{.Field}} must be a valid ISO3166 Alpha3 country code`, 85 | "string_is_iso639_alpha2": `{{.Field}} must be a valid ISO639 Alpha2 language code`, 86 | "string_is_iso639_alpha3b": `{{.Field}} must be a valid ISO639 Alpha3b language code`, 87 | "string_is_dns_name": `{{.Field}} must be a valid DNS name`, 88 | "string_is_sha3_224": `{{.Field}} must be a valid SHA3-224`, 89 | "string_is_sha3_256": `{{.Field}} must be a valid SHA3-256`, 90 | "string_is_sha3_384": `{{.Field}} must be a valid SHA3-384`, 91 | "string_is_sha3_512": `{{.Field}} must be a valid SHA3-512`, 92 | "string_is_sha512": `{{.Field}} must be a valid SHA512`, 93 | "string_is_sha384": `{{.Field}} must be a valid SHA384`, 94 | "string_is_sha256": `{{.Field}} must be a valid SHA256`, 95 | "string_is_sha1": `{{.Field}} must be a valid SHA1`, 96 | "string_is_tiger192": `{{.Field}} must be a valid Tiger192`, 97 | "string_is_tiger160": `{{.Field}} must be a valid Tiger160`, 98 | "string_is_tiger128": `{{.Field}} must be a valid Tiger128`, 99 | "string_is_ripemd160": `{{.Field}} must be a valid RipeMD160`, 100 | "string_is_ripemd128": `{{.Field}} must be a valid RipeMD128`, 101 | "string_is_crc32": `{{.Field}} must be a valid CRC32`, 102 | "string_is_crc32b": `{{.Field}} must be a valid CRC32b`, 103 | "string_is_md5": `{{.Field}} must be a valid MD5`, 104 | "string_is_md4": `{{.Field}} must be a valid MD4`, 105 | "string_is_dial_string": `{{.Field}} must be a valid dial string`, 106 | "string_is_ip": `{{.Field}} must be a valid IP address (either IPv4 or Ipv6 address)`, 107 | "string_is_port": `{{.Field}} must be a valid host Port`, 108 | "string_is_ipv4": `{{.Field}} must be a valid IPv4 address`, 109 | "string_is_ipv6": `{{.Field}} must be a valid IPv6 address`, 110 | "string_is_cidr": `{{.Field}} must be a valid CIDR`, 111 | "string_is_mac": `{{.Field}} must be a valid MAC address`, 112 | "string_is_host": `{{.Field}} must be a valid host IP or domain`, 113 | "string_is_mongo_id": `{{.Field}} must be a valid Mongo ID`, 114 | "string_is_latitude": `{{.Field}} must be a valid latitude`, 115 | "string_is_longitude": `{{.Field}} must be a valid longitude`, 116 | "string_is_imei": `{{.Field}} must be a valid IMEI number`, 117 | "string_is_imsi": `{{.Field}} must be a valid IMSI code`, 118 | "string_is_rsa_public_key": `{{.Field}} must be a valid RSA public key`, 119 | "string_is_regex": `{{.Field}} must be a valid Regex`, 120 | "string_is_ssn": `{{.Field}} must be a valid Social Security number`, 121 | "string_is_semver": `{{.Field}} must be a valid Semver`, 122 | "string_is_time": `{{.Field}} must be a valid time`, 123 | "string_is_unix_time": `{{.Field}} must be a valid Unix time`, 124 | "string_is_rfc3339": `{{.Field}} must be a valid RFC3339 date time`, 125 | "string_is_rfc3339_without_zone": `{{.Field}} must be a valid RFC3339 date time without zone`, 126 | "string_is_iso4217": `{{.Field}} must be a valid ISO4217 currency code`, 127 | "string_is_e164": `{{.Field}} must be a valid E164 phone number`, 128 | 129 | // Slice 130 | "slice_len": `{{.Field}}: number of array items must be in range {{.Min}} to {{.Max}}`, 131 | "slice_has_elem": `{{.Field}}: array must contain values {{.TargetValue}}`, 132 | "slice_has_elem_by": `{{.Field}}: array must contain values satisfying the specified function`, 133 | "slice_not_have_elem": `{{.Field}}: array must not contain values {{.TargetValue}}`, 134 | "slice_not_have_elem_by": `{{.Field}}: array must not contain values satisfying the specified function`, 135 | "slice_elem_in": `{{.Field}}: array items must be one of {{.TargetValue}}`, 136 | "slice_elem_not_in": `{{.Field}}: array items must not be one of {{.TargetValue}}`, 137 | "slice_elem_range": `{{.Field}}: array items must be in range {{.Min}} to {{.Max}}`, 138 | "slice_unique": `{{.Field}}: array items must be unique`, 139 | "slice_sorted": `{{.Field}}: array items must be sorted in ascending order`, 140 | "slice_sorted_desc": `{{.Field}}: array items must be sorted in descending order`, 141 | 142 | // Map 143 | "map_len": `{{.Field}}: number of items must be in range {{.Min}} to {{.Max}}`, 144 | "map_has_key": `{{.Field}}: map must contain keys {{.TargetValue}}`, 145 | "map_not_have_key": `{{.Field}}: map must not contain keys {{.TargetValue}}`, 146 | "map_key_in": `{{.Field}}: map keys must be one of {{.TargetValue}}`, 147 | "map_key_not_in": `{{.Field}}: map keys must not be one of {{.TargetValue}}`, 148 | "map_key_range": `{{.Field}}: map keys must be in range {{.Min}} to {{.Max}}`, 149 | "map_value_in": `{{.Field}}: map values must be one of {{.TargetValue}}`, 150 | "map_value_not_in": `{{.Field}}: map values must not be one of {{.TargetValue}}`, 151 | "map_value_range": `{{.Field}}: map values must be in range {{.Min}} to {{.Max}}`, 152 | "map_value_unique": `{{.Field}}: map values must be unique`, 153 | 154 | // Time 155 | "time_eq": `{{.Field}} must be equal to {{.TargetValue}}`, 156 | "time_gt": `{{.Field}} must be greater than {{.Min}}`, 157 | "time_gte": `{{.Field}} must be greater than or equal to {{.Min}}`, 158 | "time_lt": `{{.Field}} must be less than {{.Max}}`, 159 | "time_lte": `{{.Field}} must be less than or equal to {{.Max}}`, 160 | "time_valid": `{{.Field}} must be a valid time`, 161 | "time_range": `{{.Field}} must be in range {{.Min}} to {{.Max}}`, 162 | "time_in": `{{.Field}} must be one of {{.TargetValue}}`, 163 | "time_not_in": `{{.Field}} must not be one of {{.TargetValue}}`, 164 | } 165 | ) 166 | -------------------------------------------------------------------------------- /locale.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | enTemplates "github.com/tiendc/go-validator/internal/templates/en" 5 | ) 6 | 7 | const ( 8 | langEn = "en" 9 | ) 10 | 11 | // TemplateProvider interface for providing template for generating error messages 12 | type TemplateProvider interface { 13 | Get(Error) string 14 | } 15 | 16 | type templateProvider struct { 17 | fmtTemplates map[string]string 18 | } 19 | 20 | func (t *templateProvider) Get(e Error) string { 21 | key := e.ValueType() 22 | if key != "" { 23 | key += "_" 24 | } 25 | key += e.Type() 26 | return t.fmtTemplates[key] 27 | } 28 | 29 | var ( 30 | defaultLang = langEn 31 | fmtTemplates = map[string]TemplateProvider{ 32 | langEn: &templateProvider{fmtTemplates: enTemplates.FmtTemplates}, 33 | } 34 | ) 35 | 36 | // DefaultLang default language used in the template 37 | func DefaultLang() string { 38 | return defaultLang 39 | } 40 | 41 | // SetDefaultLang set default language used in the template 42 | func SetDefaultLang(lang string) { 43 | defaultLang = lang 44 | } 45 | 46 | // GetTemplateProvider gets current template provider 47 | func GetTemplateProvider(lang string) TemplateProvider { 48 | return fmtTemplates[lang] 49 | } 50 | 51 | // SetTemplateProvider sets current template provider 52 | func SetTemplateProvider(lang string, provider TemplateProvider) { 53 | fmtTemplates[lang] = provider 54 | } 55 | 56 | func getFmtTemplate(e Error) string { 57 | provider := GetTemplateProvider(defaultLang) 58 | if provider == nil { 59 | provider = GetTemplateProvider(langEn) 60 | } 61 | return provider.Get(e) 62 | } 63 | -------------------------------------------------------------------------------- /locale_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_DefaultLang(t *testing.T) { 10 | assert.Equal(t, langEn, DefaultLang()) 11 | SetDefaultLang("vi") 12 | assert.Equal(t, "vi", DefaultLang()) 13 | } 14 | 15 | func Test_GetTemplateProvider(t *testing.T) { 16 | SetDefaultLang(langEn) 17 | assert.NotNil(t, GetTemplateProvider(langEn)) 18 | assert.Nil(t, GetTemplateProvider("vi")) 19 | } 20 | 21 | func Test_SetTemplateProvider(t *testing.T) { 22 | provider := &templateProvider{} 23 | SetTemplateProvider("vi", provider) 24 | assert.Equal(t, provider, GetTemplateProvider("vi")) 25 | } 26 | 27 | func Test_getFmtTemplate(t *testing.T) { 28 | err := NewError() 29 | _ = err.SetType("required") 30 | 31 | SetDefaultLang(langEn) 32 | assert.Equal(t, `{{.Field}} is required`, getFmtTemplate(err)) 33 | SetDefaultLang("xy") 34 | assert.Equal(t, `{{.Field}} is required`, getFmtTemplate(err)) 35 | } 36 | -------------------------------------------------------------------------------- /transform.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/tiendc/go-validator/base" 7 | ) 8 | 9 | // ToLower transforms characters of a string to lowercase 10 | // 11 | // Example: validation.StrIsEmail(validation.ToLower(&req.Email)) 12 | func ToLower[T base.String](v *T) *T { 13 | if v == nil { 14 | return v 15 | } 16 | vv := T(strings.ToLower(string(*v))) 17 | return &vv 18 | } 19 | 20 | // ToUpper transforms characters of a string to uppercase 21 | func ToUpper[T base.String](v *T) *T { 22 | if v == nil { 23 | return v 24 | } 25 | vv := T(strings.ToUpper(string(*v))) 26 | return &vv 27 | } 28 | 29 | // ToInt64 transforms a number to int64 value 30 | func ToInt64[T base.Number](v *T) *int64 { 31 | if v == nil { 32 | return nil 33 | } 34 | vv := int64(*v) 35 | return &vv 36 | } 37 | 38 | // ToUint64 transforms a number to uint64 value 39 | func ToUint64[T base.Number](v *T) *uint64 { 40 | if v == nil { 41 | return nil 42 | } 43 | vv := uint64(*v) 44 | return &vv 45 | } 46 | 47 | // ToFloat64 transforms a number to float64 value 48 | func ToFloat64[T base.Number](v *T) *float64 { 49 | if v == nil { 50 | return nil 51 | } 52 | vv := float64(*v) 53 | return &vv 54 | } 55 | -------------------------------------------------------------------------------- /transform_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | func Test_ToLower(t *testing.T) { 11 | assert.Nil(t, ToLower[string](nil)) 12 | v := ToLower(gofn.ToPtr("aBc")) 13 | assert.Equal(t, "abc", *v) 14 | } 15 | 16 | func Test_ToUpper(t *testing.T) { 17 | assert.Nil(t, ToUpper[string](nil)) 18 | v := ToUpper(gofn.ToPtr("aBc")) 19 | assert.Equal(t, "ABC", *v) 20 | } 21 | 22 | func Test_ToInt64(t *testing.T) { 23 | assert.Nil(t, ToInt64[int](nil)) 24 | v := ToInt64(gofn.ToPtr(123)) 25 | assert.Equal(t, int64(123), *v) 26 | } 27 | 28 | func Test_ToUint64(t *testing.T) { 29 | assert.Nil(t, ToUint64[int](nil)) 30 | v := ToUint64(gofn.ToPtr(123)) 31 | assert.Equal(t, uint64(123), *v) 32 | } 33 | 34 | func Test_ToFloat64(t *testing.T) { 35 | assert.Nil(t, ToFloat64[int](nil)) 36 | v := ToFloat64(gofn.ToPtr(123.123)) 37 | assert.Equal(t, float64(123.123), *v) 38 | } 39 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "github.com/tiendc/go-validator/base" 5 | ) 6 | 7 | // errorBuild builds Error object based on the given params 8 | func errorBuild(typ string, vTyp string, value any, wrapErrs Errors, params ...base.ErrorParam) Error { 9 | err := &errorImpl{ 10 | errorType: typ, 11 | valueType: vTyp, 12 | value: value, 13 | params: ErrorParams{}, 14 | paramsFormatter: NewTypedParamFormatter(), 15 | wrappedErrors: wrapErrs, 16 | } 17 | for _, p := range params { 18 | err.params[p.Key] = p.Value 19 | } 20 | err.template = getFmtTemplate(err) 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | -------------------------------------------------------------------------------- /validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "context" 4 | 5 | // Validate executes given validators to make result. 6 | func Validate(validators ...Validator) Errors { 7 | return execValidators(context.Background(), validators, false) 8 | } 9 | 10 | // ValidateWithCtx executes given validators with given context. 11 | func ValidateWithCtx(ctx context.Context, validators ...Validator) Errors { 12 | return execValidators(ctx, validators, false) 13 | } 14 | 15 | // Group groups the given validators into one. 16 | // In case there are errors, only one error will be returned. 17 | func Group(validators ...Validator) SingleValidator { 18 | return NewSingleValidator(func(ctx context.Context) Error { 19 | if len(validators) == 0 { 20 | return nil 21 | } 22 | errs := execValidators(ctx, validators, false) 23 | if len(errs) == 0 { 24 | return nil 25 | } 26 | return errorBuild("group", "", nil, errs) 27 | }) 28 | } 29 | 30 | // OneOf checks if the target value satisfies one of the given validators. 31 | // When a validator passes, the remaining ones will be skipped. 32 | func OneOf(validators ...Validator) SingleValidator { 33 | return NewSingleValidator(func(ctx context.Context) Error { 34 | if len(validators) == 0 { 35 | return nil 36 | } 37 | wrapErrs := Errors{} 38 | for _, v := range validators { 39 | errs := v.Validate(ctx) 40 | if len(errs) == 0 { 41 | return nil // return nil when one passes 42 | } 43 | wrapErrs = append(wrapErrs, errs...) 44 | } 45 | return errorBuild("one_of", "", nil, wrapErrs) 46 | }) 47 | } 48 | 49 | // ExactOneOf checks if the target value satisfies only one of the given validators. 50 | // This returns error when there is not one or more than one validator pass. 51 | func ExactOneOf(validators ...Validator) SingleValidator { 52 | return NewSingleValidator(func(ctx context.Context) Error { 53 | numValidatorPass := 0 54 | wrapErrs := Errors{} 55 | for _, v := range validators { 56 | errs := v.Validate(ctx) 57 | if len(errs) == 0 { 58 | numValidatorPass++ 59 | } else { 60 | wrapErrs = append(wrapErrs, errs...) 61 | } 62 | if numValidatorPass > 1 { 63 | break 64 | } 65 | } 66 | if numValidatorPass == 1 { 67 | return nil 68 | } 69 | return errorBuild("exact_one_of", "", nil, wrapErrs) 70 | }) 71 | } 72 | 73 | // NotOf checks the target value not satisfy any of the given validators. 74 | // When a validator passes, an error will be returned and the remaining checks will be skipped. 75 | func NotOf(validators ...Validator) SingleValidator { 76 | return NewSingleValidator(func(ctx context.Context) Error { 77 | for _, v := range validators { 78 | errs := v.Validate(ctx) 79 | if len(errs) == 0 { 80 | return errorBuild("not_of", "", nil, nil) 81 | } 82 | } 83 | return nil 84 | }) 85 | } 86 | 87 | // If a bare check validation convenient for validating custom data such as 88 | // `If(myTime.Before(dueDate)).OnError(vld.SetCustomKey("MY_ERR_KEY"))`. 89 | // Deprecated: use `Must` instead 90 | func If(cond bool) SingleValidator { 91 | return NewSingleValidator(func(context.Context) Error { 92 | if cond { 93 | return nil 94 | } 95 | return errorBuild("if", "", cond, nil) 96 | }) 97 | } 98 | 99 | // Must a bare check validation convenient for validating custom data such as 100 | // `Must(myTime.Before(dueDate)).OnError(vld.SetCustomKey("MY_ERR_KEY"))`. 101 | func Must(cond bool) SingleValidator { 102 | return NewSingleValidator(func(context.Context) Error { 103 | if cond { 104 | return nil 105 | } 106 | return errorBuild("must", "", cond, nil) 107 | }) 108 | } 109 | 110 | // When works like a `if...then...else` statement 111 | func When(conditions ...any) SingleCondValidator { 112 | return NewSingleCondValidator(conditions...) 113 | } 114 | 115 | // Case works like a `switch...case` statement 116 | func Case(conditions ...SingleCondValidator) MultiCondValidator { 117 | return NewMultiCondValidator(conditions...) 118 | } 119 | 120 | // execValidators executes a list of validators and collect errors as result 121 | // nolint: unparam 122 | func execValidators(ctx context.Context, validators []Validator, stopOnError bool) Errors { 123 | errs := make(Errors, 0, len(validators)) 124 | for _, v := range validators { 125 | hasErr := false 126 | for _, e := range v.Validate(ctx) { 127 | if e == nil { 128 | continue 129 | } 130 | hasErr = true 131 | errs = append(errs, e) 132 | } 133 | if stopOnError && hasErr { 134 | break 135 | } 136 | } 137 | if len(errs) == 0 { 138 | return nil 139 | } 140 | return errs 141 | } 142 | -------------------------------------------------------------------------------- /validation_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Group(t *testing.T) { 10 | t.Run("success", func(t *testing.T) { 11 | i := 10 12 | errs := Validate( 13 | Group( 14 | NumGT(&i, 9), 15 | NumEQ(&i, 10), 16 | ), 17 | ) 18 | assert.Equal(t, 0, len(errs)) 19 | }) 20 | 21 | t.Run("success with empty input", func(t *testing.T) { 22 | errs := ValidateWithCtx(ctxBg, 23 | Group(), 24 | ) 25 | assert.Equal(t, 0, len(errs)) 26 | }) 27 | 28 | t.Run("failure", func(t *testing.T) { 29 | i := 10 30 | errs := Validate( 31 | Group( 32 | NumGT(&i, 9), 33 | NumGTE(&i, 11), 34 | NumIn(&i, 0), 35 | ), 36 | ) 37 | assert.Equal(t, 1, len(errs)) 38 | assert.Equal(t, "group", errs[0].Type()) 39 | assert.Equal(t, 2, len(errs[0].Unwrap())) 40 | }) 41 | } 42 | 43 | func Test_OneOf(t *testing.T) { 44 | t.Run("success", func(t *testing.T) { 45 | i := 10 46 | errs := Validate( 47 | OneOf( 48 | NumIn(&i, 0, 1, 2), 49 | NumGT(&i, 9), 50 | NumGT(&i, 5), 51 | NumLT(&i, 0), 52 | ), 53 | ) 54 | assert.Equal(t, 0, len(errs)) 55 | }) 56 | 57 | t.Run("success with empty input", func(t *testing.T) { 58 | errs := Validate( 59 | OneOf(), 60 | ) 61 | assert.Equal(t, 0, len(errs)) 62 | }) 63 | 64 | t.Run("failure", func(t *testing.T) { 65 | i := 10 66 | errs := Validate( 67 | OneOf( 68 | NumIn(&i, 0, 1, 2), 69 | NumGT(&i, 10), 70 | NumLT(&i, 0), 71 | ), 72 | ) 73 | assert.Equal(t, 1, len(errs)) 74 | assert.Equal(t, "one_of", errs[0].Type()) 75 | }) 76 | } 77 | 78 | func Test_ExactOneOf(t *testing.T) { 79 | t.Run("success", func(t *testing.T) { 80 | i := 10 81 | errs := Validate( 82 | ExactOneOf( 83 | NumIn(&i, 0, 1, 2), 84 | NumGT(&i, 9), 85 | NumLT(&i, 0), 86 | ), 87 | ) 88 | assert.Equal(t, 0, len(errs)) 89 | }) 90 | 91 | t.Run("failure", func(t *testing.T) { 92 | i := 10 93 | errs := Validate( 94 | ExactOneOf( 95 | NumIn(&i, 0, 1, 2), 96 | NumGT(&i, 9), 97 | NumGT(&i, 5), 98 | NumGT(&i, 3), 99 | NumLT(&i, 0), 100 | ), 101 | ) 102 | assert.Equal(t, 1, len(errs)) 103 | assert.Equal(t, "exact_one_of", errs[0].Type()) 104 | }) 105 | 106 | t.Run("failure with empty input", func(t *testing.T) { 107 | errs := Validate( 108 | ExactOneOf(), 109 | ) 110 | assert.Equal(t, 1, len(errs)) 111 | assert.Equal(t, "exact_one_of", errs[0].Type()) 112 | }) 113 | } 114 | 115 | func Test_NotOf(t *testing.T) { 116 | t.Run("success", func(t *testing.T) { 117 | i := 10 118 | errs := Validate( 119 | NotOf( 120 | NumEQ(&i, 9), 121 | NumGT(&i, 11), 122 | NumLT(&i, 10), 123 | ), 124 | ) 125 | assert.Equal(t, 0, len(errs)) 126 | }) 127 | 128 | t.Run("failure", func(t *testing.T) { 129 | i := 10 130 | errs := Validate( 131 | NotOf( 132 | NumEQ(&i, 9), 133 | NumGTE(&i, 10), 134 | NumLT(&i, 10), 135 | ), 136 | ) 137 | assert.Equal(t, 1, len(errs)) 138 | assert.Equal(t, "not_of", errs[0].Type()) 139 | }) 140 | } 141 | 142 | func Test_If(t *testing.T) { 143 | t.Run("success", func(t *testing.T) { 144 | i := 10 145 | errs := Validate( 146 | If(i == 10).OnError(SetCustomKey("i must be 10")), 147 | ) 148 | assert.Equal(t, 0, len(errs)) 149 | }) 150 | 151 | t.Run("failure", func(t *testing.T) { 152 | i := 11 153 | errs := Validate( 154 | If(i == 10).OnError(SetCustomKey("i must be 10")), 155 | ) 156 | assert.Equal(t, 1, len(errs)) 157 | assert.Equal(t, "if", errs[0].Type()) 158 | }) 159 | } 160 | 161 | func Test_Must(t *testing.T) { 162 | t.Run("success", func(t *testing.T) { 163 | i := 10 164 | errs := Validate( 165 | Must(i == 10).OnError(SetCustomKey("i must be 10")), 166 | ) 167 | assert.Equal(t, 0, len(errs)) 168 | }) 169 | 170 | t.Run("failure", func(t *testing.T) { 171 | i := 11 172 | errs := Validate( 173 | Must(i == 10).OnError(SetCustomKey("i must be 10")), 174 | ) 175 | assert.Equal(t, 1, len(errs)) 176 | assert.Equal(t, "must", errs[0].Type()) 177 | }) 178 | } 179 | 180 | func Test_When(t *testing.T) { 181 | t.Run("success then", func(t *testing.T) { 182 | i1 := 1 183 | i2 := 10 184 | errs := Validate( 185 | When(NumEQ(&i1, 1)).Then( 186 | NumGT(&i2, 1), 187 | NumLT(&i2, 100), 188 | ).Else( 189 | NumEQ(&i2, 1), 190 | ), 191 | ) 192 | assert.Equal(t, 0, len(errs)) 193 | }) 194 | 195 | t.Run("success else", func(t *testing.T) { 196 | i1 := 1 197 | i2 := 10 198 | errs := Validate( 199 | When(NumEQ(&i1, 2)).Then( 200 | NumEQ(&i2, 100), 201 | ).Else( 202 | NumEQ(&i2, 10), 203 | ), 204 | ) 205 | assert.Equal(t, 0, len(errs)) 206 | }) 207 | 208 | t.Run("success with OnError() set for final error", func(t *testing.T) { 209 | i1 := 1 210 | i2 := 10 211 | errs := Validate( 212 | When(NumEQ(&i1, 2)).Then( 213 | NumEQ(&i2, 100), 214 | ).Else( 215 | NumEQ(&i2, 10), 216 | ).OnError( 217 | SetCustomKey("custom_key"), 218 | ), 219 | ) 220 | assert.Equal(t, 0, len(errs)) 221 | }) 222 | 223 | t.Run("failure then", func(t *testing.T) { 224 | i1 := 1 225 | i2 := 10 226 | errs := Validate( 227 | When(NumEQ(&i1, 1)).Then( 228 | NumGT(&i2, 100), 229 | ).Else( 230 | NumEQ(&i2, 10), 231 | ), 232 | ) 233 | assert.Equal(t, 1, len(errs)) 234 | assert.Equal(t, "gt", errs[0].Type()) 235 | }) 236 | 237 | t.Run("failure else", func(t *testing.T) { 238 | i1 := 1 239 | i2 := 10 240 | errs := Validate( 241 | When(NumEQ(&i1, 2)).Then( 242 | NumGT(&i2, 100), 243 | ).Else( 244 | NumEQ(&i2, 100), 245 | ), 246 | ) 247 | assert.Equal(t, 1, len(errs)) 248 | assert.Equal(t, "eq", errs[0].Type()) 249 | }) 250 | 251 | t.Run("failure as invalid condition input", func(t *testing.T) { 252 | defer func() { 253 | e := recover() 254 | assert.Equal(t, "type unsupported: only 'bool' or 'validator' allowed", e.(error).Error()) 255 | }() 256 | 257 | _ = Validate( 258 | When(123).Then(), 259 | ) 260 | }) 261 | 262 | t.Run("failure with OnError() set for final error", func(t *testing.T) { 263 | i1 := 1 264 | i2 := 10 265 | errs := Validate( 266 | When(NumEQ(&i1, 2)).Then( 267 | NumGT(&i2, 100), 268 | ).Else( 269 | NumEQ(&i2, 100), 270 | ).OnError( 271 | SetCustomKey("custom_key"), 272 | ), 273 | ) 274 | assert.Equal(t, 1, len(errs)) 275 | assert.Equal(t, "eq", errs[0].Type()) 276 | assert.Equal(t, "custom_key", errs[0].CustomKey()) 277 | }) 278 | } 279 | 280 | func Test_Case(t *testing.T) { 281 | t.Run("success case", func(t *testing.T) { 282 | i1 := 1 283 | i2 := 10 284 | errs := Validate( 285 | Case( 286 | When(NumEQ(&i1, 1)).Then(NumGT(&i2, 0)), 287 | When(NumEQ(&i1, 2)).Then(NumLT(&i2, 100)), 288 | ).Default( 289 | NumEQ(&i2, 1), 290 | ), 291 | ) 292 | assert.Equal(t, 0, len(errs)) 293 | }) 294 | 295 | t.Run("success case default", func(t *testing.T) { 296 | i1 := 3 297 | i2 := 10 298 | errs := Validate( 299 | Case( 300 | When(NumEQ(&i1, 1)).Then(NumGT(&i2, 0)), 301 | When(NumEQ(&i1, 2)).Then(NumLT(&i2, 100)), 302 | ).Default( 303 | NumEQ(&i2, 10), 304 | ), 305 | ) 306 | assert.Equal(t, 0, len(errs)) 307 | }) 308 | 309 | t.Run("success case with OnError() set for final error", func(t *testing.T) { 310 | i1 := 1 311 | i2 := 10 312 | errs := Validate( 313 | Case( 314 | When(NumEQ(&i1, 1)).Then(NumGT(&i2, 0)), 315 | When(NumEQ(&i1, 2)).Then(NumLT(&i2, 100)), 316 | ).Default( 317 | NumEQ(&i2, 1), 318 | ).OnError( 319 | SetCustomKey("custom_key"), 320 | ), 321 | ) 322 | assert.Equal(t, 0, len(errs)) 323 | }) 324 | 325 | t.Run("failure case", func(t *testing.T) { 326 | i1 := 2 327 | i2 := 100 328 | errs := Validate( 329 | Case( 330 | When(NumEQ(&i1, 1)).Then(NumGT(&i2, 0)), 331 | When(NumEQ(&i1, 2)).Then(NumLT(&i2, 10)), 332 | ).Default( 333 | NumEQ(&i2, 10), 334 | ), 335 | ) 336 | assert.Equal(t, 1, len(errs)) 337 | assert.Equal(t, "lt", errs[0].Type()) 338 | }) 339 | 340 | t.Run("failure case default", func(t *testing.T) { 341 | i1 := 3 342 | i2 := 100 343 | errs := Validate( 344 | Case( 345 | When(NumEQ(&i1, 1)).Then(NumGT(&i2, 0)), 346 | When(NumEQ(&i1, 2)).Then(NumLT(&i2, 10)), 347 | ).Default( 348 | NumEQ(&i2, 10), 349 | ), 350 | ) 351 | assert.Equal(t, 1, len(errs)) 352 | assert.Equal(t, "eq", errs[0].Type()) 353 | }) 354 | 355 | t.Run("failure case with OnError() set for final error", func(t *testing.T) { 356 | i1 := 2 357 | i2 := 100 358 | errs := Validate( 359 | Case( 360 | When(NumEQ(&i1, 1)).Then(NumGT(&i2, 0)), 361 | When(NumEQ(&i1, 2)).Then(NumLT(&i2, 10)), 362 | ).Default( 363 | NumEQ(&i2, 10), 364 | ).OnError( 365 | SetCustomKey("custom_key"), 366 | ), 367 | ) 368 | assert.Equal(t, 1, len(errs)) 369 | assert.Equal(t, "lt", errs[0].Type()) 370 | assert.Equal(t, "custom_key", errs[0].CustomKey()) 371 | }) 372 | 373 | t.Run("failure case with OnError() set for multiple final errors", func(t *testing.T) { 374 | i1 := 2 375 | i2 := 100 376 | errs := Validate( 377 | Case( 378 | When(NumEQ(&i1, 1)).Then(NumGT(&i2, 0)), 379 | When(NumEQ(&i1, 2)).Then( 380 | NumLT(&i2, 10), 381 | NumEQ(&i2, 11), 382 | ), 383 | ).Default( 384 | NumEQ(&i2, 10), 385 | ).OnError( 386 | SetCustomKey("custom_key"), 387 | ), 388 | ) 389 | assert.Equal(t, 1, len(errs)) 390 | assert.Equal(t, "group", errs[0].Type()) 391 | assert.Equal(t, "custom_key", errs[0].CustomKey()) 392 | inErrs := errs[0].UnwrapAsErrors() 393 | assert.Equal(t, "lt", inErrs[0].Type()) 394 | assert.Equal(t, "eq", inErrs[1].Type()) 395 | }) 396 | } 397 | -------------------------------------------------------------------------------- /validator_common.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | commonFunc "github.com/tiendc/go-validator/base/common" 5 | ) 6 | 7 | // Nil validates the input pointer to be `nil` 8 | func Nil[T any](v *T) SingleValidator { 9 | return call1[*T]("nil", "", commonFunc.Nil[T])(v) 10 | } 11 | 12 | // NotNil validates the input pointer to be not `nil` 13 | func NotNil[T any](v *T) SingleValidator { 14 | return call1[*T]("not_nil", "", commonFunc.NotNil[T])(v) 15 | } 16 | 17 | // Required validates the input to be required. 18 | // Required value must be not: 19 | // - zero value (0, "", nil, false) 20 | // - empty slice, array, map, channel 21 | // - pointer points to zero value 22 | func Required(v any) SingleValidator { 23 | return call1[any]("required", "", commonFunc.Required)(v) 24 | } 25 | -------------------------------------------------------------------------------- /validator_common_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/tiendc/gofn" 9 | ) 10 | 11 | var ctxBg = context.Background() 12 | 13 | func Test_Nil_NotNil(t *testing.T) { 14 | // Success cases 15 | errsNil := Nil[any](nil).Validate(ctxBg) 16 | errsNotNil := NotNil[any](nil).Validate(ctxBg) 17 | assert.True(t, len(errsNil) == 0 && len(errsNotNil) == 1) 18 | 19 | var pStr *string 20 | errsNil = Nil(pStr).Validate(ctxBg) 21 | errsNotNil = NotNil(pStr).Validate(ctxBg) 22 | assert.True(t, len(errsNil) == 0 && len(errsNotNil) == 1) 23 | 24 | var pSlice *[]string 25 | errsNil = Nil(pSlice).Validate(ctxBg) 26 | errsNotNil = NotNil(pSlice).Validate(ctxBg) 27 | assert.True(t, len(errsNil) == 0 && len(errsNotNil) == 1) 28 | 29 | var pMap *map[int]bool 30 | errsNil = Nil(pMap).Validate(ctxBg) 31 | errsNotNil = NotNil(pMap).Validate(ctxBg) 32 | assert.True(t, len(errsNil) == 0 && len(errsNotNil) == 1) 33 | 34 | // Failure cases 35 | errsNil = Nil(gofn.ToPtr[any]("")).Validate(ctxBg) 36 | errsNotNil = NotNil(gofn.ToPtr[any]("")).Validate(ctxBg) 37 | assert.False(t, len(errsNil) == 0 && len(errsNotNil) == 1) 38 | 39 | errsNil = Nil(gofn.ToPtr[int](0)).Validate(ctxBg) 40 | errsNotNil = NotNil(gofn.ToPtr[int](0)).Validate(ctxBg) 41 | assert.False(t, len(errsNil) == 0 && len(errsNotNil) == 1) 42 | 43 | var aSlice []string 44 | errsNil = Nil(&aSlice).Validate(ctxBg) 45 | errsNotNil = NotNil(&aSlice).Validate(ctxBg) 46 | assert.False(t, len(errsNil) == 0 && len(errsNotNil) == 1) 47 | 48 | var aMap map[int]bool 49 | errsNil = Nil(&aMap).Validate(ctxBg) 50 | errsNotNil = NotNil(&aMap).Validate(ctxBg) 51 | assert.False(t, len(errsNil) == 0 && len(errsNotNil) == 1) 52 | } 53 | 54 | func Test_Required(t *testing.T) { 55 | // Failure cases 56 | errs := Required(nil).Validate(ctxBg) 57 | assert.Equal(t, 1, len(errs)) 58 | assert.Equal(t, "required", errs[0].Type()) 59 | 60 | aInt := int32(0) 61 | errs = Required(&aInt).Validate(ctxBg) 62 | assert.Equal(t, 1, len(errs)) 63 | assert.Equal(t, "required", errs[0].Type()) 64 | 65 | aStr := "" 66 | errs = Required(&aStr).Validate(ctxBg) 67 | assert.Equal(t, 1, len(errs)) 68 | assert.Equal(t, "required", errs[0].Type()) 69 | 70 | aSlice := []string{} 71 | errs = Required(&aSlice).Validate(ctxBg) 72 | assert.Equal(t, 1, len(errs)) 73 | assert.Equal(t, "required", errs[0].Type()) 74 | 75 | aMap := map[int]bool{} 76 | errs = Required(&aMap).Validate(ctxBg) 77 | assert.Equal(t, 1, len(errs)) 78 | assert.Equal(t, "required", errs[0].Type()) 79 | 80 | var aAny any 81 | aAny = aMap 82 | errs = Required(&aAny).Validate(ctxBg) 83 | assert.Equal(t, 1, len(errs)) 84 | assert.Equal(t, "required", errs[0].Type()) 85 | 86 | // Success cases 87 | aInt = 1 88 | errs = Required(&aInt).Validate(ctxBg) 89 | assert.Equal(t, 0, len(errs)) 90 | 91 | aStr = "a" 92 | errs = Required(&aStr).Validate(ctxBg) 93 | assert.Equal(t, 0, len(errs)) 94 | 95 | aSlice = []string{"a", "b"} 96 | errs = Required(&aSlice).Validate(ctxBg) 97 | assert.Equal(t, 0, len(errs)) 98 | 99 | aMap = map[int]bool{0: false} 100 | errs = Required(&aMap).Validate(ctxBg) 101 | assert.Equal(t, 0, len(errs)) 102 | 103 | aAny = aSlice 104 | errs = Required(&aAny).Validate(ctxBg) 105 | assert.Equal(t, 0, len(errs)) 106 | } 107 | -------------------------------------------------------------------------------- /validator_map.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "github.com/tiendc/go-validator/base" 5 | mapFunc "github.com/tiendc/go-validator/base/map" 6 | ) 7 | 8 | const ( 9 | mapType = "map" 10 | ) 11 | 12 | // Map allows validating every map entry 13 | func Map[K comparable, V any, M ~map[K]V](m M) MapContentValidator[K, V, M] { 14 | return NewMapContentValidator(m) 15 | } 16 | 17 | // MapLen validates the input map must have length in the specified range 18 | func MapLen[K comparable, V any, M ~map[K]V](m M, min, max int) SingleValidator { 19 | return call3[M]("len", mapType, "Min", "Max", mapFunc.Len[K, V, M])(m, min, max) 20 | } 21 | 22 | // MapHasKey validates the input map must have the specified keys 23 | func MapHasKey[K comparable, V any, M ~map[K]V](m M, keys ...K) SingleValidator { 24 | return call2N[M]("has_key", mapType, "TargetValue", mapFunc.HasKey[K, V, M])(m, keys...) 25 | } 26 | 27 | // MapNotHaveKey validates the input map must not have the specified keys 28 | func MapNotHaveKey[K comparable, V any, M ~map[K]V](m M, keys ...K) SingleValidator { 29 | return call2N[M]("not_have_key", mapType, "TargetValue", mapFunc.NotHaveKey[K, V, M])(m, keys...) 30 | } 31 | 32 | // MapKeyIn validates the input map must have keys in the specified values 33 | func MapKeyIn[K comparable, V any, M ~map[K]V](m M, keys ...K) SingleValidator { 34 | return call2N[M]("key_in", mapType, "TargetValue", mapFunc.KeyIn[K, V, M])(m, keys...) 35 | } 36 | 37 | // MapKeyNotIn validates the input map must have keys not in the specified values 38 | func MapKeyNotIn[K comparable, V any, M ~map[K]V](m M, keys ...K) SingleValidator { 39 | return call2N[M]("key_not_in", mapType, "TargetValue", mapFunc.KeyNotIn[K, V, M])(m, keys...) 40 | } 41 | 42 | // MapKeyRange validates the input map must have keys in the specified range. 43 | // Only applies to key type of number or string. 44 | func MapKeyRange[K base.Number | base.String, V any, M ~map[K]V](m M, min, max K) SingleValidator { 45 | return call3[M]("key_range", mapType, "Min", "Max", mapFunc.KeyRange[K, V, M])(m, min, max) 46 | } 47 | 48 | // MapValueIn validates the input map must have values in the specified values 49 | func MapValueIn[K comparable, V comparable, M ~map[K]V](m M, values ...V) SingleValidator { 50 | return call2N[M]("value_in", mapType, "TargetValue", mapFunc.ValueIn[K, V, M])(m, values...) 51 | } 52 | 53 | // MapValueNotIn validates the input map must have values not in the specified values 54 | func MapValueNotIn[K comparable, V comparable, M ~map[K]V](m M, values ...V) SingleValidator { 55 | return call2N[M]("value_not_in", mapType, "TargetValue", mapFunc.ValueNotIn[K, V, M])(m, values...) 56 | } 57 | 58 | // MapValueRange validates the input map must have values in the specified range. 59 | // Only applies to value type of number or string. 60 | func MapValueRange[K comparable, V base.Number | base.String, M ~map[K]V](m M, min, max V) SingleValidator { 61 | return call3[M]("value_range", mapType, "Min", "Max", mapFunc.ValueRange[K, V, M])(m, min, max) 62 | } 63 | 64 | // MapValueUnique validates the input map must have unique values 65 | func MapValueUnique[K comparable, V comparable, M ~map[K]V](m M) SingleValidator { 66 | return call1[M]("value_unique", mapType, mapFunc.ValueUnique[K, V, M])(m) 67 | } 68 | -------------------------------------------------------------------------------- /validator_map_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_MapLen(t *testing.T) { 11 | errs := MapLen(map[int]int{1: 1, 2: 2}, 2, 10).Validate(ctxBg) 12 | assert.Equal(t, 0, len(errs)) 13 | 14 | errs = MapLen(map[int]int{1: 1, 2: 2}, 3, 10).Validate(ctxBg) 15 | assert.Equal(t, "len", errs[0].Type()) 16 | } 17 | 18 | func Test_MapHasKey(t *testing.T) { 19 | errs := MapHasKey(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2, 3, 3, 2).Validate(ctxBg) 20 | assert.Equal(t, 0, len(errs)) 21 | 22 | errs = MapHasKey(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2, 3, 4).Validate(ctxBg) 23 | assert.Equal(t, "has_key", errs[0].Type()) 24 | } 25 | 26 | func Test_MapNotHaveKey(t *testing.T) { 27 | errs := MapNotHaveKey(map[int]int{3: 3, 2: 2, 1: 1}, 0, 4, 5).Validate(ctxBg) 28 | assert.Equal(t, 0, len(errs)) 29 | 30 | errs = MapNotHaveKey(map[int]int{3: 3, 2: 2, 1: 1}, 3, 1, 2).Validate(ctxBg) 31 | assert.Equal(t, "not_have_key", errs[0].Type()) 32 | } 33 | 34 | func Test_MapKeyIn(t *testing.T) { 35 | errs := MapKeyIn(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2, 3, 4, 5).Validate(ctxBg) 36 | assert.Equal(t, 0, len(errs)) 37 | 38 | errs = MapKeyIn(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2).Validate(ctxBg) 39 | assert.Equal(t, "key_in", errs[0].Type()) 40 | } 41 | 42 | func Test_MapKeyNotIn(t *testing.T) { 43 | errs := MapKeyNotIn(map[int]int{3: 3, 2: 2, 1: 1}, 4, 5, 6).Validate(ctxBg) 44 | assert.Equal(t, 0, len(errs)) 45 | 46 | errs = MapKeyNotIn(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2).Validate(ctxBg) 47 | assert.Equal(t, "key_not_in", errs[0].Type()) 48 | } 49 | 50 | func Test_MapKeyRange(t *testing.T) { 51 | errs := MapKeyRange(map[int]int{3: 3, 2: 2, 1: 1}, 0, 3).Validate(ctxBg) 52 | assert.Equal(t, 0, len(errs)) 53 | 54 | errs = MapKeyRange(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2).Validate(ctxBg) 55 | assert.Equal(t, "key_range", errs[0].Type()) 56 | } 57 | 58 | func Test_MapValueIn(t *testing.T) { 59 | errs := MapValueIn(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2, 3, 4, 5).Validate(ctxBg) 60 | assert.Equal(t, 0, len(errs)) 61 | 62 | errs = MapValueIn(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2).Validate(ctxBg) 63 | assert.Equal(t, "value_in", errs[0].Type()) 64 | } 65 | 66 | func Test_MapValueNotIn(t *testing.T) { 67 | errs := MapValueNotIn(map[int]int{3: 3, 2: 2, 1: 1}, 4, 5, 6).Validate(ctxBg) 68 | assert.Equal(t, 0, len(errs)) 69 | 70 | errs = MapValueNotIn(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2).Validate(ctxBg) 71 | assert.Equal(t, "value_not_in", errs[0].Type()) 72 | } 73 | 74 | func Test_MapValueRange(t *testing.T) { 75 | errs := MapValueRange(map[int]int{3: 3, 2: 2, 1: 1}, 0, 3).Validate(ctxBg) 76 | assert.Equal(t, 0, len(errs)) 77 | 78 | errs = MapValueRange(map[int]int{3: 3, 2: 2, 1: 1}, 1, 2).Validate(ctxBg) 79 | assert.Equal(t, "value_range", errs[0].Type()) 80 | } 81 | 82 | func Test_MapValueUnique(t *testing.T) { 83 | errs := MapValueUnique(map[int]int{3: 3, 2: 2, 1: 1}).Validate(ctxBg) 84 | assert.Equal(t, 0, len(errs)) 85 | 86 | errs = MapValueUnique(map[int]int{3: 1, 2: 2, 1: 1}).Validate(ctxBg) 87 | assert.Equal(t, "value_unique", errs[0].Type()) 88 | } 89 | 90 | func Test_MapContent_Validate(t *testing.T) { 91 | t.Run("nil/empty map", func(t *testing.T) { 92 | // Nil map 93 | errs := Map(map[int]string(nil)).ForEach(func(k int, v string, validator ItemValidator) { 94 | validator.Validate(StrLen(&v, 1, 10)) 95 | }).Validate(ctxBg) 96 | assert.Equal(t, 0, len(errs)) 97 | 98 | // Empty map 99 | errs = Map(map[int]string{}).ForEach(func(k int, v string, validator ItemValidator) { 100 | validator.Validate(StrLen(&v, 1, 10)) 101 | }).Validate(ctxBg) 102 | assert.Equal(t, 0, len(errs)) 103 | }) 104 | 105 | t.Run("validate entries", func(t *testing.T) { 106 | // Validate entries 107 | errs := Map(map[int]int{3: 3, 2: 2, 1: 1}).ForEach(func(k int, v int, validator ItemValidator) { 108 | validator.Validate( 109 | NumGTE(&v, 1), 110 | ) 111 | }).Validate(ctxBg) 112 | assert.Equal(t, 0, len(errs)) 113 | 114 | // Validate entries with errors 115 | errs = Map(map[int]int{3: 3, 2: 2, 1: 1}).ForEach(func(k int, v int, validator ItemValidator) { 116 | validator.Validate( 117 | NumGT(&v, 2).OnError( 118 | SetField(fmt.Sprintf("map[%d]", k), nil), 119 | SetCustomKey("ERR_VLD_MAP_ENTRY_INVALID"), 120 | ), 121 | ) 122 | }).OnError().Validate(ctxBg) 123 | assert.Equal(t, 2, len(errs)) 124 | assert.Equal(t, "gt", errs[0].Type()) 125 | assert.Equal(t, "gt", errs[1].Type()) 126 | }) 127 | 128 | t.Run("validate entries with Group", func(t *testing.T) { 129 | errs := Map(map[int]int{3: 3, 2: 2, 1: 1}).ForEach(func(k int, v int, validator ItemValidator) { 130 | validator.Group( 131 | NumGTE(&v, 1), 132 | NumLTE(&v, 2), 133 | ) 134 | }).Validate(ctxBg) 135 | assert.Equal(t, 1, len(errs)) 136 | assert.Equal(t, "group", errs[0].Type()) 137 | }) 138 | 139 | t.Run("validate entries with OneOf", func(t *testing.T) { 140 | errs := Map(map[int]int{3: 3, 2: 2, 1: 1}).ForEach(func(k int, v int, validator ItemValidator) { 141 | validator.OneOf( 142 | NumLTE(&v, 2), 143 | NumGTE(&v, 1), 144 | ) 145 | }).Validate(ctxBg) 146 | assert.Equal(t, 0, len(errs)) 147 | }) 148 | 149 | t.Run("validate entries with ExactOneOf", func(t *testing.T) { 150 | errs := Map(map[int]int{3: 3, 2: 2, 1: 1}).ForEach(func(k int, v int, validator ItemValidator) { 151 | validator.ExactOneOf( 152 | NumGTE(&v, 1), 153 | NumLTE(&v, 2), 154 | ) 155 | }).Validate(ctxBg) 156 | assert.Equal(t, 2, len(errs)) // slice[1] and slice[2] not satisfy 157 | }) 158 | 159 | t.Run("validate entries with NotOf", func(t *testing.T) { 160 | errs := Map(map[int]int{3: 3, 2: 2, 1: 1}).ForEach(func(k int, v int, validator ItemValidator) { 161 | validator.NotOf( 162 | NumGTE(&v, 1), 163 | NumLTE(&v, 2), 164 | ) 165 | }).Validate(ctxBg) 166 | assert.Equal(t, 3, len(errs)) 167 | }) 168 | } 169 | -------------------------------------------------------------------------------- /validator_number.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "github.com/tiendc/go-validator/base" 5 | numFunc "github.com/tiendc/go-validator/base/number" 6 | ) 7 | 8 | const ( 9 | numType = "number" 10 | ) 11 | 12 | // NumEQ validates the input number must equal to a value 13 | func NumEQ[T base.Number](v *T, val T) SingleValidator { 14 | return ptrCall2[T]("eq", numType, "TargetValue", numFunc.EQ[T])(v, val) 15 | } 16 | 17 | // NumGT validates the input number must be greater than a value 18 | func NumGT[T base.Number](v *T, min T) SingleValidator { 19 | return ptrCall2[T]("gt", numType, "Min", numFunc.GT[T])(v, min) 20 | } 21 | 22 | // NumGTE validates the input number must be greater than or equal to a value 23 | func NumGTE[T base.Number](v *T, min T) SingleValidator { 24 | return ptrCall2[T]("gte", numType, "Min", numFunc.GTE[T])(v, min) 25 | } 26 | 27 | // NumLT validates the input number must be less than a value 28 | func NumLT[T base.Number](v *T, max T) SingleValidator { 29 | return ptrCall2[T]("lt", numType, "Max", numFunc.LT[T])(v, max) 30 | } 31 | 32 | // NumLTE validates the input number must be less than or equal to a value 33 | func NumLTE[T base.Number](v *T, max T) SingleValidator { 34 | return ptrCall2[T]("lte", numType, "Max", numFunc.LTE[T])(v, max) 35 | } 36 | 37 | // NumRange validates the input number must be in the specified range 38 | func NumRange[T base.Number](v *T, min, max T) SingleValidator { 39 | return ptrCall3[T]("range", numType, "Min", "Max", numFunc.Range[T])(v, min, max) 40 | } 41 | 42 | // NumIn validates the input number must be in the specified values 43 | func NumIn[T base.Number](v *T, s ...T) SingleValidator { 44 | return ptrCall2N[T]("in", numType, "TargetValue", numFunc.In[T])(v, s...) 45 | } 46 | 47 | // NumNotIn validates the input number must be not in the specified values 48 | func NumNotIn[T base.Number](v *T, s ...T) SingleValidator { 49 | return ptrCall2N[T]("not_in", numType, "TargetValue", numFunc.NotIn[T])(v, s...) 50 | } 51 | 52 | // NumDivisibleBy validates the input number must be divisible by the specified value 53 | func NumDivisibleBy[T base.Int | base.UInt](v *T, div T) SingleValidator { 54 | return ptrCall2[T]("divisible_by", numType, "TargetValue", numFunc.DivisibleBy[T])(v, div) 55 | } 56 | 57 | // NumJsSafeInt validates the input number must be a Javascript safe integer (max 2^53-1) 58 | func NumJsSafeInt[T base.Int | base.UInt](v *T) SingleValidator { 59 | return ptrCall1[T]("js_safe_int", numType, numFunc.JsSafeInt[T])(v) 60 | } 61 | -------------------------------------------------------------------------------- /validator_number_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | func Test_NumEQ(t *testing.T) { 11 | errs := NumEQ(gofn.ToPtr(10), 10).Validate(ctxBg) 12 | assert.Equal(t, 0, len(errs)) 13 | errs = NumEQ(gofn.ToPtr(10), 9).Validate(ctxBg) 14 | assert.Equal(t, 1, len(errs)) 15 | } 16 | 17 | func Test_NumGT_NumGTE(t *testing.T) { 18 | errs := NumGT(gofn.ToPtr(10), 9).Validate(ctxBg) 19 | assert.Equal(t, 0, len(errs)) 20 | errs = NumGTE(gofn.ToPtr(10), 10).Validate(ctxBg) 21 | assert.Equal(t, 0, len(errs)) 22 | 23 | errs = NumGT(gofn.ToPtr(10), 10).Validate(ctxBg) 24 | assert.Equal(t, "gt", errs[0].Type()) 25 | errs = NumGTE(gofn.ToPtr(10), 11).Validate(ctxBg) 26 | assert.Equal(t, "gte", errs[0].Type()) 27 | } 28 | 29 | func Test_NumLT_NumLTE(t *testing.T) { 30 | errs := NumLT(gofn.ToPtr(10), 11).Validate(ctxBg) 31 | assert.Equal(t, 0, len(errs)) 32 | errs = NumLTE(gofn.ToPtr(10), 10).Validate(ctxBg) 33 | assert.Equal(t, 0, len(errs)) 34 | 35 | errs = NumLT(gofn.ToPtr(10), 10).Validate(ctxBg) 36 | assert.Equal(t, "lt", errs[0].Type()) 37 | errs = NumLTE(gofn.ToPtr(10), 9).Validate(ctxBg) 38 | assert.Equal(t, "lte", errs[0].Type()) 39 | } 40 | 41 | func Test_NumRange(t *testing.T) { 42 | errs := NumRange(gofn.ToPtr(10), 2, 10).Validate(ctxBg) 43 | assert.Equal(t, 0, len(errs)) 44 | 45 | errs = NumRange(gofn.ToPtr(10), 1, 9).Validate(ctxBg) 46 | assert.Equal(t, "range", errs[0].Type()) 47 | } 48 | 49 | func Test_NumIn(t *testing.T) { 50 | errs := NumIn(gofn.ToPtr(1), 0, 1, 10).Validate(ctxBg) 51 | assert.Equal(t, 0, len(errs)) 52 | 53 | errs = NumIn(gofn.ToPtr(1), 0, 2, 10).Validate(ctxBg) 54 | assert.Equal(t, "in", errs[0].Type()) 55 | } 56 | 57 | func Test_NumNotIn(t *testing.T) { 58 | errs := NumNotIn(gofn.ToPtr(1), 0, 2, 10).Validate(ctxBg) 59 | assert.Equal(t, 0, len(errs)) 60 | 61 | errs = NumNotIn(gofn.ToPtr(1), 0, 1, 2).Validate(ctxBg) 62 | assert.Equal(t, "not_in", errs[0].Type()) 63 | } 64 | 65 | func Test_NumDivisibleBy(t *testing.T) { 66 | errs := NumDivisibleBy(gofn.ToPtr(10), 5).Validate(ctxBg) 67 | assert.Equal(t, 0, len(errs)) 68 | 69 | errs = NumDivisibleBy(gofn.ToPtr(8), 3).Validate(ctxBg) 70 | assert.Equal(t, "divisible_by", errs[0].Type()) 71 | } 72 | 73 | func Test_NumJsSafeInt(t *testing.T) { 74 | errs := NumJsSafeInt(gofn.ToPtr(9007199254740991)).Validate(ctxBg) 75 | assert.Equal(t, 0, len(errs)) 76 | 77 | errs = NumJsSafeInt(gofn.ToPtr(9007199254740992)).Validate(ctxBg) 78 | assert.Equal(t, "js_safe_int", errs[0].Type()) 79 | } 80 | -------------------------------------------------------------------------------- /validator_slice.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "github.com/tiendc/go-validator/base" 5 | sliceFunc "github.com/tiendc/go-validator/base/slice" 6 | ) 7 | 8 | const ( 9 | sliceType = "slice" 10 | ) 11 | 12 | // Slice allows validating slice elements 13 | func Slice[T any, S ~[]T](s S) SliceContentValidator[T, S] { 14 | return NewSliceContentValidator(s) 15 | } 16 | 17 | // SliceLen validates the input slice must have length in the specified range 18 | func SliceLen[T any, S ~[]T](v S, min, max int) SingleValidator { 19 | return call3[S]("len", sliceType, "Min", "Max", sliceFunc.Len[T, S])(v, min, max) 20 | } 21 | 22 | // SliceUnique validates the input slice must contain only unique items 23 | func SliceUnique[T comparable, S ~[]T](v S) SingleValidator { 24 | return call1[S]("unique", sliceType, sliceFunc.Unique[T, S])(v) 25 | } 26 | 27 | // SliceUniqueBy validates the input slice must contain only unique items 28 | func SliceUniqueBy[T any, U comparable, S ~[]T](v S, keyFn func(T) U) SingleValidator { 29 | return call2[S]("unique", sliceType, "KeyFunction", sliceFunc.UniqueBy[T, U, S])(v, keyFn) 30 | } 31 | 32 | // SliceSorted validates the input slice must be sorted in ascending order 33 | func SliceSorted[T base.Number | base.String, S ~[]T](v S) SingleValidator { 34 | return call1[S]("sorted", sliceType, sliceFunc.Sorted[T, S])(v) 35 | } 36 | 37 | // SliceSortedBy validates the input slice must be sorted in ascending order defined by the key function 38 | func SliceSortedBy[T any, U base.Number | base.String, S ~[]T](v S, keyFn func(T) U) SingleValidator { 39 | return call2[S]("sorted", sliceType, "KeyFunction", sliceFunc.SortedBy[T, U, S])(v, keyFn) 40 | } 41 | 42 | // SliceSortedDesc validates the input slice must be sorted in descending order 43 | func SliceSortedDesc[T base.Number | base.String, S ~[]T](v S) SingleValidator { 44 | return call1[S]("sorted_desc", sliceType, sliceFunc.SortedDesc[T, S])(v) 45 | } 46 | 47 | // SliceSortedDescBy validates the input slice must be sorted in ascending order defined by the key function 48 | func SliceSortedDescBy[T any, U base.Number | base.String, S ~[]T](v S, keyFn func(T) U) SingleValidator { 49 | return call2[S]("sorted_desc", sliceType, "KeyFunction", sliceFunc.SortedDescBy[T, U, S])(v, keyFn) 50 | } 51 | 52 | // SliceHasElem validates the input slice must contain the specified values 53 | func SliceHasElem[T comparable, S ~[]T](v S, list ...T) SingleValidator { 54 | return call2N[S]("has_elem", sliceType, "TargetValue", sliceFunc.HasElem[T, S])(v, list...) 55 | } 56 | 57 | // SliceHasElemBy validates the input slice must contain certain values using custom function 58 | func SliceHasElemBy[T any, S ~[]T](v S, isExistFn func(T) bool) SingleValidator { 59 | return call2[S]("has_elem_by", sliceType, "CheckFunction", sliceFunc.HasElemBy[T, S])(v, isExistFn) 60 | } 61 | 62 | // SliceNotHaveElem validates the input slice must not contain the specified values 63 | func SliceNotHaveElem[T comparable, S ~[]T](v S, list ...T) SingleValidator { 64 | return call2N[S]("not_have_elem", sliceType, "TargetValue", sliceFunc.NotHaveElem[T, S])(v, list...) 65 | } 66 | 67 | // SliceNotHaveElemBy validates the input slice must not contain certain values using custom function 68 | func SliceNotHaveElemBy[T any, S ~[]T](v S, isExistFn func(T) bool) SingleValidator { 69 | return call2[S]("not_have_elem_by", sliceType, "CheckFunction", sliceFunc.NotHaveElemBy[T, S])( 70 | v, isExistFn) 71 | } 72 | 73 | // SliceElemIn validates the input slice must contain items in the specified values 74 | func SliceElemIn[T comparable, S ~[]T](v S, list ...T) SingleValidator { 75 | return call2N[S]("elem_in", sliceType, "TargetValue", sliceFunc.ElemIn[T, S])(v, list...) 76 | } 77 | 78 | // SliceElemNotIn validates the input slice must contain items not in the specified values 79 | func SliceElemNotIn[T comparable, S ~[]T](v S, list ...T) SingleValidator { 80 | return call2N[S]("elem_not_in", sliceType, "TargetValue", sliceFunc.ElemNotIn[T, S])(v, list...) 81 | } 82 | 83 | // SliceElemRange validates the input slice must contain items in the specified range 84 | func SliceElemRange[T base.Number | base.String, S ~[]T](v S, min, max T) SingleValidator { 85 | return call3[S]("elem_range", sliceType, "Min", "Max", sliceFunc.ElemRange[T, S])(v, min, max) 86 | } 87 | -------------------------------------------------------------------------------- /validator_slice_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_SliceLen(t *testing.T) { 11 | errs := SliceLen([]int{1, 2}, 2, 10).Validate(ctxBg) 12 | assert.Equal(t, 0, len(errs)) 13 | 14 | errs = SliceLen([]int{1, 2}, 3, 10).Validate(ctxBg) 15 | assert.Equal(t, "len", errs[0].Type()) 16 | } 17 | 18 | func Test_SliceUnique(t *testing.T) { 19 | errs := SliceUnique([]int{1, 2, 3}).Validate(ctxBg) 20 | assert.Equal(t, 0, len(errs)) 21 | 22 | errs = SliceUnique([]int{1, 2, 3, 1}).Validate(ctxBg) 23 | assert.Equal(t, "unique", errs[0].Type()) 24 | } 25 | 26 | func Test_SliceUniqueBy(t *testing.T) { 27 | errs := SliceUniqueBy([]any{1, 2, 3}, func(v any) int { return v.(int) }).Validate(ctxBg) 28 | assert.Equal(t, 0, len(errs)) 29 | 30 | errs = SliceUniqueBy([]any{1, 2, 3, 1}, func(v any) int { return v.(int) }).Validate(ctxBg) 31 | assert.Equal(t, "unique", errs[0].Type()) 32 | } 33 | 34 | func Test_SliceSorted(t *testing.T) { 35 | errs := SliceSorted([]int{1, 2, 3}).Validate(ctxBg) 36 | assert.Equal(t, 0, len(errs)) 37 | 38 | errs = SliceSorted([]int{1, 3, 2}).Validate(ctxBg) 39 | assert.Equal(t, "sorted", errs[0].Type()) 40 | } 41 | 42 | func Test_SliceSortedBy(t *testing.T) { 43 | errs := SliceSortedBy([]any{1, 2, 3}, func(v any) int { return v.(int) }).Validate(ctxBg) 44 | assert.Equal(t, 0, len(errs)) 45 | 46 | errs = SliceSortedBy([]any{1, 3, 2}, func(v any) int { return v.(int) }).Validate(ctxBg) 47 | assert.Equal(t, "sorted", errs[0].Type()) 48 | } 49 | 50 | func Test_SliceSortedDesc(t *testing.T) { 51 | errs := SliceSortedDesc([]int{3, 2, 1}).Validate(ctxBg) 52 | assert.Equal(t, 0, len(errs)) 53 | 54 | errs = SliceSortedDesc([]int{3, 2, 3}).Validate(ctxBg) 55 | assert.Equal(t, "sorted_desc", errs[0].Type()) 56 | } 57 | 58 | func Test_SliceSortedDescBy(t *testing.T) { 59 | errs := SliceSortedDescBy([]any{3, 2, 1}, func(v any) int { return v.(int) }).Validate(ctxBg) 60 | assert.Equal(t, 0, len(errs)) 61 | 62 | errs = SliceSortedDescBy([]any{3, 2, 3}, func(v any) int { return v.(int) }).Validate(ctxBg) 63 | assert.Equal(t, "sorted_desc", errs[0].Type()) 64 | } 65 | 66 | func Test_SliceHasElem(t *testing.T) { 67 | errs := SliceHasElem([]int{3, 2, 1}, 1, 2, 3).Validate(ctxBg) 68 | assert.Equal(t, 0, len(errs)) 69 | 70 | errs = SliceHasElem([]int{3, 2, 1}, 1, 2, 4).Validate(ctxBg) 71 | assert.Equal(t, "has_elem", errs[0].Type()) 72 | } 73 | 74 | func Test_SliceHasElemBy(t *testing.T) { 75 | errs := SliceHasElemBy([]any{3, 2, 1}, func(v any) bool { return v == 1 || v == 2 || v == 3 }).Validate(ctxBg) 76 | assert.Equal(t, 0, len(errs)) 77 | 78 | errs = SliceHasElemBy([]any{3, 2, 1}, func(v any) bool { return v == 3 || v == 2 }).Validate(ctxBg) 79 | assert.Equal(t, "has_elem_by", errs[0].Type()) 80 | } 81 | 82 | func Test_SliceNotHaveElem(t *testing.T) { 83 | errs := SliceNotHaveElem([]int{3, 2, 1}, 0, 4, 5).Validate(ctxBg) 84 | assert.Equal(t, 0, len(errs)) 85 | 86 | errs = SliceNotHaveElem([]int{3, 2, 1}, 4, 5, 2).Validate(ctxBg) 87 | assert.Equal(t, "not_have_elem", errs[0].Type()) 88 | } 89 | 90 | func Test_SliceNotHaveElemBy(t *testing.T) { 91 | errs := SliceNotHaveElemBy([]any{3, 2, 1}, func(v any) bool { return v == 4 || v == 5 }).Validate(ctxBg) 92 | assert.Equal(t, 0, len(errs)) 93 | 94 | errs = SliceNotHaveElemBy([]any{3, 2, 1}, func(v any) bool { return v == 1 || v == 2 }).Validate(ctxBg) 95 | assert.Equal(t, "not_have_elem_by", errs[0].Type()) 96 | } 97 | 98 | func Test_SliceElemIn(t *testing.T) { 99 | errs := SliceElemIn([]int{3, 2, 1}, 1, 2, 3, 4, 5).Validate(ctxBg) 100 | assert.Equal(t, 0, len(errs)) 101 | 102 | errs = SliceElemIn([]int{3, 2, 1}, 1, 2).Validate(ctxBg) 103 | assert.Equal(t, "elem_in", errs[0].Type()) 104 | } 105 | 106 | func Test_SliceElemNotIn(t *testing.T) { 107 | errs := SliceElemNotIn([]int{3, 2, 1}, 4, 5, 6).Validate(ctxBg) 108 | assert.Equal(t, 0, len(errs)) 109 | 110 | errs = SliceElemNotIn([]int{3, 2, 1}, 1, 4, 5).Validate(ctxBg) 111 | assert.Equal(t, "elem_not_in", errs[0].Type()) 112 | } 113 | 114 | func Test_SliceElemRange(t *testing.T) { 115 | errs := SliceElemRange([]int{3, 2, 1}, 0, 3).Validate(ctxBg) 116 | assert.Equal(t, 0, len(errs)) 117 | 118 | errs = SliceElemRange([]int{3, 2, 1}, 1, 2).Validate(ctxBg) 119 | assert.Equal(t, "elem_range", errs[0].Type()) 120 | } 121 | 122 | func Test_SliceElemValidate(t *testing.T) { 123 | t.Run("nil/empty slice", func(t *testing.T) { 124 | // Nil slice 125 | errs := Slice([]string(nil)).ForEach(func(elem string, index int, validator ItemValidator) { 126 | validator.Validate(StrLen(&elem, 1, 10)) 127 | }).Validate(ctxBg) 128 | assert.Equal(t, 0, len(errs)) 129 | 130 | // Empty slice 131 | errs = Slice([]string{}).ForEach(func(elem string, index int, validator ItemValidator) { 132 | validator.Validate(StrLen(&elem, 1, 10)) 133 | }).Validate(ctxBg) 134 | assert.Equal(t, 0, len(errs)) 135 | }) 136 | 137 | t.Run("validate elements", func(t *testing.T) { 138 | // Validate element 139 | errs := Slice([]int{3, 2, 1}).ForEach(func(elem int, index int, validator ItemValidator) { 140 | validator.Validate( 141 | NumGTE(&elem, 1), 142 | ) 143 | }).Validate(ctxBg) 144 | assert.Equal(t, 0, len(errs)) 145 | 146 | // Validate element with errors 147 | errs = Slice([]int{3, 2, 1}).ForEach(func(elem int, index int, validator ItemValidator) { 148 | validator.Validate( 149 | NumGT(&elem, 2).OnError( 150 | SetField(fmt.Sprintf("slice[%d]", index), nil), 151 | SetCustomKey("ERR_VLD_SLICE_ELEMENT_INVALID"), 152 | ), 153 | ) 154 | }).OnError().Validate(ctxBg) 155 | assert.Equal(t, 2, len(errs)) 156 | assert.Equal(t, "gt", errs[0].Type()) 157 | assert.Equal(t, "gt", errs[1].Type()) 158 | }) 159 | 160 | t.Run("validate element with Group", func(t *testing.T) { 161 | errs := Slice([]int{3, 2, 1}).ForEach(func(elem int, index int, validator ItemValidator) { 162 | validator.Group( 163 | NumGTE(&elem, 1), 164 | NumLTE(&elem, 2), 165 | ) 166 | }).Validate(ctxBg) 167 | assert.Equal(t, 1, len(errs)) 168 | assert.Equal(t, "group", errs[0].Type()) 169 | }) 170 | 171 | t.Run("validate element with OneOf", func(t *testing.T) { 172 | errs := Slice([]int{3, 2, 1}).ForEach(func(elem int, index int, validator ItemValidator) { 173 | validator.OneOf( 174 | NumLTE(&elem, 2), 175 | NumGTE(&elem, 1), 176 | ) 177 | }).Validate(ctxBg) 178 | assert.Equal(t, 0, len(errs)) 179 | }) 180 | 181 | t.Run("validate element with ExactOneOf", func(t *testing.T) { 182 | errs := Slice([]int{3, 2, 1}).ForEach(func(elem int, index int, validator ItemValidator) { 183 | validator.ExactOneOf( 184 | NumGTE(&elem, 1), 185 | NumLTE(&elem, 2), 186 | ) 187 | }).Validate(ctxBg) 188 | assert.Equal(t, 2, len(errs)) // slice[1] and slice[2] not satisfy 189 | }) 190 | 191 | t.Run("validate element with NotOf", func(t *testing.T) { 192 | errs := Slice([]int{3, 2, 1}).ForEach(func(elem int, index int, validator ItemValidator) { 193 | validator.NotOf( 194 | NumGTE(&elem, 1), 195 | NumLTE(&elem, 2), 196 | ) 197 | }).Validate(ctxBg) 198 | assert.Equal(t, 3, len(errs)) 199 | }) 200 | } 201 | -------------------------------------------------------------------------------- /validator_str_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/tiendc/gofn" 9 | ) 10 | 11 | func Test_StrLen(t *testing.T) { 12 | errs := StrLen(gofn.ToPtr("chào"), 2, 10).Validate(ctxBg) 13 | assert.Equal(t, 0, len(errs)) 14 | 15 | errs = StrLen(gofn.ToPtr("chào"), 1, 3).Validate(ctxBg) 16 | assert.Equal(t, "len", errs[0].Type()) 17 | } 18 | 19 | func Test_StrByteLen(t *testing.T) { 20 | errs := StrByteLen(gofn.ToPtr("ab "), 2, 10).Validate(ctxBg) 21 | assert.Equal(t, 0, len(errs)) 22 | 23 | errs = StrByteLen(gofn.ToPtr("chào"), 1, 4).Validate(ctxBg) 24 | assert.Equal(t, "byte_len", errs[0].Type()) 25 | } 26 | 27 | func Test_StrEQ(t *testing.T) { 28 | errs := StrEQ(gofn.ToPtr("abc"), "abc").Validate(ctxBg) 29 | assert.Equal(t, 0, len(errs)) 30 | 31 | errs = StrEQ(gofn.ToPtr("abc"), "aBc").Validate(ctxBg) 32 | assert.Equal(t, "eq", errs[0].Type()) 33 | } 34 | 35 | func Test_StrIn(t *testing.T) { 36 | errs := StrIn(gofn.ToPtr("chào"), "", "a", "chào").Validate(ctxBg) 37 | assert.Equal(t, 0, len(errs)) 38 | 39 | errs = StrIn(gofn.ToPtr("a"), "", "b").Validate(ctxBg) 40 | assert.Equal(t, "in", errs[0].Type()) 41 | } 42 | 43 | func Test_StrNotIn(t *testing.T) { 44 | errs := StrNotIn(gofn.ToPtr("chào"), "", "a", "b").Validate(ctxBg) 45 | assert.Equal(t, 0, len(errs)) 46 | 47 | errs = StrNotIn(gofn.ToPtr("a"), "", "a", "b").Validate(ctxBg) 48 | assert.Equal(t, "not_in", errs[0].Type()) 49 | } 50 | 51 | func Test_StrMatch(t *testing.T) { 52 | re := regexp.MustCompile("[0-9]+") 53 | errs := StrMatch(gofn.ToPtr("123"), re).Validate(ctxBg) 54 | assert.Equal(t, 0, len(errs)) 55 | 56 | errs = StrMatch(gofn.ToPtr("abc"), re).Validate(ctxBg) 57 | assert.Equal(t, "match", errs[0].Type()) 58 | } 59 | 60 | func Test_StrByteMatch(t *testing.T) { 61 | re := regexp.MustCompile("[0-9]+") 62 | errs := StrByteMatch(gofn.ToPtr("123"), re).Validate(ctxBg) 63 | assert.Equal(t, 0, len(errs)) 64 | 65 | errs = StrByteMatch(gofn.ToPtr("abc"), re).Validate(ctxBg) 66 | assert.Equal(t, "byte_match", errs[0].Type()) 67 | } 68 | 69 | // nolint: lll 70 | func Test_StrIs_Functions(t *testing.T) { 71 | assert.Equal(t, 0, len(StrIsEmail(gofn.ToPtr("test@example.com")).Validate(ctxBg))) 72 | assert.Equal(t, "is_email", StrIsEmail(gofn.ToPtr("Test@eXample")).Validate(ctxBg)[0].Type()) 73 | 74 | assert.Equal(t, 0, len(StrIsExistingEmail(gofn.ToPtr("test@example.com")).Validate(ctxBg))) 75 | assert.Equal(t, "is_existing_email", StrIsExistingEmail(gofn.ToPtr("Test@eXample")).Validate(ctxBg)[0].Type()) 76 | 77 | assert.Equal(t, 0, len(StrIsURL(gofn.ToPtr("http://host.com")).Validate(ctxBg))) 78 | assert.Equal(t, "is_url", StrIsURL(gofn.ToPtr("host")).Validate(ctxBg)[0].Type()) 79 | 80 | assert.Equal(t, 0, len(StrIsRequestURL(gofn.ToPtr("http://host.com:3000")).Validate(ctxBg))) 81 | assert.Equal(t, "is_request_url", StrIsRequestURL(gofn.ToPtr("abc3000")).Validate(ctxBg)[0].Type()) 82 | 83 | assert.Equal(t, 0, len(StrIsRequestURI(gofn.ToPtr("http://host.com:3000")).Validate(ctxBg))) 84 | assert.Equal(t, "is_request_uri", StrIsRequestURI(gofn.ToPtr("abc3000")).Validate(ctxBg)[0].Type()) 85 | 86 | assert.Equal(t, 0, len(StrIsAlpha(gofn.ToPtr("abc")).Validate(ctxBg))) 87 | assert.Equal(t, "is_alpha", StrIsAlpha(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 88 | 89 | assert.Equal(t, 0, len(StrIsUTFLetter(gofn.ToPtr("Tiến")).Validate(ctxBg))) 90 | assert.Equal(t, "is_utf_letter", StrIsUTFLetter(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 91 | 92 | assert.Equal(t, 0, len(StrIsAlphanumeric(gofn.ToPtr("abc012")).Validate(ctxBg))) 93 | assert.Equal(t, "is_alpha_numeric", StrIsAlphanumeric(gofn.ToPtr("abc-123")).Validate(ctxBg)[0].Type()) 94 | 95 | assert.Equal(t, 0, len(StrIsUTFLetterNumeric(gofn.ToPtr("abc012")).Validate(ctxBg))) 96 | assert.Equal(t, "is_utf_letter_numeric", StrIsUTFLetterNumeric(gofn.ToPtr("abc+123")).Validate(ctxBg)[0].Type()) 97 | 98 | assert.Equal(t, 0, len(StrIsNumeric(gofn.ToPtr("012")).Validate(ctxBg))) 99 | assert.Equal(t, "is_numeric", StrIsNumeric(gofn.ToPtr("-123")).Validate(ctxBg)[0].Type()) 100 | 101 | assert.Equal(t, 0, len(StrIsUTFNumeric(gofn.ToPtr("012")).Validate(ctxBg))) 102 | assert.Equal(t, "is_utf_numeric", StrIsUTFNumeric(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 103 | 104 | assert.Equal(t, 0, len(StrIsUTFDigit(gofn.ToPtr("123456")).Validate(ctxBg))) 105 | assert.Equal(t, "is_utf_digit", StrIsUTFDigit(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 106 | 107 | assert.Equal(t, 0, len(StrIsHexadecimal(gofn.ToPtr("abdcef")).Validate(ctxBg))) 108 | assert.Equal(t, "is_hexadecimal", StrIsHexadecimal(gofn.ToPtr("abdcefg")).Validate(ctxBg)[0].Type()) 109 | 110 | assert.Equal(t, 0, len(StrIsHexcolor(gofn.ToPtr("#ffffff")).Validate(ctxBg))) 111 | assert.Equal(t, "is_hexcolor", StrIsHexcolor(gofn.ToPtr("#fffffg")).Validate(ctxBg)[0].Type()) 112 | 113 | assert.Equal(t, 0, len(StrIsRGBcolor(gofn.ToPtr("rgb(1,1,1)")).Validate(ctxBg))) 114 | assert.Equal(t, "is_rgbcolor", StrIsRGBcolor(gofn.ToPtr("rgb(1,1,a)")).Validate(ctxBg)[0].Type()) 115 | 116 | assert.Equal(t, 0, len(StrIsLowerCase(gofn.ToPtr("abc123")).Validate(ctxBg))) 117 | assert.Equal(t, "is_lower_case", StrIsLowerCase(gofn.ToPtr("aBc123")).Validate(ctxBg)[0].Type()) 118 | 119 | assert.Equal(t, 0, len(StrIsUpperCase(gofn.ToPtr("ABC123")).Validate(ctxBg))) 120 | assert.Equal(t, "is_upper_case", StrIsUpperCase(gofn.ToPtr("aBc123")).Validate(ctxBg)[0].Type()) 121 | 122 | assert.Equal(t, 0, len(StrHasLowerCase(gofn.ToPtr("aBC123")).Validate(ctxBg))) 123 | assert.Equal(t, "has_lower_case", StrHasLowerCase(gofn.ToPtr("ABC123")).Validate(ctxBg)[0].Type()) 124 | 125 | assert.Equal(t, 0, len(StrHasUpperCase(gofn.ToPtr("aBc123")).Validate(ctxBg))) 126 | assert.Equal(t, "has_upper_case", StrHasUpperCase(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 127 | 128 | assert.Equal(t, 0, len(StrIsInt(gofn.ToPtr("-123")).Validate(ctxBg))) 129 | assert.Equal(t, "is_int", StrIsInt(gofn.ToPtr("123a")).Validate(ctxBg)[0].Type()) 130 | 131 | assert.Equal(t, 0, len(StrIsFloat(gofn.ToPtr("-123.123")).Validate(ctxBg))) 132 | assert.Equal(t, "is_float", StrIsFloat(gofn.ToPtr("123.123f")).Validate(ctxBg)[0].Type()) 133 | 134 | assert.Equal(t, 0, len(StrHasWhitespaceOnly(gofn.ToPtr(" ")).Validate(ctxBg))) 135 | assert.Equal(t, "has_whitespace_only", StrHasWhitespaceOnly(gofn.ToPtr("ab c")).Validate(ctxBg)[0].Type()) 136 | 137 | assert.Equal(t, 0, len(StrHasWhitespace(gofn.ToPtr(" b c")).Validate(ctxBg))) 138 | assert.Equal(t, "has_whitespace", StrHasWhitespace(gofn.ToPtr("abc")).Validate(ctxBg)[0].Type()) 139 | 140 | assert.Equal(t, 0, len(StrIsUUIDv3(gofn.ToPtr("2037da5d-a759-3ba8-bfeb-84519bb669c6")).Validate(ctxBg))) 141 | assert.Equal(t, "is_uuid_v3", StrIsUUIDv3(gofn.ToPtr("2037da5d-a759-3ba8-bfeb-84519bb669g6")).Validate(ctxBg)[0].Type()) 142 | 143 | assert.Equal(t, 0, len(StrIsUUIDv4(gofn.ToPtr("fb3e2e7c-e478-4d76-aa84-9880d6eb67f4")).Validate(ctxBg))) 144 | assert.Equal(t, "is_uuid_v4", StrIsUUIDv4(gofn.ToPtr("fb3e2e7c-e478-4d76-aa84-9880d6eb67g4")).Validate(ctxBg)[0].Type()) 145 | 146 | assert.Equal(t, 0, len(StrIsUUIDv5(gofn.ToPtr("aa43954a-ced3-5b84-9931-d3516b2e1867")).Validate(ctxBg))) 147 | assert.Equal(t, "is_uuid_v5", StrIsUUIDv5(gofn.ToPtr("aa43954a-ced3-5b84-9931-d3516b2e186g")).Validate(ctxBg)[0].Type()) 148 | 149 | assert.Equal(t, 0, len(StrIsUUID(gofn.ToPtr("2037da5d-a759-3ba8-bfeb-84519bb669c6")).Validate(ctxBg))) 150 | assert.Equal(t, "is_uuid", StrIsUUID(gofn.ToPtr("fb3e2e7c-e478-4d76-aa84-9880d6eb67g4")).Validate(ctxBg)[0].Type()) 151 | 152 | assert.Equal(t, 0, len(StrIsULID(gofn.ToPtr("01G65Z755AFWAKHE12NY0CQ9FH")).Validate(ctxBg))) 153 | assert.Equal(t, "is_ulid", StrIsULID(gofn.ToPtr("01G65Z755AFWAKHE12NY0CQ9FHx")).Validate(ctxBg)[0].Type()) 154 | 155 | assert.Equal(t, 0, len(StrIsCreditCard(gofn.ToPtr("4242-8888-8888-8888")).Validate(ctxBg))) 156 | assert.Equal(t, "is_credit_card", StrIsCreditCard(gofn.ToPtr("5242-8888-8888-8888")).Validate(ctxBg)[0].Type()) 157 | 158 | assert.Equal(t, 0, len(StrIsISBN13(gofn.ToPtr("978-3-16-148410-0")).Validate(ctxBg))) 159 | assert.Equal(t, "is_isbn13", StrIsISBN13(gofn.ToPtr("0-306-40615-2")).Validate(ctxBg)[0].Type()) 160 | 161 | assert.Equal(t, 0, len(StrIsISBN10(gofn.ToPtr("0-306-40615-2")).Validate(ctxBg))) 162 | assert.Equal(t, "is_isbn10", StrIsISBN10(gofn.ToPtr("978-3-16-148410-0")).Validate(ctxBg)[0].Type()) 163 | 164 | assert.Equal(t, 0, len(StrIsISBN(gofn.ToPtr("978-3-16-148410-0")).Validate(ctxBg))) 165 | 166 | assert.Equal(t, 0, len(StrIsJSON(gofn.ToPtr("123")).Validate(ctxBg))) 167 | assert.Equal(t, "is_json", StrIsJSON(gofn.ToPtr(`{"k":v}`)).Validate(ctxBg)[0].Type()) 168 | 169 | assert.Equal(t, 0, len(StrIsMultibyte(gofn.ToPtr("Chào buổi sáng")).Validate(ctxBg))) 170 | assert.Equal(t, "is_multibyte", StrIsMultibyte(gofn.ToPtr("abc 123")).Validate(ctxBg)[0].Type()) 171 | 172 | assert.Equal(t, 0, len(StrIsASCII(gofn.ToPtr("hello")).Validate(ctxBg))) 173 | assert.Equal(t, "is_ascii", StrIsASCII(gofn.ToPtr("hello Tiến")).Validate(ctxBg)[0].Type()) 174 | 175 | assert.Equal(t, 0, len(StrIsPrintableASCII(gofn.ToPtr("hello")).Validate(ctxBg))) 176 | assert.Equal(t, "is_printable_ascii", StrIsPrintableASCII(gofn.ToPtr("hello\t")).Validate(ctxBg)[0].Type()) 177 | 178 | assert.Equal(t, 0, len(StrIsFullWidth(gofn.ToPtr("Type or Paste")).Validate(ctxBg))) 179 | assert.Equal(t, "is_full_width", StrIsFullWidth(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 180 | 181 | assert.Equal(t, 0, len(StrIsHalfWidth(gofn.ToPtr("abc123")).Validate(ctxBg))) 182 | assert.Equal(t, "is_half_width", StrIsHalfWidth(gofn.ToPtr("Type or Paste")).Validate(ctxBg)[0].Type()) 183 | 184 | assert.Equal(t, 0, len(StrIsVariableWidth(gofn.ToPtr("Type or Paste")).Validate(ctxBg))) 185 | assert.Equal(t, "is_variable_width", StrIsVariableWidth(gofn.ToPtr("Type")).Validate(ctxBg)[0].Type()) 186 | 187 | assert.Equal(t, 0, len(StrIsBase64(gofn.ToPtr("aGVsbG8gd29ybGQ=")).Validate(ctxBg))) 188 | assert.Equal(t, "is_base64", StrIsBase64(gofn.ToPtr("hello")).Validate(ctxBg)[0].Type()) 189 | 190 | assert.Equal(t, 0, len(StrIsFilePath(gofn.ToPtr(`C:\\abc`)).Validate(ctxBg))) 191 | 192 | assert.Equal(t, 0, len(StrIsWinFilePath(gofn.ToPtr(`C:\abc`)).Validate(ctxBg))) 193 | assert.Equal(t, "is_win_file_path", StrIsWinFilePath(gofn.ToPtr("/root")).Validate(ctxBg)[0].Type()) 194 | 195 | assert.Equal(t, 0, len(StrIsUnixFilePath(gofn.ToPtr(`/root/path`)).Validate(ctxBg))) 196 | 197 | assert.Equal(t, 0, len(StrIsDataURI(gofn.ToPtr("data:image/png;base64,iVBORw0OHwAAAABJRU5ErkJggg==")).Validate(ctxBg))) 198 | assert.Equal(t, "is_data_uri", StrIsDataURI(gofn.ToPtr("example.com")).Validate(ctxBg)[0].Type()) 199 | 200 | assert.Equal(t, 0, len(StrIsMagnetURI(gofn.ToPtr("magnet:?xt=urn:xxxxxx:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&dn=bbb&tr=ccc")).Validate(ctxBg))) 201 | assert.Equal(t, "is_magnet_uri", StrIsMagnetURI(gofn.ToPtr("example.com")).Validate(ctxBg)[0].Type()) 202 | 203 | assert.Equal(t, 0, len(StrIsISO3166Alpha2(gofn.ToPtr("VN")).Validate(ctxBg))) 204 | assert.Equal(t, "is_iso3166_alpha2", StrIsISO3166Alpha2(gofn.ToPtr("VND")).Validate(ctxBg)[0].Type()) 205 | 206 | assert.Equal(t, 0, len(StrIsISO3166Alpha3(gofn.ToPtr("USA")).Validate(ctxBg))) 207 | assert.Equal(t, "is_iso3166_alpha3", StrIsISO3166Alpha3(gofn.ToPtr("USD")).Validate(ctxBg)[0].Type()) 208 | 209 | assert.Equal(t, 0, len(StrIsISO639Alpha2(gofn.ToPtr("vi")).Validate(ctxBg))) 210 | assert.Equal(t, "is_iso639_alpha2", StrIsISO639Alpha2(gofn.ToPtr("vie")).Validate(ctxBg)[0].Type()) 211 | 212 | assert.Equal(t, 0, len(StrIsISO639Alpha3b(gofn.ToPtr("vie")).Validate(ctxBg))) 213 | assert.Equal(t, "is_iso639_alpha3b", StrIsISO639Alpha3b(gofn.ToPtr("vi")).Validate(ctxBg)[0].Type()) 214 | 215 | assert.Equal(t, 0, len(StrIsDNSName(gofn.ToPtr("MX")).Validate(ctxBg))) 216 | assert.Equal(t, "is_dns_name", StrIsDNSName(gofn.ToPtr("")).Validate(ctxBg)[0].Type()) 217 | 218 | assert.Equal(t, 0, len(StrIsSHA3224(gofn.ToPtr("d9cf77bff9d00e47ad2d4841539bf72b0cfeff5e106819625e4f99f4")).Validate(ctxBg))) 219 | assert.Equal(t, "is_sha3_224", StrIsSHA3224(gofn.ToPtr("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb61c8cb4f173e3a526761dd")).Validate(ctxBg)[0].Type()) 220 | 221 | assert.Equal(t, 0, len(StrIsSHA3256(gofn.ToPtr("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8")).Validate(ctxBg))) 222 | assert.Equal(t, "is_sha3_256", StrIsSHA3256(gofn.ToPtr("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb61c8cb4f173e3a526761dd")).Validate(ctxBg)[0].Type()) 223 | 224 | assert.Equal(t, 0, len(StrIsSHA3384(gofn.ToPtr("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb61c8cb4f173e3a526761dd")).Validate(ctxBg))) 225 | assert.Equal(t, "is_sha3_384", StrIsSHA3384(gofn.ToPtr("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8")).Validate(ctxBg)[0].Type()) 226 | 227 | assert.Equal(t, 0, len(StrIsSHA3512(gofn.ToPtr("44a94f581e500c84b59bb38b990f499b4619c6774fd51e0e8534f89ee18dc3c6ea6ed096b37ff5a38517cec3ceb46bba2bd6989aef708da76fa6345810f9b1c4")).Validate(ctxBg))) 228 | assert.Equal(t, "is_sha3_512", StrIsSHA3512(gofn.ToPtr("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb61c8cb4f173e3a526761dd")).Validate(ctxBg)[0].Type()) 229 | 230 | assert.Equal(t, 0, len(StrIsSHA512(gofn.ToPtr("44a94f581e500c84b59bb38b990f499b4619c6774fd51e0e8534f89ee18dc3c6ea6ed096b37ff5a38517cec3ceb46bba2bd6989aef708da76fa6345810f9b1c4")).Validate(ctxBg))) 231 | assert.Equal(t, "is_sha512", StrIsSHA512(gofn.ToPtr("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb61c8cb4f173e3a526761dd")).Validate(ctxBg)[0].Type()) 232 | 233 | assert.Equal(t, 0, len(StrIsSHA384(gofn.ToPtr("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb61c8cb4f173e3a526761dd")).Validate(ctxBg))) 234 | assert.Equal(t, "is_sha384", StrIsSHA384(gofn.ToPtr("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8")).Validate(ctxBg)[0].Type()) 235 | 236 | assert.Equal(t, 0, len(StrIsSHA256(gofn.ToPtr("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8")).Validate(ctxBg))) 237 | assert.Equal(t, "is_sha256", StrIsSHA256(gofn.ToPtr("3d479bdfb2d870868fef4f8dd56941e741c1d9c306f5ab0e6918e5f26ee1a0237c97606efb61c8cb4f173e3a526761dd")).Validate(ctxBg)[0].Type()) 238 | 239 | assert.Equal(t, 0, len(StrIsSHA1(gofn.ToPtr("4b2b79b6f371ca18f1216461cffeaddf6848a50e")).Validate(ctxBg))) 240 | assert.Equal(t, "is_sha1", StrIsSHA1(gofn.ToPtr("19697671a75511d50bbb5382ab6f53e5799481bb27968f0af7f42ca69474aac8")).Validate(ctxBg)[0].Type()) 241 | 242 | assert.Equal(t, 0, len(StrIsTiger192(gofn.ToPtr("8d0484881b88804442e390b0784003a1981db0b31b5bf7af")).Validate(ctxBg))) 243 | assert.Equal(t, "is_tiger192", StrIsTiger192(gofn.ToPtr("8d0484881b88804442e390b0784003a1981db0b3")).Validate(ctxBg)[0].Type()) 244 | 245 | assert.Equal(t, 0, len(StrIsTiger160(gofn.ToPtr("8d0484881b88804442e390b0784003a1981db0b3")).Validate(ctxBg))) 246 | assert.Equal(t, "is_tiger160", StrIsTiger160(gofn.ToPtr("8d0484881b88804442e390b0784003a1981db0b31b5bf7af")).Validate(ctxBg)[0].Type()) 247 | 248 | assert.Equal(t, 0, len(StrIsTiger128(gofn.ToPtr("b8d02ffdd4d054b5327eed23b20e8716")).Validate(ctxBg))) 249 | assert.Equal(t, "is_tiger128", StrIsTiger128(gofn.ToPtr("8d0484881b88804442e390b0784003a1981db0b3")).Validate(ctxBg)[0].Type()) 250 | 251 | assert.Equal(t, 0, len(StrIsRipeMD160(gofn.ToPtr("2330032576b1cc1df37b92abec140368c7396745")).Validate(ctxBg))) 252 | assert.Equal(t, "is_ripemd160", StrIsRipeMD160(gofn.ToPtr("afbed790a7824ade4a3ae531285f8fe8")).Validate(ctxBg)[0].Type()) 253 | 254 | assert.Equal(t, 0, len(StrIsRipeMD128(gofn.ToPtr("afbed790a7824ade4a3ae531285f8fe8")).Validate(ctxBg))) 255 | assert.Equal(t, "is_ripemd128", StrIsRipeMD128(gofn.ToPtr("2330032576b1cc1df37b92abec140368c7396745")).Validate(ctxBg)[0].Type()) 256 | 257 | assert.Equal(t, 0, len(StrIsCRC32(gofn.ToPtr("cd71f992")).Validate(ctxBg))) 258 | assert.Equal(t, "is_crc32", StrIsCRC32(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 259 | 260 | assert.Equal(t, 0, len(StrIsCRC32b(gofn.ToPtr("94d6f306")).Validate(ctxBg))) 261 | assert.Equal(t, "is_crc32b", StrIsCRC32b(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 262 | 263 | assert.Equal(t, 0, len(StrIsMD5(gofn.ToPtr("ec02c59dee6faaca3189bace969c22d3")).Validate(ctxBg))) 264 | assert.Equal(t, "is_md5", StrIsMD5(gofn.ToPtr("abc")).Validate(ctxBg)[0].Type()) 265 | 266 | assert.Equal(t, 0, len(StrIsMD4(gofn.ToPtr("538a2a786ca1bc47694c0bc3f3ac3228")).Validate(ctxBg))) 267 | assert.Equal(t, "is_md4", StrIsMD4(gofn.ToPtr("abc")).Validate(ctxBg)[0].Type()) 268 | 269 | assert.Equal(t, 0, len(StrIsDialString(gofn.ToPtr("golang.org:3000")).Validate(ctxBg))) 270 | assert.Equal(t, "is_dial_string", StrIsDialString(gofn.ToPtr("abc")).Validate(ctxBg)[0].Type()) 271 | 272 | assert.Equal(t, 0, len(StrIsIP(gofn.ToPtr("1.1.1.1")).Validate(ctxBg))) 273 | assert.Equal(t, "is_ip", StrIsIP(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 274 | 275 | assert.Equal(t, 0, len(StrIsPort(gofn.ToPtr("1234")).Validate(ctxBg))) 276 | assert.Equal(t, "is_port", StrIsPort(gofn.ToPtr("70000")).Validate(ctxBg)[0].Type()) 277 | 278 | assert.Equal(t, 0, len(StrIsIPv4(gofn.ToPtr("1.1.1.1")).Validate(ctxBg))) 279 | assert.Equal(t, "is_ipv4", StrIsIPv4(gofn.ToPtr("FEDC:BA98:768A:0C98:FEBA:CB87:7678:1111")).Validate(ctxBg)[0].Type()) 280 | 281 | assert.Equal(t, 0, len(StrIsIPv6(gofn.ToPtr("FEDC:BA98:768A:0C98:FEBA:CB87:7678:1111")).Validate(ctxBg))) 282 | assert.Equal(t, "is_ipv6", StrIsIPv6(gofn.ToPtr("1.1.1.1")).Validate(ctxBg)[0].Type()) 283 | 284 | assert.Equal(t, 0, len(StrIsCIDR(gofn.ToPtr("255.255.255.0/32")).Validate(ctxBg))) 285 | assert.Equal(t, "is_cidr", StrIsCIDR(gofn.ToPtr("255.255.255.255")).Validate(ctxBg)[0].Type()) 286 | 287 | assert.Equal(t, 0, len(StrIsMAC(gofn.ToPtr("01-80-C2-FF-FF-FF")).Validate(ctxBg))) 288 | assert.Equal(t, "is_mac", StrIsMAC(gofn.ToPtr("1.1.1.1")).Validate(ctxBg)[0].Type()) 289 | 290 | assert.Equal(t, 0, len(StrIsHost(gofn.ToPtr("example.com")).Validate(ctxBg))) 291 | assert.Equal(t, "is_host", StrIsHost(gofn.ToPtr("abc/123")).Validate(ctxBg)[0].Type()) 292 | 293 | assert.Equal(t, 0, len(StrIsMongoID(gofn.ToPtr("507f1f77bcf86cd799439011")).Validate(ctxBg))) 294 | assert.Equal(t, "is_mongo_id", StrIsMongoID(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 295 | 296 | assert.Equal(t, 0, len(StrIsLatitude(gofn.ToPtr("38.8951")).Validate(ctxBg))) 297 | assert.Equal(t, "is_latitude", StrIsLatitude(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 298 | 299 | assert.Equal(t, 0, len(StrIsLongitude(gofn.ToPtr("-77.0364")).Validate(ctxBg))) 300 | assert.Equal(t, "is_longitude", StrIsLongitude(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 301 | 302 | assert.Equal(t, 0, len(StrIsIMEI(gofn.ToPtr("11222222333333")).Validate(ctxBg))) 303 | assert.Equal(t, "is_imei", StrIsIMEI(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 304 | 305 | assert.Equal(t, 0, len(StrIsIMSI(gofn.ToPtr("313460000000001")).Validate(ctxBg))) 306 | assert.Equal(t, "is_imsi", StrIsIMSI(gofn.ToPtr("11222222333333")).Validate(ctxBg)[0].Type()) 307 | 308 | assert.Equal(t, 0, len(StrIsRsaPublicKey(gofn.ToPtr("MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ1oCVw+tgIf52KApencj1hHW/KtvqwfnmQsFCmb4IhywfFAPbJ5qx1jX1HPDb+v/yMXzGbvlcE2kFzjYFy/LUsCAwEAAQ=="), 512).Validate(ctxBg))) 309 | assert.Equal(t, "is_rsa_public_key", StrIsRsaPublicKey(gofn.ToPtr("MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ1oCVw+tgIf52KApencj1hHW/KtvqwfnmQsFCmb4IhywfFAPbJ5qx1jX1HPDb+v/yMXzGbvlcE2kFzjYFy/LUsCAwEAAQ=="), 1024).Validate(ctxBg)[0].Type()) 310 | 311 | assert.Equal(t, 0, len(StrIsRegex(gofn.ToPtr("[0-9a-z]+")).Validate(ctxBg))) 312 | 313 | assert.Equal(t, 0, len(StrIsSSN(gofn.ToPtr("111-22-3333")).Validate(ctxBg))) 314 | assert.Equal(t, "is_ssn", StrIsSSN(gofn.ToPtr("111.22.3333")).Validate(ctxBg)[0].Type()) 315 | 316 | assert.Equal(t, 0, len(StrIsSemver(gofn.ToPtr("1.2.3")).Validate(ctxBg))) 317 | assert.Equal(t, "is_semver", StrIsSemver(gofn.ToPtr("1.2")).Validate(ctxBg)[0].Type()) 318 | 319 | assert.Equal(t, 0, len(StrIsTime(gofn.ToPtr("2023-10-01"), "2006-02-01").Validate(ctxBg))) 320 | assert.Equal(t, "is_time", StrIsTime(gofn.ToPtr("2023/10/01"), "2006-02-01").Validate(ctxBg)[0].Type()) 321 | 322 | assert.Equal(t, 0, len(StrIsUnixTime(gofn.ToPtr("123456789")).Validate(ctxBg))) 323 | assert.Equal(t, "is_unix_time", StrIsUnixTime(gofn.ToPtr("12345678912345678912345")).Validate(ctxBg)[0].Type()) 324 | 325 | assert.Equal(t, 0, len(StrIsRFC3339(gofn.ToPtr("2020-12-09T16:09:53-00:00")).Validate(ctxBg))) 326 | assert.Equal(t, "is_rfc3339", StrIsRFC3339(gofn.ToPtr("2020-12-09 16:09:53+00:00")).Validate(ctxBg)[0].Type()) 327 | 328 | assert.Equal(t, 0, len(StrIsRFC3339WithoutZone(gofn.ToPtr("2020-12-09T16:09:53")).Validate(ctxBg))) 329 | assert.Equal(t, "is_rfc3339_without_zone", StrIsRFC3339WithoutZone(gofn.ToPtr("2020-12-09T16:09:53-00:00")).Validate(ctxBg)[0].Type()) 330 | 331 | assert.Equal(t, 0, len(StrIsISO4217(gofn.ToPtr("USD")).Validate(ctxBg))) 332 | assert.Equal(t, "is_iso4217", StrIsISO4217(gofn.ToPtr("usd")).Validate(ctxBg)[0].Type()) 333 | 334 | assert.Equal(t, 0, len(StrIsE164(gofn.ToPtr("+442071838750")).Validate(ctxBg))) 335 | assert.Equal(t, "is_e164", StrIsE164(gofn.ToPtr("abc123")).Validate(ctxBg)[0].Type()) 336 | } 337 | -------------------------------------------------------------------------------- /validator_time.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tiendc/go-validator/base" 7 | timeFunc "github.com/tiendc/go-validator/base/time" 8 | ) 9 | 10 | const ( 11 | timeType = "time" 12 | ) 13 | 14 | // TimeEQ validates the input time must equal to the specified value 15 | func TimeEQ[T base.Time](v T, val time.Time) SingleValidator { 16 | return call2[T]("eq", timeType, "TargetValue", timeFunc.EQ[T])(v, val) 17 | } 18 | 19 | // TimeGT validates the input time must be greater than the specified value 20 | func TimeGT[T base.Time](v T, min time.Time) SingleValidator { 21 | return call2[T]("gt", timeType, "Min", timeFunc.GT[T])(v, min) 22 | } 23 | 24 | // TimeGTE validates the input time must be greater than or equal to the specified value 25 | func TimeGTE[T base.Time](v T, min time.Time) SingleValidator { 26 | return call2[T]("gte", timeType, "Min", timeFunc.GTE[T])(v, min) 27 | } 28 | 29 | // TimeLT validates the input time must be less than the specified value 30 | func TimeLT[T base.Time](v T, max time.Time) SingleValidator { 31 | return call2[T]("lt", timeType, "Max", timeFunc.LT[T])(v, max) 32 | } 33 | 34 | // TimeLTE validates the input time must be less than or equal to the specified value 35 | func TimeLTE[T base.Time](v T, max time.Time) SingleValidator { 36 | return call2[T]("lte", timeType, "Max", timeFunc.LTE[T])(v, max) 37 | } 38 | 39 | // TimeValid validates the input time must be not zero value 40 | func TimeValid[T base.Time](v T) SingleValidator { 41 | return call1[T]("valid", timeType, timeFunc.Valid[T])(v) 42 | } 43 | 44 | // TimeRange validates the input time must be in the specified range 45 | func TimeRange[T base.Time](v T, min, max time.Time) SingleValidator { 46 | return call3[T]("range", timeType, "Min", "Max", timeFunc.Range[T])(v, min, max) 47 | } 48 | 49 | // TimeIn validates the input time must be in the specified values 50 | func TimeIn[T base.Time](v T, s ...time.Time) SingleValidator { 51 | return call2N[T]("in", timeType, "TargetValue", timeFunc.In[T])(v, s...) 52 | } 53 | 54 | // TimeNotIn validates the input time must be not in the specified values 55 | func TimeNotIn[T base.Time](v T, s ...time.Time) SingleValidator { 56 | return call2N[T]("not_in", timeType, "TargetValue", timeFunc.NotIn[T])(v, s...) 57 | } 58 | -------------------------------------------------------------------------------- /validator_time_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | // nolint: unparam 11 | func parse(s, layout string) time.Time { 12 | t, e := time.Parse(layout, s) 13 | if e != nil { 14 | panic(e) 15 | } 16 | return t 17 | } 18 | 19 | func Test_EQ(t *testing.T) { 20 | l1 := time.DateTime 21 | errs := TimeEQ(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 22 | assert.Equal(t, 0, len(errs)) 23 | 24 | errs = TimeEQ(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)).Validate(ctxBg) 25 | assert.Equal(t, "eq", errs[0].Type()) 26 | } 27 | 28 | func Test_TimeGT_TimeGTE(t *testing.T) { 29 | l1 := time.DateTime 30 | errs := TimeGT(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:09", l1)).Validate(ctxBg) 31 | assert.Equal(t, 0, len(errs)) 32 | errs = TimeGTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 33 | assert.Equal(t, 0, len(errs)) 34 | 35 | errs = TimeGT(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 36 | assert.Equal(t, "gt", errs[0].Type()) 37 | errs = TimeGTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)).Validate(ctxBg) 38 | assert.Equal(t, "gte", errs[0].Type()) 39 | } 40 | 41 | func Test_TimeLT_TimeLTE(t *testing.T) { 42 | l1 := time.DateTime 43 | errs := TimeLT(parse("2023-10-01 10:10:09", l1), parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 44 | assert.Equal(t, 0, len(errs)) 45 | errs = TimeLTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 46 | assert.Equal(t, 0, len(errs)) 47 | 48 | errs = TimeLT(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 49 | assert.Equal(t, "lt", errs[0].Type()) 50 | errs = TimeLTE(parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:09", l1)).Validate(ctxBg) 51 | assert.Equal(t, "lte", errs[0].Type()) 52 | } 53 | 54 | func Test_TimeValid(t *testing.T) { 55 | l1 := time.DateTime 56 | errs := TimeValid(parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 57 | assert.Equal(t, 0, len(errs)) 58 | 59 | errs = TimeValid(time.Time{}).Validate(ctxBg) 60 | assert.Equal(t, "valid", errs[0].Type()) 61 | } 62 | 63 | func Test_TimeRange(t *testing.T) { 64 | l1 := time.DateTime 65 | errs := TimeRange(parse("2023-10-01 10:10:10", l1), 66 | parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)).Validate(ctxBg) 67 | assert.Equal(t, 0, len(errs)) 68 | 69 | errs = TimeRange(parse("2023-10-01 10:10:10", l1), 70 | parse("2023-10-01 10:10:11", l1), parse("2023-10-01 10:10:12", l1)).Validate(ctxBg) 71 | assert.Equal(t, "range", errs[0].Type()) 72 | } 73 | 74 | func Test_TimeIn(t *testing.T) { 75 | l1 := time.DateTime 76 | errs := TimeIn(parse("2023-10-01 10:10:10", l1), 77 | parse("2023-10-01 10:10:10", l1), parse("2023-10-01 10:10:11", l1)).Validate(ctxBg) 78 | assert.Equal(t, 0, len(errs)) 79 | 80 | errs = TimeIn(parse("2023-10-01 10:10:10", l1), 81 | parse("2023-10-01 10:10:11", l1), parse("2023-10-01 10:10:12", l1)).Validate(ctxBg) 82 | assert.Equal(t, "in", errs[0].Type()) 83 | } 84 | 85 | func Test_TimeNotIn(t *testing.T) { 86 | l1 := time.DateTime 87 | errs := TimeNotIn(parse("2023-10-01 10:10:10", l1), 88 | parse("2023-10-01 10:10:11", l1), parse("2023-10-01 10:10:12", l1)).Validate(ctxBg) 89 | assert.Equal(t, 0, len(errs)) 90 | 91 | errs = TimeNotIn(parse("2023-10-01 10:10:10", l1), 92 | parse("2023-10-01 10:10:11", l1), parse("2023-10-01 10:10:10", l1)).Validate(ctxBg) 93 | assert.Equal(t, "not_in", errs[0].Type()) 94 | } 95 | -------------------------------------------------------------------------------- /validator_types.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // Validator interface represents a validator object 9 | type Validator interface { 10 | Validate(context.Context) Errors 11 | OnError(...ErrorMod) Validator 12 | } 13 | 14 | // baseValidator base validator 15 | type baseValidator struct { 16 | errMods []ErrorMod 17 | } 18 | 19 | // applyErrMods applies mods on the target error 20 | func (b *baseValidator) applyErrMods(err Error) { 21 | for _, mod := range b.errMods { 22 | mod(err) 23 | } 24 | } 25 | 26 | // applyErrModsWithGrouping applies mods on the target error. 27 | // If the input has more than 1 error, this creates a `group` error and applies mods on it. 28 | func (b *baseValidator) applyErrModsWithGrouping(errs Errors) Errors { 29 | if len(b.errMods) == 0 || len(errs) == 0 { 30 | return errs 31 | } 32 | if len(errs) > 1 { 33 | errs = []Error{errorBuild("group", "", nil, errs)} 34 | } 35 | b.applyErrMods(errs[0]) 36 | return errs 37 | } 38 | 39 | // SingleValidator interface represents a validator that performs a single validation 40 | type SingleValidator interface { 41 | Validator 42 | } 43 | 44 | // NewSingleValidator creates a new SingleValidator 45 | func NewSingleValidator(execFn func(ctx context.Context) Error) SingleValidator { 46 | return &singleValidator{ 47 | execFn: execFn, 48 | } 49 | } 50 | 51 | // singleValidator implementation of SingleValidator interface 52 | type singleValidator struct { 53 | baseValidator 54 | execFn func(ctx context.Context) Error 55 | } 56 | 57 | // OnError implementation of Validator interface 58 | func (v *singleValidator) OnError(mods ...ErrorMod) Validator { 59 | v.errMods = mods 60 | return v 61 | } 62 | 63 | // Validate executes the validator 64 | func (v *singleValidator) Validate(ctx context.Context) Errors { 65 | err := v.execFn(ctx) 66 | if err == nil { 67 | return nil 68 | } 69 | v.applyErrMods(err) 70 | return []Error{err} 71 | } 72 | 73 | // CondValidator interface represents a validator that performs multiple validations based on 74 | // specified conditions. 75 | type CondValidator interface { 76 | Validator 77 | ValidateWithCond(context.Context) (bool, Errors) 78 | } 79 | 80 | // SingleCondValidator validator that accepts only one condition 81 | type SingleCondValidator interface { 82 | CondValidator 83 | Then(validators ...Validator) SingleCondValidator 84 | Else(validators ...Validator) SingleCondValidator 85 | } 86 | 87 | // singleCondValidator implementation of SingleCondValidator 88 | type singleCondValidator struct { 89 | baseValidator 90 | conditions []any 91 | thenValidators []Validator 92 | elseValidators []Validator 93 | } 94 | 95 | // NewSingleCondValidator creates a new SingleCondValidator 96 | func NewSingleCondValidator(conditions ...any) SingleCondValidator { 97 | return &singleCondValidator{conditions: conditions} 98 | } 99 | 100 | func (c *singleCondValidator) Then(validators ...Validator) SingleCondValidator { 101 | c.thenValidators = validators 102 | return c 103 | } 104 | 105 | func (c *singleCondValidator) Else(validators ...Validator) SingleCondValidator { 106 | c.elseValidators = validators 107 | return c 108 | } 109 | 110 | func (c *singleCondValidator) OnError(errMods ...ErrorMod) Validator { 111 | c.errMods = errMods 112 | return c 113 | } 114 | 115 | func (c *singleCondValidator) Validate(ctx context.Context) Errors { 116 | _, errs := c.ValidateWithCond(ctx) 117 | return c.applyErrModsWithGrouping(errs) 118 | } 119 | 120 | func (c *singleCondValidator) ValidateWithCond(ctx context.Context) (bool, Errors) { 121 | validators := c.thenValidators 122 | match := c.match(ctx) 123 | if !match { 124 | validators = c.elseValidators 125 | } 126 | if len(validators) == 0 { 127 | return match, nil 128 | } 129 | return match, execValidators(ctx, validators, false) 130 | } 131 | 132 | func (c *singleCondValidator) match(ctx context.Context) bool { 133 | if len(c.conditions) == 0 { 134 | return false 135 | } 136 | for _, cond := range c.conditions { 137 | boolVal, ok := cond.(bool) 138 | if ok { 139 | if !boolVal { 140 | return false 141 | } 142 | continue 143 | } 144 | validator, ok := cond.(Validator) 145 | if ok { 146 | errs := validator.Validate(ctx) 147 | if len(errs) > 0 { 148 | return false 149 | } 150 | continue 151 | } 152 | panic(fmt.Errorf("%w: only 'bool' or 'validator' allowed", ErrTypeUnsupported)) 153 | } 154 | return true 155 | } 156 | 157 | // MultiCondValidator validator that accepts multiple conditions 158 | type MultiCondValidator interface { 159 | CondValidator 160 | Default(validators ...Validator) MultiCondValidator 161 | } 162 | 163 | // multiCondValidator implementation of MultiCondValidator 164 | type multiCondValidator struct { 165 | baseValidator 166 | conditions []SingleCondValidator 167 | defaultValidators []Validator 168 | } 169 | 170 | // NewMultiCondValidator creates a new MultiCondValidator 171 | func NewMultiCondValidator(conditions ...SingleCondValidator) MultiCondValidator { 172 | return &multiCondValidator{conditions: conditions} 173 | } 174 | 175 | func (c *multiCondValidator) Default(validators ...Validator) MultiCondValidator { 176 | c.defaultValidators = validators 177 | return c 178 | } 179 | 180 | func (c *multiCondValidator) OnError(mods ...ErrorMod) Validator { 181 | c.errMods = mods 182 | return c 183 | } 184 | 185 | func (c *multiCondValidator) Validate(ctx context.Context) Errors { 186 | _, errs := c.ValidateWithCond(ctx) 187 | return c.applyErrModsWithGrouping(errs) 188 | } 189 | 190 | func (c *multiCondValidator) ValidateWithCond(ctx context.Context) (bool, Errors) { 191 | for _, v := range c.conditions { 192 | match, errs := v.ValidateWithCond(ctx) 193 | if match { 194 | return true, errs 195 | } 196 | } 197 | // No match condition, executes the default ones 198 | return false, execValidators(ctx, c.defaultValidators, false) 199 | } 200 | 201 | // ItemValidator validator to collect input validators via its Validate() func 202 | type ItemValidator interface { 203 | Validate(validators ...Validator) 204 | Group(validators ...Validator) SingleValidator 205 | OneOf(validators ...Validator) SingleValidator 206 | ExactOneOf(validators ...Validator) SingleValidator 207 | NotOf(validators ...Validator) SingleValidator 208 | } 209 | 210 | // itemValidator implements ItemValidator interface 211 | type itemValidator struct { 212 | ItemValidator 213 | validators []Validator 214 | } 215 | 216 | func (iv *itemValidator) Validate(validators ...Validator) { 217 | iv.validators = append(iv.validators, validators...) 218 | } 219 | 220 | func (iv *itemValidator) Group(validators ...Validator) SingleValidator { 221 | group := Group(validators...) 222 | iv.validators = append(iv.validators, group) 223 | return group 224 | } 225 | 226 | func (iv *itemValidator) OneOf(validators ...Validator) SingleValidator { 227 | oneOf := OneOf(validators...) 228 | iv.validators = append(iv.validators, oneOf) 229 | return oneOf 230 | } 231 | 232 | func (iv *itemValidator) ExactOneOf(validators ...Validator) SingleValidator { 233 | exactOneOf := ExactOneOf(validators...) 234 | iv.validators = append(iv.validators, exactOneOf) 235 | return exactOneOf 236 | } 237 | 238 | func (iv *itemValidator) NotOf(validators ...Validator) SingleValidator { 239 | notOf := NotOf(validators...) 240 | iv.validators = append(iv.validators, notOf) 241 | return notOf 242 | } 243 | 244 | func (iv *itemValidator) get() []Validator { 245 | return iv.validators 246 | } 247 | 248 | // SliceContentValidator validator that validates slice elements 249 | type SliceContentValidator[T any, S ~[]T] interface { 250 | Validator 251 | ForEach(fn func(element T, index int, elemValidator ItemValidator)) SliceContentValidator[T, S] 252 | } 253 | 254 | // sliceContentValidator implementation of SliceContentValidator 255 | type sliceContentValidator[T any, S ~[]T] struct { 256 | baseValidator 257 | slice S 258 | elemValidatorFunc func(T, int, ItemValidator) 259 | } 260 | 261 | // NewSliceContentValidator creates a new SliceContentValidator 262 | func NewSliceContentValidator[T any, S ~[]T](slice S) SliceContentValidator[T, S] { 263 | return &sliceContentValidator[T, S]{slice: slice} 264 | } 265 | 266 | func (c *sliceContentValidator[T, S]) ForEach(fn func(T, int, ItemValidator)) SliceContentValidator[T, S] { 267 | c.elemValidatorFunc = fn 268 | return c 269 | } 270 | 271 | func (c *sliceContentValidator[T, S]) OnError(errMods ...ErrorMod) Validator { 272 | c.errMods = errMods 273 | return c 274 | } 275 | 276 | func (c *sliceContentValidator[T, S]) Validate(ctx context.Context) Errors { 277 | if len(c.slice) == 0 { 278 | return nil 279 | } 280 | elemValidator := &itemValidator{} 281 | for i, elem := range c.slice { 282 | c.elemValidatorFunc(elem, i, elemValidator) 283 | } 284 | errs := execValidators(ctx, elemValidator.get(), false) 285 | return c.applyErrModsWithGrouping(errs) 286 | } 287 | 288 | // MapContentValidator validator that validates map entries 289 | type MapContentValidator[K comparable, V any, M ~map[K]V] interface { 290 | Validator 291 | ForEach(fn func(k K, v V, entryValidator ItemValidator)) MapContentValidator[K, V, M] 292 | } 293 | 294 | // mapContentValidator implementation of MapContentValidator 295 | type mapContentValidator[K comparable, V any, M ~map[K]V] struct { 296 | baseValidator 297 | mapObj M 298 | entryValidatorFunc func(K, V, ItemValidator) 299 | } 300 | 301 | // NewMapContentValidator creates a new MapContentValidator 302 | func NewMapContentValidator[K comparable, V any, M ~map[K]V](mapObj M) MapContentValidator[K, V, M] { 303 | return &mapContentValidator[K, V, M]{mapObj: mapObj} 304 | } 305 | 306 | func (m *mapContentValidator[K, V, M]) ForEach(fn func(K, V, ItemValidator)) MapContentValidator[K, V, M] { 307 | m.entryValidatorFunc = fn 308 | return m 309 | } 310 | 311 | func (m *mapContentValidator[K, V, M]) OnError(errMods ...ErrorMod) Validator { 312 | m.errMods = errMods 313 | return m 314 | } 315 | 316 | func (m *mapContentValidator[K, V, M]) Validate(ctx context.Context) Errors { 317 | if len(m.mapObj) == 0 { 318 | return nil 319 | } 320 | entryValidator := &itemValidator{} 321 | for k, v := range m.mapObj { 322 | m.entryValidatorFunc(k, v, entryValidator) 323 | } 324 | errs := execValidators(ctx, entryValidator.get(), false) 325 | return m.applyErrModsWithGrouping(errs) 326 | } 327 | -------------------------------------------------------------------------------- /validator_utils.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tiendc/go-validator/base" 7 | ) 8 | 9 | // call1 generics function to execute a validation function with 1 param 10 | func call1[P any]( 11 | errType, valueType string, 12 | validateFunc func(P) (bool, []base.ErrorParam), 13 | ) func(v P) SingleValidator { 14 | return func(v P) SingleValidator { 15 | return NewSingleValidator(func(ctx context.Context) Error { 16 | success, params := validateFunc(v) 17 | if success { 18 | return nil 19 | } 20 | return errorBuild(errType, valueType, v, nil, params...) 21 | }) 22 | } 23 | } 24 | 25 | // call2 generics function to execute a validation function with 2 params 26 | func call2[P1 any, P2 any]( 27 | errType, valueType string, p2Name string, 28 | validateFunc func(P1, P2) (bool, []base.ErrorParam), 29 | ) func(P1, P2) SingleValidator { 30 | return func(v P1, p2 P2) SingleValidator { 31 | return NewSingleValidator(func(ctx context.Context) Error { 32 | success, params := validateFunc(v, p2) 33 | if success { 34 | return nil 35 | } 36 | params = append(params, base.ErrorParam{Key: p2Name, Value: p2}) 37 | return errorBuild(errType, valueType, v, nil, params...) 38 | }) 39 | } 40 | } 41 | 42 | // call2N generics function to execute a validation function with 1 fixed param and a variable param 43 | func call2N[P1 any, P2 any]( 44 | errType, valueType string, p2Name string, 45 | validateFunc func(P1, ...P2) (bool, []base.ErrorParam), 46 | ) func(P1, ...P2) SingleValidator { 47 | return func(v P1, p2s ...P2) SingleValidator { 48 | return NewSingleValidator(func(ctx context.Context) Error { 49 | success, params := validateFunc(v, p2s...) 50 | if success { 51 | return nil 52 | } 53 | params = append(params, base.ErrorParam{Key: p2Name, Value: p2s}) 54 | return errorBuild(errType, valueType, v, nil, params...) 55 | }) 56 | } 57 | } 58 | 59 | // call3 generics function to execute a validation function with 3 params 60 | func call3[P1 any, P2 any, P3 any]( 61 | errType, valueType string, p2Name, p3Name string, 62 | validateFunc func(P1, P2, P3) (bool, []base.ErrorParam), 63 | ) func(P1, P2, P3) SingleValidator { 64 | return func(v P1, p2 P2, p3 P3) SingleValidator { 65 | return NewSingleValidator(func(ctx context.Context) Error { 66 | success, params := validateFunc(v, p2, p3) 67 | if success { 68 | return nil 69 | } 70 | params = append(params, base.ErrorParam{Key: p2Name, Value: p2}, 71 | base.ErrorParam{Key: p3Name, Value: p3}) 72 | return errorBuild(errType, valueType, v, nil, params...) 73 | }) 74 | } 75 | } 76 | 77 | // ptrCall1 generics function to execute a validation function with 1 pointer param 78 | func ptrCall1[P any]( 79 | errType, valueType string, 80 | validateFunc func(P) (bool, []base.ErrorParam), 81 | ) func(v *P) SingleValidator { 82 | return func(v *P) SingleValidator { 83 | return NewSingleValidator(func(ctx context.Context) Error { 84 | if v == nil { 85 | return nil 86 | } 87 | success, params := validateFunc(*v) 88 | if success { 89 | return nil 90 | } 91 | return errorBuild(errType, valueType, *v, nil, params...) 92 | }) 93 | } 94 | } 95 | 96 | // ptrCall2 generics function to execute a validation function with 2 pointer params 97 | func ptrCall2[P1 any, P2 any]( 98 | errType, valueType string, p2Name string, 99 | validateFunc func(P1, P2) (bool, []base.ErrorParam), 100 | ) func(*P1, P2) SingleValidator { 101 | return func(v *P1, p2 P2) SingleValidator { 102 | return NewSingleValidator(func(ctx context.Context) Error { 103 | if v == nil { 104 | return nil 105 | } 106 | success, params := validateFunc(*v, p2) 107 | if success { 108 | return nil 109 | } 110 | params = append(params, base.ErrorParam{Key: p2Name, Value: p2}) 111 | return errorBuild(errType, valueType, *v, nil, params...) 112 | }) 113 | } 114 | } 115 | 116 | // ptrCall2N generics function to execute a validation function with 1 fixed param and a variable param 117 | func ptrCall2N[P1 any, P2 any]( 118 | errType, valueType string, p2Name string, 119 | validateFunc func(P1, ...P2) (bool, []base.ErrorParam), 120 | ) func(*P1, ...P2) SingleValidator { 121 | return func(v *P1, p2s ...P2) SingleValidator { 122 | return NewSingleValidator(func(ctx context.Context) Error { 123 | if v == nil { 124 | return nil 125 | } 126 | success, params := validateFunc(*v, p2s...) 127 | if success { 128 | return nil 129 | } 130 | params = append(params, base.ErrorParam{Key: p2Name, Value: p2s}) 131 | return errorBuild(errType, valueType, *v, nil, params...) 132 | }) 133 | } 134 | } 135 | 136 | // ptrCall3 generics function to execute a validation function with 3 pointer params 137 | func ptrCall3[P1 any, P2 any, P3 any]( 138 | errType, valueType string, p2Name, p3Name string, 139 | validateFunc func(P1, P2, P3) (bool, []base.ErrorParam), 140 | ) func(*P1, P2, P3) SingleValidator { 141 | return func(v *P1, p2 P2, p3 P3) SingleValidator { 142 | return NewSingleValidator(func(ctx context.Context) Error { 143 | if v == nil { 144 | return nil 145 | } 146 | success, params := validateFunc(*v, p2, p3) 147 | if success { 148 | return nil 149 | } 150 | params = append(params, base.ErrorParam{Key: p2Name, Value: p2}, 151 | base.ErrorParam{Key: p3Name, Value: p3}) 152 | return errorBuild(errType, valueType, *v, nil, params...) 153 | }) 154 | } 155 | } 156 | --------------------------------------------------------------------------------