├── .github ├── FUNDING.yml └── workflows │ ├── standard-go-test.yml │ └── standard-stale.yml ├── .gitignore ├── .gometalinter.json ├── LICENSE ├── LICENSE.txt ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── validate.go ├── validate_test.go └── validators ├── bytes_are_present.go ├── bytes_are_present_test.go ├── common.go ├── common_test.go ├── email_is_present.go ├── email_is_present_test.go ├── func_validator.go ├── func_validator_test.go ├── int_array_is_present.go ├── int_array_is_present_test.go ├── int_is_greater_than.go ├── int_is_greater_than_test.go ├── int_is_less_than.go ├── int_is_less_than_test.go ├── int_is_present.go ├── int_is_present_test.go ├── ints_are_equal.go ├── ints_are_equal_test.go ├── ints_are_not_equal.go ├── ints_are_not_equal_test.go ├── regex_match.go ├── regex_match_test.go ├── string_inclusion.go ├── string_inclusion_test.go ├── string_is_present.go ├── string_is_present_test.go ├── string_length_in_range.go ├── string_length_in_range_test.go ├── strings_match.go ├── strings_match_test.go ├── time_after_time.go ├── time_after_time_test.go ├── time_is_before_time.go ├── time_is_before_time_test.go ├── time_is_present.go ├── time_is_present_test.go ├── url_is_present.go ├── url_is_present_test.go ├── uuid_is_present.go └── uuid_is_present_test.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: markbates 4 | patreon: buffalo 5 | -------------------------------------------------------------------------------- /.github/workflows/standard-go-test.yml: -------------------------------------------------------------------------------- 1 | name: Standard Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | call-standard-test: 10 | name: Test 11 | uses: gobuffalo/.github/.github/workflows/go-test.yml@v1 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /.github/workflows/standard-stale.yml: -------------------------------------------------------------------------------- 1 | name: Standard Autocloser 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | call-standard-autocloser: 9 | name: Autocloser 10 | uses: gobuffalo/.github/.github/workflows/stale.yml@v1 11 | secrets: inherit 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | cover.out 3 | .DS_Store 4 | doc 5 | tmp 6 | pkg 7 | *.gem 8 | *.pid 9 | coverage 10 | coverage.data 11 | build/* 12 | *.pbxuser 13 | *.mode1v3 14 | .svn 15 | profile 16 | .console_history 17 | .sass-cache/* 18 | .rake_tasks~ 19 | *.log.lck 20 | solr/ 21 | .jhw-cache/ 22 | jhw.* 23 | *.sublime* 24 | node_modules/ 25 | dist/ 26 | generated/ 27 | .vendor/ 28 | bin/* 29 | gin-bin 30 | .idea/ 31 | -------------------------------------------------------------------------------- /.gometalinter.json: -------------------------------------------------------------------------------- 1 | { 2 | "Enable": ["vet", "golint", "goimports", "deadcode", "gotype", "ineffassign", "misspell", "nakedret", "unconvert", "megacheck", "varcheck"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Mark Bates 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 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Mark Bates 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test -failfast -short -cover ./... 3 | go mod tidy -v 4 | 5 | cov: 6 | go test -short -coverprofile cover.out ./... 7 | go tool cover -html cover.out 8 | go mod tidy -v 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github.com/gobuffalo/validate 2 | [![Build Status](https://travis-ci.org/gobuffalo/validate.svg?branch=master)](https://travis-ci.org/gobuffalo/validate) [![Actions Status](https://github.com/gobuffalo/validate/workflows/Tests/badge.svg)](https://github.com/gobuffalo/validate/actions) [![GoDoc](https://godoc.org/github.com/gobuffalo/validate?status.svg)](https://godoc.org/github.com/gobuffalo/validate) 3 | 4 | This package provides a framework for writing validations for Go applications. It does provide you with few validators, but if you need others you can easly build them. 5 | 6 | ## Installation 7 | 8 | ```bash 9 | $ go get github.com/gobuffalo/validate 10 | ``` 11 | 12 | ## Usage 13 | 14 | Using validate is pretty easy, just define some `Validator` objects and away you go. 15 | 16 | Here is a pretty simple example: 17 | 18 | ```go 19 | package main 20 | 21 | import ( 22 | "log" 23 | 24 | v "github.com/gobuffalo/validate" 25 | ) 26 | 27 | type User struct { 28 | Name string 29 | Email string 30 | } 31 | 32 | func (u *User) IsValid(errors *v.Errors) { 33 | if u.Name == "" { 34 | errors.Add("name", "Name must not be blank!") 35 | } 36 | if u.Email == "" { 37 | errors.Add("email", "Email must not be blank!") 38 | } 39 | } 40 | 41 | func main() { 42 | u := User{Name: "", Email: ""} 43 | errors := v.Validate(&u) 44 | log.Println(errors.Errors) 45 | // map[name:[Name must not be blank!] email:[Email must not be blank!]] 46 | } 47 | ``` 48 | 49 | In the previous example I wrote a single `Validator` for the `User` struct. To really get the benefit of using go-validator, as well as the Go language, I would recommend creating distinct validators for each thing you want to validate, that way they can be run concurrently. 50 | 51 | ```go 52 | package main 53 | 54 | import ( 55 | "fmt" 56 | "log" 57 | "strings" 58 | 59 | v "github.com/gobuffalo/validate" 60 | ) 61 | 62 | type User struct { 63 | Name string 64 | Email string 65 | } 66 | 67 | type PresenceValidator struct { 68 | Field string 69 | Value string 70 | } 71 | 72 | func (v *PresenceValidator) IsValid(errors *v.Errors) { 73 | if v.Value == "" { 74 | errors.Add(strings.ToLower(v.Field), fmt.Sprintf("%s must not be blank!", v.Field)) 75 | } 76 | } 77 | 78 | func main() { 79 | u := User{Name: "", Email: ""} 80 | errors := v.Validate(&PresenceValidator{"Email", u.Email}, &PresenceValidator{"Name", u.Name}) 81 | log.Println(errors.Errors) 82 | // map[name:[Name must not be blank!] email:[Email must not be blank!]] 83 | } 84 | ``` 85 | 86 | That's really it. Pretty simple and straight-forward Just a nice clean framework for writing your own validators. Use in good health. 87 | 88 | ## Built-in Validators 89 | 90 | To make it even simpler, this package has a children package with some nice built-in validators. 91 | 92 | ```go 93 | package main 94 | 95 | import ( 96 | "log" 97 | 98 | "github.com/gobuffalo/validate" 99 | "github.com/gobuffalo/validate/validators" 100 | ) 101 | 102 | type User struct { 103 | Name string 104 | Email string 105 | } 106 | 107 | 108 | func main() { 109 | u := User{Name: "", Email: ""} 110 | errors := validate.Validate( 111 | &validators.EmailIsPresent{Name: "Email", Field: u.Email, Message: "Mail is not in the right format."}, 112 | &validators.StringIsPresent{Field: u.Name, Name: "Name"}, 113 | ) 114 | log.Println(errors.Errors) 115 | // map[name:[Name can not be blank.] email:[Mail is not in the right format.]] 116 | } 117 | ``` 118 | 119 | All fields are required for each validators, except Message (every validator has a default error message). 120 | 121 | ### Available Validators 122 | 123 | A full list of available validators can be found at [https://pkg.go.dev/github.com/gobuffalo/validate/v3/validators](https://pkg.go.dev/github.com/gobuffalo/validate/v3/validators). 124 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gobuffalo/validate/v3 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gobuffalo/flect v0.3.0 7 | github.com/gofrs/uuid v4.3.0+incompatible 8 | github.com/stretchr/testify v1.8.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk= 5 | github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= 6 | github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc= 7 | github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 11 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 12 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 14 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /validate.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | // Errors holds onto all of the error messages 11 | // that get generated during the validation process. 12 | type Errors struct { 13 | Errors map[string][]string `json:"errors" xml:"errors"` 14 | Lock *sync.RWMutex `json:"-"` 15 | } 16 | 17 | func (e Errors) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { 18 | start.Name = xml.Name{Local: "errors"} 19 | tokens := []xml.Token{start} 20 | 21 | for name, messages := range e.Errors { 22 | outer := xml.StartElement{Name: xml.Name{Local: name}} 23 | 24 | tks := []xml.Token{outer} 25 | for _, m := range messages { 26 | t := xml.StartElement{Name: xml.Name{Local: "message"}} 27 | tks = append(tks, t, xml.CharData(m), xml.EndElement{Name: xml.Name{Local: "message"}}) 28 | } 29 | 30 | tokens = append(tokens, tks...) 31 | tokens = append(tokens, xml.EndElement{Name: outer.Name}) 32 | } 33 | 34 | tokens = append(tokens, xml.EndElement{Name: start.Name}) 35 | 36 | for _, t := range tokens { 37 | err := enc.EncodeToken(t) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | 43 | // flush to ensure tokens are written 44 | return enc.Flush() 45 | } 46 | 47 | // Validator must be implemented in order to pass the 48 | // validator object into the Validate function. 49 | type Validator interface { 50 | IsValid(errors *Errors) 51 | } 52 | 53 | type vfWrapper struct { 54 | vf func(errors *Errors) 55 | } 56 | 57 | func (v vfWrapper) IsValid(errors *Errors) { 58 | v.vf(errors) 59 | } 60 | 61 | // ValidatorFunc wraps any function in a "Validator" to make 62 | // it easy to write custom ones. 63 | func ValidatorFunc(fn func(errors *Errors)) Validator { 64 | return vfWrapper{fn} 65 | } 66 | 67 | // NewErrors returns a pointer to a Errors 68 | // object that has been primed and ready to go. 69 | func NewErrors() *Errors { 70 | return &Errors{ 71 | Errors: make(map[string][]string), 72 | Lock: new(sync.RWMutex), 73 | } 74 | } 75 | 76 | // Error implements the error interface 77 | func (v *Errors) Error() string { 78 | errs := []string{} 79 | for _, v := range v.Errors { 80 | errs = append(errs, v...) 81 | } 82 | return strings.Join(errs, "\n") 83 | } 84 | 85 | // Count returns the number of errors. 86 | func (v *Errors) Count() int { 87 | return len(v.Errors) 88 | } 89 | 90 | // HasAny returns true/false depending on whether any errors 91 | // have been tracked. 92 | func (v *Errors) HasAny() bool { 93 | if v == nil { 94 | return false 95 | } 96 | return v.Count() > 0 97 | } 98 | 99 | // Append concatenates two Errors objects together. 100 | // This will modify the first object in place. 101 | func (v *Errors) Append(ers *Errors) { 102 | for key, value := range ers.Errors { 103 | for _, msg := range value { 104 | v.Add(key, msg) 105 | } 106 | } 107 | } 108 | 109 | // Add will add a new message to the list of errors using 110 | // the given key. If the key already exists the message will 111 | // be appended to the array of the existing messages. 112 | func (v *Errors) Add(key string, msg string) { 113 | v.Lock.Lock() 114 | v.Errors[key] = append(v.Errors[key], msg) 115 | v.Lock.Unlock() 116 | } 117 | 118 | // Get returns an array of error messages for the given key. 119 | func (v *Errors) Get(key string) []string { 120 | return v.Errors[key] 121 | } 122 | 123 | func (v *Errors) String() string { 124 | b, _ := json.Marshal(v) 125 | return string(b) 126 | } 127 | 128 | // Keys return all field names which have error 129 | func (v *Errors) Keys() []string { 130 | keys := []string{} 131 | for key := range v.Errors { 132 | keys = append(keys, key) 133 | } 134 | 135 | return keys 136 | } 137 | 138 | // Validate takes in n number of Validator objects and will run 139 | // them and return back a point to a Errors object that 140 | // will contain any errors. 141 | func Validate(validators ...Validator) *Errors { 142 | errors := NewErrors() 143 | 144 | wg := &sync.WaitGroup{} 145 | for i, _ := range validators { 146 | wg.Add(1) 147 | go func(wg *sync.WaitGroup, i int) { 148 | defer wg.Done() 149 | validator := validators[i] 150 | validator.IsValid(errors) 151 | }(wg, i) 152 | } 153 | wg.Wait() 154 | 155 | return errors 156 | } 157 | -------------------------------------------------------------------------------- /validate_test.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | type v1 struct{} 11 | 12 | func (v *v1) IsValid(errors *Errors) { 13 | errors.Add("v1", "there's an error with v1") 14 | } 15 | 16 | type v2 struct{} 17 | 18 | func (v *v2) IsValid(errors *Errors) { 19 | errors.Add("v2", "there's an error with v2") 20 | } 21 | 22 | func TestValidate(t *testing.T) { 23 | r := require.New(t) 24 | 25 | errors := Validate(&v1{}, &v2{}) 26 | r.Equal(errors.Count(), 2) 27 | r.Equal(errors.HasAny(), true) 28 | r.Equal(errors.Errors["v1"], []string{"there's an error with v1"}) 29 | r.Equal(errors.Errors["v2"], []string{"there's an error with v2"}) 30 | 31 | r.Equal(errors.String(), `{"errors":{"v1":["there's an error with v1"],"v2":["there's an error with v2"]}}`) 32 | } 33 | 34 | func TestErrorsKeys(t *testing.T) { 35 | r := require.New(t) 36 | errors := Validate(&v1{}, &v2{}) 37 | r.Contains(errors.Keys(), "v1") 38 | r.Contains(errors.Keys(), "v2") 39 | } 40 | 41 | func Test_ErrorsXML(t *testing.T) { 42 | r := require.New(t) 43 | 44 | errors := Errors{ 45 | Errors: map[string][]string{ 46 | "name": []string{"name1", "name2"}, 47 | "email": []string{"emailA", "emailB"}, 48 | }, 49 | } 50 | 51 | x, err := xml.Marshal(errors) 52 | r.NoError(err) 53 | r.Contains(string(x), "") 54 | r.Contains(string(x), "emailA") 55 | } 56 | -------------------------------------------------------------------------------- /validators/bytes_are_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | ) 8 | 9 | type BytesArePresent struct { 10 | Name string 11 | Field []byte 12 | Message string 13 | } 14 | 15 | // IsValid adds an error if the field is not empty. 16 | func (v *BytesArePresent) IsValid(errors *validate.Errors) { 17 | if len(v.Field) > 0 { 18 | return 19 | } 20 | 21 | if len(v.Message) > 0 { 22 | errors.Add(GenerateKey(v.Name), v.Message) 23 | return 24 | } 25 | 26 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s can not be blank.", v.Name)) 27 | } 28 | -------------------------------------------------------------------------------- /validators/bytes_are_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_BytesArePresent(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := BytesArePresent{Name: "Name", Field: []byte("Mark")} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = BytesArePresent{Name: "Name", Field: []byte("")} 19 | v.IsValid(errors) 20 | r.Equal(errors.Count(), 1) 21 | r.Equal(errors.Get("name"), []string{"Name can not be blank."}) 22 | 23 | errors = validate.NewErrors() 24 | v = BytesArePresent{Name: "Name", Field: []byte(""), Message: "Field can't be blank."} 25 | v.IsValid(errors) 26 | r.Equal(errors.Count(), 1) 27 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 28 | 29 | errors = validate.NewErrors() 30 | v = BytesArePresent{"Name", []byte(""), "Field can't be blank."} 31 | v.IsValid(errors) 32 | r.Equal(errors.Count(), 1) 33 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 34 | } 35 | -------------------------------------------------------------------------------- /validators/common.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/gobuffalo/flect" 5 | ) 6 | 7 | var CustomKeys = map[string]string{} 8 | 9 | func GenerateKey(s string) string { 10 | key := CustomKeys[s] 11 | if key != "" { 12 | return key 13 | } 14 | return flect.Underscore(s) 15 | } 16 | -------------------------------------------------------------------------------- /validators/common_test.go: -------------------------------------------------------------------------------- 1 | package validators_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3/validators" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_GenerateKey(t *testing.T) { 11 | r := require.New(t) 12 | 13 | r.Equal("foo", validators.GenerateKey("Foo")) 14 | r.Equal("created_at", validators.GenerateKey("CreatedAt")) 15 | r.Equal("created_at", validators.GenerateKey("Created At")) 16 | r.Equal("person_id", validators.GenerateKey("PersonID")) 17 | r.Equal("content_type", validators.GenerateKey("Content-Type")) 18 | 19 | validators.CustomKeys["ODGroupIDs"] = "od_group_ids" 20 | r.Equal("od_group_ids", validators.GenerateKey("ODGroupIDs")) 21 | } 22 | -------------------------------------------------------------------------------- /validators/email_is_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/gobuffalo/validate/v3" 9 | ) 10 | 11 | var rxEmail *regexp.Regexp 12 | 13 | func init() { 14 | rxEmail = regexp.MustCompile("^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$") 15 | } 16 | 17 | type EmailIsPresent struct { 18 | Name string 19 | Field string 20 | Message string 21 | } 22 | 23 | // IsValid performs the validation based on the email regexp match. 24 | func (v *EmailIsPresent) IsValid(errors *validate.Errors) { 25 | if !rxEmail.Match([]byte(v.Field)) { 26 | if v.Message == "" { 27 | v.Message = fmt.Sprintf("%s does not match the email format.", v.Name) 28 | } 29 | errors.Add(GenerateKey(v.Name), v.Message) 30 | } 31 | } 32 | 33 | // EmailLike checks that email has two parts (username and domain separated by @) 34 | // Also it check that domain have domain zone (don`t check that zone is valid) 35 | type EmailLike struct { 36 | Name string 37 | Field string 38 | Message string 39 | } 40 | 41 | // IsValid performs the validation based on email struct (username@domain) 42 | func (v *EmailLike) IsValid(errors *validate.Errors) { 43 | parts := strings.Split(v.Field, "@") 44 | if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 { 45 | if v.Message == "" { 46 | v.Message = fmt.Sprintf("%s does not match the email format.", v.Name) 47 | } 48 | errors.Add(GenerateKey(v.Name), v.Message) 49 | } else if len(parts) == 2 { 50 | domain := parts[1] 51 | // Check that domain is valid 52 | if len(strings.Split(domain, ".")) < 2 { 53 | if v.Message == "" { 54 | v.Message = fmt.Sprintf("%s does not match the email format (email domain).", v.Name) 55 | } 56 | errors.Add(GenerateKey(v.Name), v.Message) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /validators/email_is_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_EmailIsPresent(t *testing.T) { 11 | r := require.New(t) 12 | 13 | var tests = []struct { 14 | email string 15 | valid bool 16 | }{ 17 | {"", false}, 18 | {"foo@bar.com", true}, 19 | {"x@x.x", true}, 20 | {"foo@bar.com.au", true}, 21 | {"foo+bar@bar.com", true}, 22 | {"foo@bar.coffee", true}, 23 | {"foo@bar.中文网", true}, 24 | {"invalidemail@", false}, 25 | {"invalid.com", false}, 26 | {"@invalid.com", false}, 27 | {"test|123@m端ller.com", true}, 28 | {"hans@m端ller.com", true}, 29 | {"hans.m端ller@test.com", true}, 30 | {"NathAn.daVIeS@DomaIn.cOM", true}, 31 | {"NATHAN.DAVIES@DOMAIN.CO.UK", true}, 32 | } 33 | for _, test := range tests { 34 | v := EmailIsPresent{Name: "email", Field: test.email} 35 | errors := validate.NewErrors() 36 | v.IsValid(errors) 37 | r.Equal(test.valid, !errors.HasAny()) 38 | if !test.valid { 39 | r.Equal(errors.Get("email"), []string{"email does not match the email format."}) 40 | } 41 | } 42 | v := EmailIsPresent{Name: "email", Field: "", Message: "Email don't match the right format."} 43 | errors := validate.NewErrors() 44 | v.IsValid(errors) 45 | r.Equal(errors.Count(), 1) 46 | r.Equal(errors.Get("email"), []string{"Email don't match the right format."}) 47 | 48 | } 49 | 50 | func Test_EmailLike(t *testing.T) { 51 | r := require.New(t) 52 | 53 | var tests = []struct { 54 | email string 55 | valid bool 56 | }{ 57 | {"", false}, 58 | {"foo@bar.com", true}, 59 | {"x@x.x", true}, 60 | {"foo@bar.com.au", true}, 61 | {"foo+bar@bar.com", true}, 62 | {"foo@bar.coffee", true}, 63 | {"foo@bar.中文网", true}, 64 | {"invalidemail@", false}, 65 | {"invalid.com", false}, 66 | {"@", false}, 67 | {"@invalid.com", false}, 68 | {"test|123@m端ller.com", true}, 69 | {"hans@m端ller.com", true}, 70 | {"hans.m端ller@test.com", true}, 71 | {"NathAn.daVIeS@DomaIn.cOM", true}, 72 | {"NATHAN.DAVIES@DOMAIN.CO.UK", true}, 73 | } 74 | for _, test := range tests { 75 | v := EmailLike{Name: "email", Field: test.email} 76 | errors := validate.NewErrors() 77 | v.IsValid(errors) 78 | r.Equal(test.valid, !errors.HasAny(), test.email) 79 | if !test.valid { 80 | r.Equal(errors.Get("email"), []string{"email does not match the email format."}) 81 | } 82 | } 83 | v := EmailLike{Name: "email", Field: "foo@bar"} 84 | errors := validate.NewErrors() 85 | v.IsValid(errors) 86 | r.Equal(errors.Count(), 1) 87 | r.Equal(errors.Get("email"), []string{"email does not match the email format (email domain)."}) 88 | v = EmailLike{Name: "email", Field: "", Message: "Email don't match the right format."} 89 | errors = validate.NewErrors() 90 | v.IsValid(errors) 91 | r.Equal(errors.Count(), 1) 92 | r.Equal(errors.Get("email"), []string{"Email don't match the right format."}) 93 | } 94 | 95 | func BenchmarkEmailIsPresent_IsValid(b *testing.B) { 96 | errors := validate.NewErrors() 97 | for i := 0; i <= b.N; i++ { 98 | v := EmailLike{Name: "email", Field: "email@gmail.com"} 99 | v.IsValid(errors) 100 | } 101 | } 102 | 103 | func BenchmarkEmailLike_IsValid(b *testing.B) { 104 | errors := validate.NewErrors() 105 | for i := 0; i <= b.N; i++ { 106 | v := EmailIsPresent{Name: "email", Field: "email@gmail.com"} 107 | v.IsValid(errors) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /validators/func_validator.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type FuncValidator struct { 11 | Fn func() bool 12 | Field string 13 | Name string 14 | Message string 15 | } 16 | 17 | func (f *FuncValidator) IsValid(verrs *validate.Errors) { 18 | // for backwards compatability 19 | if strings.TrimSpace(f.Name) == "" { 20 | f.Name = f.Field 21 | } 22 | if !f.Fn() { 23 | verrs.Add(GenerateKey(f.Name), fmt.Sprintf(f.Message, f.Field)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /validators/func_validator_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_FuncValidator(t *testing.T) { 11 | r := require.New(t) 12 | 13 | fv := &FuncValidator{ 14 | Name: "Name", 15 | Field: "Field", 16 | Message: "%s is an invalid name", 17 | Fn: func() bool { 18 | return false 19 | }, 20 | } 21 | 22 | verrs := validate.NewErrors() 23 | fv.IsValid(verrs) 24 | 25 | r.Equal([]string{"Field is an invalid name"}, verrs.Get("name")) 26 | } 27 | 28 | func Test_FuncValidatorNoName(t *testing.T) { 29 | r := require.New(t) 30 | 31 | fv := &FuncValidator{ 32 | Field: "Name", 33 | Message: "%s is invalid", 34 | Fn: func() bool { 35 | return false 36 | }, 37 | } 38 | 39 | verrs := validate.NewErrors() 40 | fv.IsValid(verrs) 41 | 42 | r.Equal([]string{"Name is invalid"}, verrs.Get("name")) 43 | } 44 | -------------------------------------------------------------------------------- /validators/int_array_is_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | ) 8 | 9 | type IntArrayIsPresent struct { 10 | Name string 11 | Field []int 12 | Message string 13 | } 14 | 15 | // IsValid adds an error if the field is an empty array. 16 | func (v *IntArrayIsPresent) IsValid(errors *validate.Errors) { 17 | if len(v.Field) > 0 { 18 | return 19 | } 20 | 21 | if len(v.Message) > 0 { 22 | errors.Add(GenerateKey(v.Name), v.Message) 23 | return 24 | } 25 | 26 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s can not be empty.", v.Name)) 27 | } 28 | -------------------------------------------------------------------------------- /validators/int_array_is_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_IntArrayIsPresent(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := IntArrayIsPresent{Name: "Name", Field: []int{1}} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = IntArrayIsPresent{Name: "Name", Field: []int{}} 19 | v.IsValid(errors) 20 | r.Equal(errors.Count(), 1) 21 | r.Equal(errors.Get("name"), []string{"Name can not be empty."}) 22 | 23 | errors = validate.NewErrors() 24 | v = IntArrayIsPresent{Name: "Name", Field: []int{}, Message: "Field can't be blank."} 25 | v.IsValid(errors) 26 | r.Equal(errors.Count(), 1) 27 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 28 | 29 | errors = validate.NewErrors() 30 | v = IntArrayIsPresent{"Name", []int{}, "Field can't be blank."} 31 | v.IsValid(errors) 32 | r.Equal(errors.Count(), 1) 33 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 34 | } 35 | -------------------------------------------------------------------------------- /validators/int_is_greater_than.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | ) 8 | 9 | type IntIsGreaterThan struct { 10 | Name string 11 | Field int 12 | Compared int 13 | Message string 14 | } 15 | 16 | // IsValid adds an error if the field is not greater than the compared value. 17 | func (v *IntIsGreaterThan) IsValid(errors *validate.Errors) { 18 | if v.Field > v.Compared { 19 | return 20 | } 21 | 22 | if len(v.Message) > 0 { 23 | errors.Add(GenerateKey(v.Name), v.Message) 24 | return 25 | } 26 | 27 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%d is not greater than %d.", v.Field, v.Compared)) 28 | } 29 | -------------------------------------------------------------------------------- /validators/int_is_greater_than_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_IntIsGreaterThan(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := IntIsGreaterThan{Name: "Number", Field: 2, Compared: 1} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(0, errors.Count()) 17 | 18 | v = IntIsGreaterThan{Name: "number", Field: 1, Compared: 2} 19 | v.IsValid(errors) 20 | r.Equal(1, errors.Count()) 21 | r.Equal(errors.Get("number"), []string{"1 is not greater than 2."}) 22 | 23 | v = IntIsGreaterThan{Name: "number", Field: 1, Compared: 2, Message: "number isn't greater than 2."} 24 | errors = validate.NewErrors() 25 | v.IsValid(errors) 26 | r.Equal(1, errors.Count()) 27 | r.Equal(errors.Get("number"), []string{"number isn't greater than 2."}) 28 | } 29 | -------------------------------------------------------------------------------- /validators/int_is_less_than.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | ) 8 | 9 | type IntIsLessThan struct { 10 | Name string 11 | Field int 12 | Compared int 13 | Message string 14 | } 15 | 16 | // IsValid adds an error if the field is not less than the compared value. 17 | func (v *IntIsLessThan) IsValid(errors *validate.Errors) { 18 | if v.Field < v.Compared { 19 | return 20 | } 21 | 22 | if len(v.Message) > 0 { 23 | errors.Add(GenerateKey(v.Name), v.Message) 24 | return 25 | } 26 | 27 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%d is not less than %d.", v.Field, v.Compared)) 28 | } 29 | -------------------------------------------------------------------------------- /validators/int_is_less_than_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_IntIsLessThan(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := IntIsLessThan{Name: "Number", Field: 1, Compared: 2} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = IntIsLessThan{Name: "number", Field: 1, Compared: 0} 19 | v.IsValid(errors) 20 | r.Equal(errors.Count(), 1) 21 | r.Equal(errors.Get("number"), []string{"1 is not less than 0."}) 22 | 23 | v = IntIsLessThan{Name: "number", Field: 1, Compared: 0, Message: "number is not less than 0."} 24 | errors = validate.NewErrors() 25 | v.IsValid(errors) 26 | r.Equal(errors.Count(), 1) 27 | r.Equal(errors.Get("number"), []string{"number is not less than 0."}) 28 | } 29 | -------------------------------------------------------------------------------- /validators/int_is_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | ) 8 | 9 | type IntIsPresent struct { 10 | Name string 11 | Field int 12 | Message string 13 | } 14 | 15 | // IsValid adds an error if the field equals 0. 16 | func (v *IntIsPresent) IsValid(errors *validate.Errors) { 17 | if v.Field != 0 { 18 | return 19 | } 20 | 21 | if len(v.Message) > 0 { 22 | errors.Add(GenerateKey(v.Name), v.Message) 23 | return 24 | } 25 | 26 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s can not be blank.", v.Name)) 27 | } 28 | -------------------------------------------------------------------------------- /validators/int_is_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_IntIsPresent(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := IntIsPresent{Name: "Name", Field: 1} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = IntIsPresent{Name: "Name", Field: 0} 19 | v.IsValid(errors) 20 | r.Equal(errors.Count(), 1) 21 | r.Equal(errors.Get("name"), []string{"Name can not be blank."}) 22 | 23 | errors = validate.NewErrors() 24 | v = IntIsPresent{Name: "Name", Field: 0, Message: "Field can't be blank."} 25 | v.IsValid(errors) 26 | r.Equal(errors.Count(), 1) 27 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 28 | 29 | errors = validate.NewErrors() 30 | v = IntIsPresent{"Name", 0, "Field can't be blank."} 31 | v.IsValid(errors) 32 | r.Equal(errors.Count(), 1) 33 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 34 | } 35 | -------------------------------------------------------------------------------- /validators/ints_are_equal.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | ) 8 | 9 | // IntsAreEqual is a validator that will compare two integers and add 10 | // an error if they are not equal 11 | type IntsAreEqual struct { 12 | ValueOne int 13 | ValueTwo int 14 | Name string 15 | Message string 16 | } 17 | 18 | func (v *IntsAreEqual) IsValid(errors *validate.Errors) { 19 | if v.ValueOne != v.ValueTwo { 20 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%d is not equal to %d", v.ValueOne, v.ValueTwo)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validators/ints_are_equal_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_IntsAreEqual(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := IntsAreEqual{Name: "Number", ValueOne: 1, ValueTwo: 1} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = IntsAreEqual{Name: "Number", ValueOne: 1, ValueTwo: 2} 19 | errors = validate.NewErrors() 20 | v.IsValid(errors) 21 | r.Equal(errors.Count(), 1) 22 | r.Equal(errors.Get("number"), []string{"1 is not equal to 2"}) 23 | } 24 | -------------------------------------------------------------------------------- /validators/ints_are_not_equal.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | ) 8 | 9 | // IntsAreNotEqual is a validator that compares two integers and will add 10 | // an error if they are equal 11 | type IntsAreNotEqual struct { 12 | ValueOne int 13 | ValueTwo int 14 | Name string 15 | Message string 16 | } 17 | 18 | func (v *IntsAreNotEqual) IsValid(errors *validate.Errors) { 19 | if v.ValueOne == v.ValueTwo { 20 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%d is equal to %d", v.ValueOne, v.ValueTwo)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validators/ints_are_not_equal_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_IntsAreNotEqual(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := IntsAreNotEqual{Name: "Number", ValueOne: 2, ValueTwo: 1} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = IntsAreNotEqual{Name: "Number", ValueOne: 2, ValueTwo: 2} 19 | errors = validate.NewErrors() 20 | v.IsValid(errors) 21 | r.Equal(errors.Count(), 1) 22 | r.Equal(errors.Get("number"), []string{"2 is equal to 2"}) 23 | } 24 | -------------------------------------------------------------------------------- /validators/regex_match.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | // RegexMatch specifies the properties needed by the validation. 11 | type RegexMatch struct { 12 | Name string 13 | Field string 14 | Expr string 15 | Message string 16 | } 17 | 18 | // IsValid performs the validation based on the regexp match. 19 | func (v *RegexMatch) IsValid(errors *validate.Errors) { 20 | r := regexp.MustCompile(v.Expr) 21 | if r.Match([]byte(v.Field)) { 22 | return 23 | } 24 | 25 | if len(v.Message) > 0 { 26 | errors.Add(GenerateKey(v.Name), v.Message) 27 | return 28 | } 29 | 30 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s does not match the expected format.", v.Name)) 31 | } 32 | -------------------------------------------------------------------------------- /validators/regex_match_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_RegexMatch(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := RegexMatch{Name: "Phone", Field: "555-555-5555", Expr: "^([0-9]{3}-[0-9]{3}-[0-9]{4})$"} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = RegexMatch{Name: "Phone", Field: "123-ab1-1424", Expr: "^([0-9]{3}-[0-9]{3}-[0-9]{4})$"} 19 | v.IsValid(errors) 20 | r.Equal(errors.Count(), 1) 21 | r.Equal(errors.Get("phone"), []string{"Phone does not match the expected format."}) 22 | 23 | errors = validate.NewErrors() 24 | v = RegexMatch{Name: "Phone", Field: "123-ab1-1424", Expr: "^([0-9]{3}-[0-9]{3}-[0-9]{4})$", Message: "Phone number does not match the expected format."} 25 | v.IsValid(errors) 26 | r.Equal(errors.Count(), 1) 27 | r.Equal(errors.Get("phone"), []string{"Phone number does not match the expected format."}) 28 | 29 | errors = validate.NewErrors() 30 | v = RegexMatch{"Phone", "123-ab1-1424", "^([0-9]{3}-[0-9]{3}-[0-9]{4})$", "Phone number does not match the expected format."} 31 | v.IsValid(errors) 32 | r.Equal(errors.Count(), 1) 33 | r.Equal(errors.Get("phone"), []string{"Phone number does not match the expected format."}) 34 | } 35 | -------------------------------------------------------------------------------- /validators/string_inclusion.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type StringInclusion struct { 11 | Name string 12 | Field string 13 | List []string 14 | Message string 15 | } 16 | 17 | // IsValid adds an error if the field is not one of the allowed values. 18 | func (v *StringInclusion) IsValid(errors *validate.Errors) { 19 | found := false 20 | for _, l := range v.List { 21 | if l == v.Field { 22 | found = true 23 | break 24 | } 25 | } 26 | if !found { 27 | if len(v.Message) > 0 { 28 | errors.Add(GenerateKey(v.Name), v.Message) 29 | return 30 | } 31 | 32 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s is not in the list [%s].", v.Name, strings.Join(v.List, ", "))) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /validators/string_inclusion_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_StringInclusion(t *testing.T) { 11 | r := require.New(t) 12 | 13 | l := []string{"Mark", "Bates"} 14 | 15 | v := StringInclusion{Name: "Name", Field: "Mark", List: l} 16 | errors := validate.NewErrors() 17 | v.IsValid(errors) 18 | r.Equal(errors.Count(), 0) 19 | 20 | v = StringInclusion{Name: "Name", Field: "Foo", List: l} 21 | v.IsValid(errors) 22 | r.Equal(errors.Count(), 1) 23 | r.Equal(errors.Get("name"), []string{"Name is not in the list [Mark, Bates]."}) 24 | 25 | errors = validate.NewErrors() 26 | v = StringInclusion{Name: "Name", Field: "Foo", Message: "Name is not in the list (Mark, Bates).", List: l} 27 | v.IsValid(errors) 28 | r.Equal(errors.Count(), 1) 29 | r.Equal(errors.Get("name"), []string{"Name is not in the list (Mark, Bates)."}) 30 | 31 | errors = validate.NewErrors() 32 | v = StringInclusion{"Name", "Foo", l, "Name is not in the list (Mark, Bates)."} 33 | v.IsValid(errors) 34 | r.Equal(errors.Count(), 1) 35 | r.Equal(errors.Get("name"), []string{"Name is not in the list (Mark, Bates)."}) 36 | } 37 | -------------------------------------------------------------------------------- /validators/string_is_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type StringIsPresent struct { 11 | Name string 12 | Field string 13 | Message string 14 | } 15 | 16 | // IsValid adds an error if the field is empty. 17 | func (v *StringIsPresent) IsValid(errors *validate.Errors) { 18 | if strings.TrimSpace(v.Field) != "" { 19 | return 20 | } 21 | 22 | if len(v.Message) > 0 { 23 | errors.Add(GenerateKey(v.Name), v.Message) 24 | return 25 | } 26 | 27 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s can not be blank.", v.Name)) 28 | } 29 | -------------------------------------------------------------------------------- /validators/string_is_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_StringIsPresent(t *testing.T) { 11 | r := require.New(t) 12 | 13 | v := StringIsPresent{Name: "Name", Field: "Mark"} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(errors.Count(), 0) 17 | 18 | v = StringIsPresent{Name: "Name", Field: ""} 19 | v.IsValid(errors) 20 | r.Equal(errors.Count(), 1) 21 | r.Equal(errors.Get("name"), []string{"Name can not be blank."}) 22 | 23 | errors = validate.NewErrors() 24 | v = StringIsPresent{Name: "Name", Field: "", Message: "Field can't be blank."} 25 | v.IsValid(errors) 26 | r.Equal(errors.Count(), 1) 27 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 28 | 29 | errors = validate.NewErrors() 30 | v = StringIsPresent{"Name", "", "Field can't be blank."} 31 | v.IsValid(errors) 32 | r.Equal(errors.Count(), 1) 33 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 34 | } 35 | -------------------------------------------------------------------------------- /validators/string_length_in_range.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type StringLengthInRange struct { 11 | Name string 12 | Field string 13 | Min int 14 | Max int 15 | Message string 16 | } 17 | 18 | // IsValid checks that string in range of min:max 19 | // if max not present or it equal to 0 it will be equal to string length 20 | func (v *StringLengthInRange) IsValid(errors *validate.Errors) { 21 | strLength := utf8.RuneCountInString(v.Field) 22 | if v.Max == 0 { 23 | v.Max = strLength 24 | } 25 | if v.Message == "" { 26 | v.Message = fmt.Sprintf("%s not in range(%d, %d)", v.Name, v.Min, v.Max) 27 | } 28 | if !(strLength >= v.Min && strLength <= v.Max) { 29 | errors.Add(GenerateKey(v.Name), v.Message) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /validators/string_length_in_range_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_StringLengthInRange(t *testing.T) { 12 | r := require.New(t) 13 | var tests = []struct { 14 | value string 15 | min int 16 | max int 17 | expected bool 18 | }{ 19 | {"123456", 0, 100, true}, 20 | {"1239999", 0, 0, true}, 21 | {"1239asdfasf99", 100, 200, false}, 22 | {"1239999asdff29", 10, 30, true}, 23 | {"あいうえお", 0, 5, true}, 24 | {"あいうえおか", 0, 5, false}, 25 | {"あいうえお", 0, 0, true}, 26 | {"あいうえ", 5, 10, false}, 27 | } 28 | 29 | for _, test := range tests { 30 | v := StringLengthInRange{Name: "email", Field: test.value, Min: test.min, Max: test.max} 31 | errors := validate.NewErrors() 32 | v.IsValid(errors) 33 | r.Equal(test.expected, !errors.HasAny(), fmt.Sprintf("Value: %s, Min:%d, Max:%d", test.value, test.min, test.max)) 34 | } 35 | v := StringLengthInRange{Name: "email", Field: "1234567", Min: 40, Max: 50, Message: "Value length not between 40 and 50."} 36 | errors := validate.NewErrors() 37 | v.IsValid(errors) 38 | r.Equal(errors.Count(), 1) 39 | r.Equal(errors.Get("email"), []string{"Value length not between 40 and 50."}) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /validators/strings_match.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type StringsMatch struct { 11 | Name string 12 | Field string 13 | Field2 string 14 | Message string 15 | } 16 | 17 | // IsValid performs the validation equality of two strings. 18 | func (v *StringsMatch) IsValid(errors *validate.Errors) { 19 | if strings.TrimSpace(v.Field) != strings.TrimSpace(v.Field2) { 20 | if v.Message == "" { 21 | v.Message = fmt.Sprintf("%s does not equal %s.", v.Field, v.Field2) 22 | } 23 | errors.Add(GenerateKey(v.Name), v.Message) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /validators/strings_match_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_StringsMatch_IsValid(t *testing.T) { 11 | r := require.New(t) 12 | var cases = []struct { 13 | str1 string 14 | str2 string 15 | expected bool 16 | }{ 17 | {"test", "test", true}, 18 | {"test_fail", "test_true", false}, 19 | {"test with space", " test with space ", true}, 20 | {" test with space second", " test with space second ", true}, 21 | } 22 | 23 | for _, test_case := range cases { 24 | v := StringsMatch{Name: "strings", Field: test_case.str1, Field2: test_case.str2} 25 | errors := validate.NewErrors() 26 | v.IsValid(errors) 27 | r.Equal(test_case.expected, !errors.HasAny(), "Str1: %s, Str2: %s", test_case.str1, test_case.str2) 28 | } 29 | 30 | v := StringsMatch{Name: "strings", Field: "test_fail", Field2: "test", Message: "String doesn't match."} 31 | errors := validate.NewErrors() 32 | v.IsValid(errors) 33 | r.Equal(errors.Count(), 1) 34 | r.Equal(errors.Get("strings"), []string{"String doesn't match."}) 35 | 36 | } 37 | 38 | func BenchmarkStringsMatch_IsValid_Valid(b *testing.B) { 39 | errors := validate.NewErrors() 40 | for i := 0; i <= b.N; i++ { 41 | v := StringsMatch{Name: "strings", Field: " Some string ", Field2: " Some string "} 42 | v.IsValid(errors) 43 | } 44 | } 45 | 46 | func BenchmarkStringsMatch_IsValid_InValid(b *testing.B) { 47 | errors := validate.NewErrors() 48 | for i := 0; i <= b.N; i++ { 49 | v := StringsMatch{Name: "strings", Field: " Some string ", Field2: " Some string failure"} 50 | v.IsValid(errors) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /validators/time_after_time.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type TimeAfterTime struct { 11 | FirstName string 12 | FirstTime time.Time 13 | SecondName string 14 | SecondTime time.Time 15 | Message string 16 | } 17 | 18 | // IsValid adds an error if the FirstTime is not after the SecondTime. 19 | func (v *TimeAfterTime) IsValid(errors *validate.Errors) { 20 | 21 | // UnixNano wraps around to negative numbers when a time is too far 22 | // into the future (e.g. 260 years) 23 | if v.FirstTime.Year() > v.SecondTime.Year() { 24 | return 25 | } 26 | 27 | if v.FirstTime.UnixNano() >= v.SecondTime.UnixNano() { 28 | return 29 | } 30 | 31 | if len(v.Message) > 0 { 32 | errors.Add(GenerateKey(v.FirstName), v.Message) 33 | return 34 | } 35 | 36 | errors.Add(GenerateKey(v.FirstName), fmt.Sprintf("%s must be after %s.", v.FirstName, v.SecondName)) 37 | } 38 | -------------------------------------------------------------------------------- /validators/time_after_time_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_TimeAfterTime(t *testing.T) { 12 | r := require.New(t) 13 | now := time.Now() 14 | v := TimeAfterTime{ 15 | FirstName: "Opens At", FirstTime: now.Add(100000), 16 | SecondName: "Now", SecondTime: now, 17 | } 18 | 19 | errors := validate.NewErrors() 20 | v.IsValid(errors) 21 | r.Equal(0, errors.Count()) 22 | 23 | v.SecondTime = now.Add(200000) 24 | v.IsValid(errors) 25 | 26 | r.Equal(1, errors.Count()) 27 | r.Equal(errors.Get("opens_at"), []string{"Opens At must be after Now."}) 28 | 29 | errors = validate.NewErrors() 30 | v.Message = "OpensAt must be later than Now." 31 | 32 | v.IsValid(errors) 33 | 34 | r.Equal(1, errors.Count()) 35 | r.Equal(errors.Get("opens_at"), []string{"OpensAt must be later than Now."}) 36 | 37 | firstTime := now.AddDate(260, 0, 0) 38 | v = TimeAfterTime{ 39 | FirstName: "Opens At", FirstTime: firstTime, 40 | SecondName: "Now", SecondTime: now, 41 | } 42 | 43 | errors = validate.NewErrors() 44 | v.IsValid(errors) 45 | r.Equal(0, errors.Count()) 46 | } 47 | -------------------------------------------------------------------------------- /validators/time_is_before_time.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type TimeIsBeforeTime struct { 11 | FirstName string 12 | FirstTime time.Time 13 | SecondName string 14 | SecondTime time.Time 15 | Message string 16 | } 17 | 18 | // IsValid adds an error if the FirstTime is after the SecondTime. 19 | func (v *TimeIsBeforeTime) IsValid(errors *validate.Errors) { 20 | if v.FirstTime.Year() < v.SecondTime.Year() { 21 | return 22 | } 23 | if v.FirstTime.UnixNano() <= v.SecondTime.UnixNano() { 24 | return 25 | } 26 | 27 | if len(v.Message) > 0 { 28 | errors.Add(GenerateKey(v.FirstName), v.Message) 29 | return 30 | } 31 | 32 | errors.Add(GenerateKey(v.FirstName), fmt.Sprintf("%s must be before %s.", v.FirstName, v.SecondName)) 33 | } 34 | -------------------------------------------------------------------------------- /validators/time_is_before_time_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_TimeIsBeforeTime(t *testing.T) { 12 | r := require.New(t) 13 | now := time.Now() 14 | v := TimeIsBeforeTime{ 15 | FirstName: "Opens At", FirstTime: now, 16 | SecondName: "Closes At", SecondTime: now.Add(100000), 17 | } 18 | 19 | errors := validate.NewErrors() 20 | v.IsValid(errors) 21 | r.Equal(0, errors.Count()) 22 | 23 | v.SecondTime = now.Add(-100000) 24 | v.IsValid(errors) 25 | 26 | r.Equal(1, errors.Count()) 27 | r.Equal(errors.Get("opens_at"), []string{"Opens At must be before Closes At."}) 28 | 29 | errors = validate.NewErrors() 30 | v.Message = "OpensAt must be earlier than ClosesAt." 31 | 32 | v.IsValid(errors) 33 | 34 | r.Equal(1, errors.Count()) 35 | r.Equal(errors.Get("opens_at"), []string{"OpensAt must be earlier than ClosesAt."}) 36 | 37 | firstTime := now.AddDate(-400, 0, 0) 38 | v = TimeIsBeforeTime{ 39 | FirstName: "Opens At", FirstTime: firstTime, 40 | SecondName: "Now", SecondTime: now, 41 | } 42 | 43 | errors = validate.NewErrors() 44 | v.IsValid(errors) 45 | r.Equal(0, errors.Count()) 46 | } 47 | -------------------------------------------------------------------------------- /validators/time_is_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type TimeIsPresent struct { 11 | Name string 12 | Field time.Time 13 | Message string 14 | } 15 | 16 | // IsValid adds an error if the field is not a valid time. 17 | func (v *TimeIsPresent) IsValid(errors *validate.Errors) { 18 | t := time.Time{} 19 | if v.Field.UnixNano() != t.UnixNano() { 20 | return 21 | } 22 | 23 | if len(v.Message) > 0 { 24 | errors.Add(GenerateKey(v.Name), v.Message) 25 | return 26 | } 27 | 28 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s can not be blank.", v.Name)) 29 | } 30 | -------------------------------------------------------------------------------- /validators/time_is_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_TimeIsPresent(t *testing.T) { 12 | r := require.New(t) 13 | v := TimeIsPresent{Name: "Created At", Field: time.Now()} 14 | errors := validate.NewErrors() 15 | v.IsValid(errors) 16 | r.Equal(0, errors.Count()) 17 | 18 | v = TimeIsPresent{Name: "Created At", Field: time.Time{}} 19 | v.IsValid(errors) 20 | r.Equal(1, errors.Count()) 21 | r.Equal(errors.Get("created_at"), []string{"Created At can not be blank."}) 22 | 23 | errors = validate.NewErrors() 24 | v = TimeIsPresent{Name: "Created At", Field: time.Time{}, Message: "Field can't be blank."} 25 | v.IsValid(errors) 26 | r.Equal(errors.Count(), 1) 27 | r.Equal(errors.Get("created_at"), []string{"Field can't be blank."}) 28 | 29 | errors = validate.NewErrors() 30 | v = TimeIsPresent{"Created At", time.Time{}, "Field can't be blank."} 31 | v.IsValid(errors) 32 | r.Equal(errors.Count(), 1) 33 | r.Equal(errors.Get("created_at"), []string{"Field can't be blank."}) 34 | } 35 | -------------------------------------------------------------------------------- /validators/url_is_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | ) 9 | 10 | type URLIsPresent struct { 11 | Name string 12 | Field string 13 | Message string 14 | } 15 | 16 | // IsValid performs the validation to check if URL is formatted correctly 17 | // uses net/url ParseRequestURI to check validity 18 | func (v *URLIsPresent) IsValid(errors *validate.Errors) { 19 | if v.Field == "http://" || v.Field == "https://" { 20 | if v.Message == "" { 21 | v.Message = fmt.Sprintf("%s url is empty", v.Name) 22 | } 23 | errors.Add(GenerateKey(v.Name), v.Message) 24 | } 25 | parsedUrl, err := url.ParseRequestURI(v.Field) 26 | if err != nil { 27 | if v.Message == "" { 28 | v.Message = fmt.Sprintf("%s does not match url format. Err: %s", v.Name, 29 | err) 30 | } 31 | errors.Add(GenerateKey(v.Name), v.Message) 32 | } else { 33 | if parsedUrl.Scheme != "" && parsedUrl.Scheme != "http" && parsedUrl.Scheme != "https" { 34 | if v.Message == "" { 35 | v.Message = fmt.Sprintf("%s invalid url scheme", v.Name) 36 | } 37 | errors.Add(GenerateKey(v.Name), v.Message) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /validators/url_is_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_URLIsPresent(t *testing.T) { 11 | r := require.New(t) 12 | 13 | var tests = []struct { 14 | url string 15 | valid bool 16 | }{ 17 | {"", false}, 18 | {"http://", false}, 19 | {"https://", false}, 20 | {"http", false}, 21 | {"google.com", false}, 22 | {"http://www.google.com", true}, 23 | {"http://google.com", true}, 24 | {"google.com", false}, 25 | {"https://www.google.cOM", true}, 26 | {"ht123tps://www.google.cOM", false}, 27 | {"https://golang.Org", true}, 28 | {"https://invalid#$%#$@.Org", false}, 29 | } 30 | for _, test := range tests { 31 | v := URLIsPresent{Name: "URL", Field: test.url} 32 | errors := validate.NewErrors() 33 | v.IsValid(errors) 34 | r.Equal(test.valid, !errors.HasAny(), test.url, errors.Error()) 35 | } 36 | v := URLIsPresent{Name: "URL", Field: "http://", Message: "URL isn't valid."} 37 | errors := validate.NewErrors() 38 | v.IsValid(errors) 39 | r.Equal(errors.Count(), 1) 40 | r.Equal(errors.Get("url"), []string{"URL isn't valid."}) 41 | } 42 | -------------------------------------------------------------------------------- /validators/uuid_is_present.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gobuffalo/validate/v3" 8 | "github.com/gofrs/uuid" 9 | ) 10 | 11 | type UUIDIsPresent struct { 12 | Name string 13 | Field uuid.UUID 14 | Message string 15 | } 16 | 17 | // IsValid adds an error if the field is not a valid uuid. 18 | func (v *UUIDIsPresent) IsValid(errors *validate.Errors) { 19 | s := v.Field.String() 20 | if strings.TrimSpace(s) != "" && v.Field != uuid.Nil { 21 | return 22 | } 23 | 24 | if len(v.Message) > 0 { 25 | errors.Add(GenerateKey(v.Name), v.Message) 26 | return 27 | } 28 | 29 | errors.Add(GenerateKey(v.Name), fmt.Sprintf("%s can not be blank.", v.Name)) 30 | } 31 | -------------------------------------------------------------------------------- /validators/uuid_is_present_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gobuffalo/validate/v3" 7 | "github.com/gofrs/uuid" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_UUIDIsPresent(t *testing.T) { 12 | r := require.New(t) 13 | 14 | id, err := uuid.NewV4() 15 | r.NoError(err) 16 | v := UUIDIsPresent{Name: "Name", Field: id} 17 | errors := validate.NewErrors() 18 | v.IsValid(errors) 19 | r.Equal(errors.Count(), 0) 20 | 21 | v = UUIDIsPresent{Name: "Name", Field: uuid.UUID{}} 22 | v.IsValid(errors) 23 | r.Equal(errors.Count(), 1) 24 | r.Equal(errors.Get("name"), []string{"Name can not be blank."}) 25 | 26 | errors = validate.NewErrors() 27 | v = UUIDIsPresent{Name: "Name", Field: uuid.UUID{}, Message: "Field can't be blank."} 28 | v.IsValid(errors) 29 | r.Equal(errors.Count(), 1) 30 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 31 | 32 | errors = validate.NewErrors() 33 | v = UUIDIsPresent{"Name", uuid.UUID{}, "Field can't be blank."} 34 | v.IsValid(errors) 35 | r.Equal(errors.Count(), 1) 36 | r.Equal(errors.Get("name"), []string{"Field can't be blank."}) 37 | } 38 | --------------------------------------------------------------------------------