Skip to content

equals validator #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# gokay [![CircleCI](https://circleci.com/gh/zencoder/gokay.svg?style=svg&circle-token=90f42bc5cbb6fe74834f7649d67298130431d88d)](https://circleci.com/gh/zencoder/gokay) [![Coverage Status](https://coveralls.io/repos/github/zencoder/gokay/badge.svg?branch=circle-fixes&t=A2kWWv)](https://coveralls.io/github/zencoder/gokay?branch=circle-fixes)
# gokay [![CircleCI](https://circleci.com/gh/zencoder/gokay.svg?style=svg&circle-token=90f42bc5cbb6fe74834f7649d67298130431d88d)](https://circleci.com/gh/zencoder/gokay) [![Coverage Status](https://coveralls.io/repos/github/zencoder/gokay/badge.svg?t=A2kWWv)](https://coveralls.io/github/zencoder/gokay)
Codegenerated struct validation tool for go.

## How it works
Expand Down Expand Up @@ -70,6 +70,8 @@ Hex | N/A | `(*)string` | Checks if a string field is a valid hexadecimal forma
NotNil | N/A | pointers | Checks and fails if a pointer is nil
Length | 1 | `(*)string` | Checks if a string's length matches the tag's parameter
UUID | N/A | `(*)string` | Checks and fails if a string is not a valid UUID
NotEqual | 1 | `(*)string` or `(*)number` | Checks and fails if field is equal to specific value
Set | 1+ | `(*)string` | Checks and fails if field is not in a specific set of values

### Implicitly generated validations
These sections of code will be added to the generated `Validate()` method regardless of a field's `valid` tag's contents.
Expand Down
6 changes: 6 additions & 0 deletions gkgen/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@ type NotEqualTestPointerStruct struct {
NotEqualComplex128 *complex128 `valid:"NotEqual=(0)"`
NotEqualString string `valid:"NotEqual=(0)"`
}

// SetTestStruct is used in the Set unit tests
type SetTestStruct struct {
SetString string `valid:"Set=(cat)(dog)(mouse)"`
SetStringPtr *string `valid:"Set=(cat)(dog)(mouse)"`
}
2 changes: 2 additions & 0 deletions gkgen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ type ValidateGenerator struct {
// - Hex: checks if a string is a valid hexadecimal format number
// - Length: takes 1 integer argument and compares the length of a string field against that
// - NotNil: Validate fails if field is nil
// - Set: Validate fails if field is not in a specific set of values
// - NotEqual: Validate fails if field is equal to specific value
// - UUID: Checks and fails if a string is not a valid UUID
func NewValidateGenerator() *ValidateGenerator {
v := &ValidateGenerator{make(map[string]Generater)}
v.AddValidation(NewNotNilValidator())
v.AddValidation(NewSetValidator())
v.AddValidation(NewNotEqualValidator())
v.AddValidation(NewLengthValidator())
v.AddValidation(NewHexValidator())
Expand Down
65 changes: 65 additions & 0 deletions gkgen/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gkgen

import (
"errors"
"fmt"
"reflect"
"strings"
)

// SetValidator generates code that will verify a fields does Set one of an allowed set of values
// The SetValidator will look at the field or the dereferenced value of the field
// nil values for a field are not considered invalid
type SetValidator struct {
name string
}

// NewSetValidator holds the SetValidator state
func NewSetValidator() *SetValidator {
return &SetValidator{name: "Set"}
}

// Generate generates validation code
func (s *SetValidator) Generate(sType reflect.Type, fieldStruct reflect.StructField, params []string) (string, error) {
if len(params) == 0 {
return "", errors.New("Set validation requires at least 1 parameter")
}

field := fieldStruct.Type

switch field.Kind() {
case reflect.String:
conditions := make([]string, len(params))
for i, param := range params {
conditions[i] = fmt.Sprintf(`s.%[1]s == "%[2]s"`, fieldStruct.Name, param)
}
condition := strings.Join(conditions, " || ")
return fmt.Sprintf(`
if s.%[1]s != "" && !(%[2]s) {
errors%[1]s = append(errors%[1]s, errors.New("%[1]s must equal %[3]s"))
}`, fieldStruct.Name, condition, strings.Join(params, " or ")), nil
case reflect.Ptr:
field = field.Elem()
switch field.Kind() {
case reflect.String:
conditions := make([]string, len(params))
for i, param := range params {
conditions[i] = fmt.Sprintf(`*s.%[1]s == "%[2]s"`, fieldStruct.Name, param)
}
condition := strings.Join(conditions, " || ")
return fmt.Sprintf(`
if s.%[1]s != nil && !(%[2]s) {
errors%[1]s = append(errors%[1]s, errors.New("%[1]s must equal %[3]s"))
}`, fieldStruct.Name, condition, strings.Join(params, " or ")), nil
default:
return "", fmt.Errorf("Set does not work on type '%s'", field.Kind())
}
default:
return "", fmt.Errorf("Set does not work on type '%s'", field.Kind())
}
}

// Name provides access to the name field
func (s *SetValidator) Name() string {
return s.name
}
101 changes: 101 additions & 0 deletions gkgen/set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package gkgen

import (
"fmt"
"reflect"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestSet(t *testing.T) {
nv := NewSetValidator()
e := SetTestStruct{}
et := reflect.TypeOf(e)

field, ok := et.FieldByName("SetString")
require.True(t, ok)
code, err := nv.Generate(et, field, []string{"cat"})
require.NoError(t, err)
code = strings.Replace(strings.TrimSpace(code), "\t", "", -1)
require.Equal(
t,
fmt.Sprintf("if s.%[1]s != \"\" && !(s.%[1]s == \"cat\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat\"))\n}", field.Name),
code,
)

code, err = nv.Generate(et, field, []string{"cat", "dog", "mouse"})
require.NoError(t, err)
code = strings.Replace(strings.TrimSpace(code), "\t", "", -1)
require.Equal(
t,
fmt.Sprintf("if s.%[1]s != \"\" && !(s.%[1]s == \"cat\" || s.%[1]s == \"dog\" || s.%[1]s == \"mouse\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat or dog or mouse\"))\n}", field.Name),
code,
)
}

func TestSetPointer(t *testing.T) {
nv := NewSetValidator()
e := SetTestStruct{}
et := reflect.TypeOf(e)

field, ok := et.FieldByName("SetStringPtr")
require.True(t, ok)
code, err := nv.Generate(et, field, []string{"cat"})
require.NoError(t, err)
code = strings.Replace(strings.TrimSpace(code), "\t", "", -1)
require.Equal(
t,
fmt.Sprintf("if s.%[1]s != nil && !(*s.%[1]s == \"cat\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat\"))\n}", field.Name),
code,
)

code, err = nv.Generate(et, field, []string{"cat", "dog", "mouse"})
require.NoError(t, err)
code = strings.Replace(strings.TrimSpace(code), "\t", "", -1)
require.Equal(
t,
fmt.Sprintf("if s.%[1]s != nil && !(*s.%[1]s == \"cat\" || *s.%[1]s == \"dog\" || *s.%[1]s == \"mouse\") {\nerrors%[1]s = append(errors%[1]s, errors.New(\"%[1]s must equal cat or dog or mouse\"))\n}", field.Name),
code,
)
}

func TestSetInvalidTypes(t *testing.T) {
nv := NewSetValidator()
e := NotNilTestStruct{}
et := reflect.TypeOf(e)

field, _ := et.FieldByName("NotNilMap")
_, err := nv.Generate(et, field, []string{"0"})
require.Error(t, err)
require.Equal(
t,
"Set does not work on type 'map'",
err.Error(),
)

field, _ = et.FieldByName("NotNilSlice")
_, err = nv.Generate(et, field, []string{"test"})
require.Error(t, err)
require.Equal(
t,
"Set does not work on type 'slice'",
err.Error(),
)
}

func TestSetInvalidParameters(t *testing.T) {
nv := NewSetValidator()
e := SetTestStruct{}
et := reflect.TypeOf(e)

field, _ := et.FieldByName("SetString")
_, err := nv.Generate(et, field, []string{})
require.Error(t, err)
require.Equal(
t,
"Set validation requires at least 1 parameter",
err.Error(),
)
}
5 changes: 5 additions & 0 deletions internal/gkexample/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ type NotEqualTestStruct struct {
NotEqualInt64Ptr *int64 `valid:"NotEqual=(7)"`
}

type SetTestStruct struct {
SetString string `valid:"Set=(cat)(dog)(mouse)"`
SetStringPtr *string `valid:"Set=(cat)(dog)(mouse)"`
}

type TestValidate struct {
Valid bool
}
Expand Down
37 changes: 37 additions & 0 deletions internal/gkexample/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,40 @@ func TestValidateNotEqual_Inalid(t *testing.T) {
err := underTest.Validate()
require.Equal(t, expected, err)
}

func TestValidateSet_Valid(t *testing.T) {
validCases := []string{"cat", "dog", "mouse"}

for _, tc := range validCases {
underTest := SetTestStruct{
SetString: tc,
SetStringPtr: &tc,
}

err := underTest.Validate()
require.Nil(t, err)
}
}

func TestValidateSet_NilValid(t *testing.T) {
underTest := SetTestStruct{}

err := underTest.Validate()
require.Nil(t, err)
}

func TestValidateSet_Inalid(t *testing.T) {
expected := gokay.ErrorMap{
"SetString": gokay.ErrorSlice{errors.New("SetString must equal cat or dog or mouse")},
"SetStringPtr": gokay.ErrorSlice{errors.New("SetStringPtr must equal cat or dog or mouse")},
}

gokay := "gokay"
underTest := SetTestStruct{
SetString: "Cat",
SetStringPtr: &gokay,
}

err := underTest.Validate()
require.Equal(t, expected, err)
}
34 changes: 34 additions & 0 deletions internal/gkexample/example_validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,37 @@ func (s NotNilTestStruct) Validate() error {
}

}
func (s SetTestStruct) Validate() error {
em := make(gokay.ErrorMap)

// BEGIN SetString field Validations
errorsSetString := make(gokay.ErrorSlice, 0, 0)
// Set
if s.SetString != "" && !(s.SetString == "cat" || s.SetString == "dog" || s.SetString == "mouse") {
errorsSetString = append(errorsSetString, errors.New("SetString must equal cat or dog or mouse"))
}

if len(errorsSetString) > 0 {
em["SetString"] = errorsSetString
}
// END SetString field Validations

// BEGIN SetStringPtr field Validations
errorsSetStringPtr := make(gokay.ErrorSlice, 0, 0)
// Set
if s.SetStringPtr != nil && !(*s.SetStringPtr == "cat" || *s.SetStringPtr == "dog" || *s.SetStringPtr == "mouse") {
errorsSetStringPtr = append(errorsSetStringPtr, errors.New("SetStringPtr must equal cat or dog or mouse"))
}

if len(errorsSetStringPtr) > 0 {
em["SetStringPtr"] = errorsSetStringPtr
}
// END SetStringPtr field Validations

if len(em) > 0 {
return em
} else {
return nil
}

}