Add email and struct validation

This commit is contained in:
Luis Eduardo Jeréz Girón
2024-07-22 20:33:23 -06:00
parent 01cbe3bc0b
commit f3a8efe69a
5 changed files with 275 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
# Validate functions
All the functions in this directory are used to validate data, all of them _MUST BE PURE FUNCTIONS_.
https://en.wikipedia.org/wiki/Pure_function

View File

@@ -0,0 +1,17 @@
package validate
import "regexp"
// Email validates an email address.
// It returns a boolean indicating whether
// the email is valid or not.
func Email(email string) bool {
// Regular expression to match email format
regex := `^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`
// Compile the regular expression
re := regexp.MustCompile(regex)
// Match the email against the regular expression
return re.MatchString(email)
}

View File

@@ -0,0 +1,25 @@
package validate
import "testing"
func TestEmail(t *testing.T) {
tests := []struct {
email string
valid bool
}{
{"", false},
{"test", false},
{"test@", false},
{"@example.com", false},
{"test@example", false},
{"test@example.com", true},
{"test@example.com.gt", true},
}
for _, testItem := range tests {
isValid := Email(testItem.email)
if isValid != testItem.valid {
t.Errorf("Email(%s) expected %v, got %v", testItem.email, testItem.valid, isValid)
}
}
}

View File

@@ -0,0 +1,98 @@
package validate
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// StructError is the error returned by Struct.
type StructError struct {
errs []error
}
// SetErrs sets the errors.
func (e *StructError) SetErrs(errs []error) {
e.errs = errs
}
// AddErr adds an error.
func (e *StructError) AddErr(err error) {
e.errs = append(e.errs, err)
}
// HasErrs returns true if there are errors.
func (e *StructError) HasErrs() bool {
return len(e.errs) > 0
}
// Error returns all the errors as a string separated by commas.
func (e *StructError) Error() string {
errStr := ""
for idx, err := range e.errs {
if idx > 0 {
errStr += ", "
}
errStr += err.Error()
}
return errStr
}
// Errors returns all the errors as a slice of strings.
func (e *StructError) Errors() []string {
errStrs := make([]string, len(e.errs))
for idx, err := range e.errs {
errStrs[idx] = err.Error()
}
return errStrs
}
// ErrorsRaw returns all the errors as a slice of errors.
func (e *StructError) ErrorsRaw() []error {
return e.errs
}
// Struct validates the given struct using go-playground/validator.
func Struct[T any](sPointer *T) *StructError {
err := validator.New().Struct(sPointer)
if err != nil {
errs := StructError{}
if _, ok := err.(*validator.InvalidValidationError); ok {
errs.AddErr(fmt.Errorf("validation error (check if it's a struct): %s", err))
}
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, validationError := range validationErrors {
errs.AddErr(fmt.Errorf(
"error in field %s: %s",
validationError.StructField(),
validationError.Tag(),
))
}
}
return &errs
}
return nil
}
// StructSlice validates the given slice of structs using go-playground/validator.
func StructSlice[T any](sPointerSlice *[]T) *StructError {
for i, sPointer := range *sPointerSlice {
num := i + 1
if err := Struct(&sPointer); err != nil {
se := &StructError{}
errs := []error{fmt.Errorf("error in row %d", num)}
errs = append(errs, err.ErrorsRaw()...)
se.SetErrs(errs)
return se
}
}
return nil
}

View File

@@ -0,0 +1,130 @@
package validate
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
type TestStruct struct {
Field string `validate:"required"`
}
func TestValidateStruct(t *testing.T) {
t.Run("Success", func(t *testing.T) {
s := TestStruct{
Field: "value",
}
err := Struct(&s)
assert.Nil(t, err)
assert.Equal(t, "value", s.Field)
})
t.Run("Fail", func(t *testing.T) {
s := TestStruct{
Field: "",
}
err := Struct(&s)
assert.NotNil(t, err)
assert.IsType(t, &StructError{}, err)
})
}
func TestStructSlice(t *testing.T) {
t.Run("Success", func(t *testing.T) {
s := []TestStruct{
{Field: "value1"},
{Field: "value2"},
}
err := StructSlice(&s)
assert.Nil(t, err)
})
t.Run("Fail on row 1", func(t *testing.T) {
s := []TestStruct{
{Field: ""},
{Field: "value2"},
}
err := StructSlice(&s)
assert.NotNil(t, err)
assert.IsType(t, &StructError{}, err)
assert.Contains(t, err.Error(), "error in row 1")
})
t.Run("Fail on row 2", func(t *testing.T) {
s := []TestStruct{
{Field: "value1"},
{Field: ""},
}
err := StructSlice(&s)
assert.NotNil(t, err)
assert.IsType(t, &StructError{}, err)
assert.Contains(t, err.Error(), "error in row 2")
})
}
func TestStructError(t *testing.T) {
t.Run("Error method", func(t *testing.T) {
err := &StructError{
errs: []error{errors.New("error1"), errors.New("error2")},
}
assert.Equal(t, "error1, error2", err.Error())
})
t.Run("Errors method", func(t *testing.T) {
err := &StructError{
errs: []error{errors.New("error1"), errors.New("error2")},
}
assert.Equal(t, []string{"error1", "error2"}, err.Errors())
})
t.Run("ErrorsRaw method", func(t *testing.T) {
err := &StructError{
errs: []error{errors.New("error1"), errors.New("error2")},
}
assert.Equal(
t,
[]error{errors.New("error1"), errors.New("error2")},
err.ErrorsRaw(),
)
})
t.Run("AddErr method", func(t *testing.T) {
err := &StructError{}
err.AddErr(errors.New("error1"))
err.AddErr(errors.New("error2"))
assert.Equal(t, []string{"error1", "error2"}, err.Errors())
})
t.Run("SetErrs method", func(t *testing.T) {
err := &StructError{}
err.SetErrs([]error{errors.New("error1"), errors.New("error2")})
assert.Equal(t, []string{"error1", "error2"}, err.Errors())
})
t.Run("SetErrs method (overwrite)", func(t *testing.T) {
err := &StructError{
errs: []error{errors.New("error0")},
}
err.SetErrs([]error{errors.New("error1"), errors.New("error2")})
assert.Equal(t, []string{"error1", "error2"}, err.Errors())
})
t.Run("HasErrs method (with errors)", func(t *testing.T) {
err := &StructError{
errs: []error{errors.New("error1"), errors.New("error2")},
}
assert.True(t, err.HasErrs())
})
t.Run("HasErrs method (without errors)", func(t *testing.T) {
err := &StructError{}
assert.False(t, err.HasErrs())
})
}