Merge as a pure function on validate

This commit is contained in:
anthdm 2024-06-08 09:52:30 +02:00
parent c5ef533dd7
commit 96ec0691f7
5 changed files with 114 additions and 39 deletions

7
go.mod
View file

@ -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
View file

@ -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=

View file

@ -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
}

View file

@ -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
View 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)
}