Merge as a pure function on validate
This commit is contained in:
parent
c5ef533dd7
commit
96ec0691f7
5 changed files with 114 additions and 39 deletions
7
go.mod
7
go.mod
|
@ -3,3 +3,10 @@ module github.com/anthdm/gothkit
|
|||
go 1.22.0
|
||||
|
||||
require github.com/a-h/templ v0.2.707
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
9
go.sum
9
go.sum
|
@ -1,4 +1,13 @@
|
|||
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -3,8 +3,29 @@ package validate
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
emailRegex = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||
urlRegex = regexp.MustCompile(`^(http(s)?://)?([\da-z\.-]+)\.([a-z\.]{2,6})([/\w \.-]*)*/?$`)
|
||||
)
|
||||
|
||||
type RuleSet struct {
|
||||
Name string
|
||||
RuleValue any
|
||||
FieldValue any
|
||||
FieldName any
|
||||
ErrorMessage string
|
||||
MessageFunc func(RuleSet) string
|
||||
ValidateFunc func(RuleSet) bool
|
||||
}
|
||||
|
||||
func (set RuleSet) Message(msg string) RuleSet {
|
||||
set.ErrorMessage = msg
|
||||
return set
|
||||
}
|
||||
|
||||
type Numeric interface {
|
||||
int | float64
|
||||
}
|
||||
|
|
|
@ -5,57 +5,42 @@ import (
|
|||
"maps"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
emailRegex = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||
urlRegex = regexp.MustCompile(`^(http(s)?://)?([\da-z\.-]+)\.([a-z\.]{2,6})([/\w \.-]*)*/?$`)
|
||||
)
|
||||
|
||||
type RuleFunc func() RuleSet
|
||||
|
||||
type RuleSet struct {
|
||||
Name string
|
||||
RuleValue any
|
||||
FieldValue any
|
||||
FieldName any
|
||||
ErrorMessage string
|
||||
MessageFunc func(RuleSet) string
|
||||
ValidateFunc func(RuleSet) bool
|
||||
}
|
||||
|
||||
func (set RuleSet) Message(msg string) RuleSet {
|
||||
set.ErrorMessage = msg
|
||||
return set
|
||||
}
|
||||
|
||||
// Errors is a map holding all the possible errors that may
|
||||
// occur during validation.
|
||||
type Errors map[string][]string
|
||||
|
||||
// Any return true if there is any error.
|
||||
func (e Errors) Any() bool {
|
||||
return len(e) > 0
|
||||
}
|
||||
|
||||
func (e Errors) Add(name string, msg string) {
|
||||
if _, ok := e[name]; !ok {
|
||||
e[name] = []string{}
|
||||
// Add adds an error for a specific field
|
||||
func (e Errors) Add(field string, msg string) {
|
||||
if _, ok := e[field]; !ok {
|
||||
e[field] = []string{}
|
||||
}
|
||||
e[name] = append(e[name], msg)
|
||||
e[field] = append(e[field], msg)
|
||||
}
|
||||
|
||||
func (e Errors) Get(name string) []string {
|
||||
return e[name]
|
||||
// Get returns all the errors for the given field.
|
||||
func (e Errors) Get(field string) []string {
|
||||
return e[field]
|
||||
}
|
||||
|
||||
func (e Errors) Has(name string) bool {
|
||||
return len(e[name]) > 0
|
||||
// Has returns true whether the given field has any errors.
|
||||
func (e Errors) Has(field string) bool {
|
||||
return len(e[field]) > 0
|
||||
}
|
||||
|
||||
// Schema represents a validation schema.
|
||||
type Schema map[string][]RuleSet
|
||||
|
||||
func (schema Schema) Merge(other Schema) Schema {
|
||||
// Merge merges the two given schemas returning a new Schema.
|
||||
func Merge(schema, other Schema) Schema {
|
||||
newSchema := Schema{}
|
||||
maps.Copy(newSchema, schema)
|
||||
maps.Copy(newSchema, other)
|
||||
|
@ -89,13 +74,8 @@ func Request(r *http.Request, data any, schema Schema) (Errors, bool) {
|
|||
func validate(data any, schema Schema, errors Errors) (Errors, bool) {
|
||||
ok := true
|
||||
for fieldName, ruleSets := range schema {
|
||||
// reflect panics on un-exported variables.
|
||||
if !unicode.IsUpper(rune(fieldName[0])) {
|
||||
errors[fieldName] = []string{
|
||||
"cant marshal unexported field",
|
||||
}
|
||||
return errors, false
|
||||
}
|
||||
// Uppercase the field name so we never check un-exported fields
|
||||
fieldName = string(unicode.ToUpper(rune(fieldName[0]))) + fieldName[1:]
|
||||
fieldValue := getFieldValueByName(data, fieldName)
|
||||
for _, set := range ruleSets {
|
||||
set.FieldValue = fieldValue
|
||||
|
|
58
validate/validate_test.go
Normal file
58
validate/validate_test.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package validate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRuleIn(t *testing.T) {
|
||||
type Foo struct {
|
||||
Currency string
|
||||
}
|
||||
foo := Foo{"eur"}
|
||||
schema := Schema{
|
||||
"currency": Rules(In([]string{"eur", "usd", "chz"})),
|
||||
}
|
||||
errors, ok := Validate(foo, schema)
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, errors)
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
type User struct {
|
||||
Email string
|
||||
Username string
|
||||
}
|
||||
schema := Schema{
|
||||
"email": Rules(Email()),
|
||||
// Test both lower and uppercase
|
||||
"Username": Rules(Min(3), Max(10)),
|
||||
}
|
||||
user := User{
|
||||
Email: "foo@bar.com",
|
||||
Username: "pedropedro",
|
||||
}
|
||||
errors, ok := Validate(user, schema)
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, errors)
|
||||
}
|
||||
|
||||
func TestMergeSchemas(t *testing.T) {
|
||||
expected := Schema{
|
||||
"Name": Rules(),
|
||||
"Email": Rules(),
|
||||
"FirstName": Rules(),
|
||||
"LastName": Rules(),
|
||||
}
|
||||
a := Schema{
|
||||
"Name": Rules(),
|
||||
"Email": Rules(),
|
||||
}
|
||||
b := Schema{
|
||||
"FirstName": Rules(),
|
||||
"LastName": Rules(),
|
||||
}
|
||||
c := Merge(a, b)
|
||||
assert.Equal(t, expected, c)
|
||||
}
|
Loading…
Reference in a new issue