diff --git a/hw09_struct_validator/.sync b/hw09_struct_validator/.sync deleted file mode 100644 index e69de29..0000000 diff --git a/hw09_struct_validator/go.mod b/hw09_struct_validator/go.mod index a22eccc..3683f8b 100644 --- a/hw09_struct_validator/go.mod +++ b/hw09_struct_validator/go.mod @@ -1,3 +1,8 @@ -module github.com/fixme_my_friend/hw09_struct_validator +module github.com/sofiiakulish/hw09_struct_validator go 1.16 + +require ( + github.com/fatih/structs v1.1.0 + github.com/stretchr/testify v1.8.0 +) diff --git a/hw09_struct_validator/go.sum b/hw09_struct_validator/go.sum new file mode 100644 index 0000000..aa2b388 --- /dev/null +++ b/hw09_struct_validator/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go index b85fbec..c3c4c0e 100644 --- a/hw09_struct_validator/validator.go +++ b/hw09_struct_validator/validator.go @@ -1,5 +1,35 @@ package hw09structvalidator +import ( + "errors" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" +) + +const VALIDATE_TAG_NAME = "validate" + +var NoValidationError = ValidationError{"", nil} + +type ValidatorType string + +const ( + MIN ValidatorType = "min" + MAX ValidatorType = "max" + LEN ValidatorType = "len" + REGEXP ValidatorType = "regexp" + IN ValidatorType = "in" +) + +type ValidatorValue string + +type Validator struct { + Type ValidatorType + Value ValidatorValue +} + type ValidationError struct { Field string Err error @@ -8,10 +38,283 @@ type ValidationError struct { type ValidationErrors []ValidationError func (v ValidationErrors) Error() string { - panic("implement me") + var b strings.Builder + + for _, validationError := range v { + b.WriteString(fmt.Sprintf("Validation error for field %q: %s\n", validationError.Field, validationError.Err.Error())) + } + + return b.String() } func Validate(v interface{}) error { - // Place your code here. - return nil + var result ValidationErrors + + stT := reflect.TypeOf(v) + stV := reflect.ValueOf(v) + + for i:=0; i validatorValue { + return ValidationError{fieldName, fmt.Errorf("Value should be less than %d. %d was provided", validatorValue, fieldValue)}, nil + } + + return NoValidationError, nil +} + +func validateInString(fieldName string, fieldValue string, validator Validator) (ValidationError, error) { + validatorValues := strings.Split(string(validator.Value), ",") + + for _, val := range validatorValues { + if val == fieldValue { + return NoValidationError, nil + } + } + + return ValidationError{fieldName, fmt.Errorf("Value should be in the list %q. %q was provided", string(validator.Value), fieldValue)}, nil +} + +func validateInInt(fieldName string, fieldValue int, validator Validator) (ValidationError, error) { + validatorValuesString := strings.Split(string(validator.Value), ",") + validatorValues := make([]int, len(validatorValuesString)) + + for k, v := range validatorValuesString { + value, err := strconv.Atoi(v) + if err != nil { + return NoValidationError, fmt.Errorf("Error parsing validation value: %s", err) + } + + validatorValues[k] = value + } + + for _, val := range validatorValues { + if val == fieldValue { + return NoValidationError, nil + } + } + + return ValidationError{fieldName, fmt.Errorf("Value should be in the list %q. %d was provided", string(validator.Value), fieldValue)}, nil +} + +func validateLen(fieldName string, fieldValue string, validator Validator) (ValidationError, error) { + validatorValue, _ := strconv.Atoi(string(validator.Value)) + + if len(fieldValue) != validatorValue { + return ValidationError{fieldName, fmt.Errorf("Value should have length %d. %d (%q) provided", validatorValue, len(fieldValue), fieldValue)}, nil + } + + return NoValidationError, nil +} + +func validateRegexp(fieldName string, fieldValue string, validator Validator) (ValidationError, error) { + validatorValue := string(validator.Value) + + matched, err := regexp.MatchString(validatorValue, fieldValue) + if err != nil { + return NoValidationError, fmt.Errorf("Error regexp: %s", err) + } + + if matched == false { + return ValidationError{fieldName, fmt.Errorf("Value should match regexp %q. Value %q is not match", validatorValue, fieldValue)}, nil + } + + return NoValidationError, nil } diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go index 459facc..570bc6c 100644 --- a/hw09_struct_validator/validator_test.go +++ b/hw09_struct_validator/validator_test.go @@ -2,7 +2,9 @@ package hw09structvalidator import ( "encoding/json" + "errors" "fmt" + "github.com/stretchr/testify/require" "testing" ) @@ -34,18 +36,95 @@ type ( Code int `validate:"in:200,404,500"` Body string `json:"omitempty"` } + + InvalidLenIntDefinition struct { + ID int `validate:"len:36"` + } + + InvalidMinStringDefinition struct { + Name string `validate:"min:36"` + } + + InvalidMaxStringDefinition struct { + Name string `validate:"max:36"` + } + + InvalidRegexpIntDefinition struct { + Name int `validate:"regexp:^\\w+@\\w+\\.\\w+$"` + } + InvalidLenDefinition struct { + ID int `validate:"len:a"` + } + + InvalidMinDefinition struct { + ID int `validate:"min:a"` + } + + InvalidMaxDefinition struct { + ID int `validate:"max:a"` + } ) -func TestValidate(t *testing.T) { +func TestValidateWithValidationErrors(t *testing.T) { + tests := []struct { + in interface{} + expectedErrString string + }{ + {in: User{"123", "Name", 20, "1@gmail.com", "admin", []string{"12345678901"}, []byte(`{"data": "test"}`)}, expectedErrString: "Validation error for field \"ID\": Value should have length 36. 3 (\"123\") provided\n"}, + {in: User{"123", "Name", 12, "1@gmail.com", "admin", []string{"12345678901"}, []byte(`{"data": "test"}`)}, expectedErrString: "Validation error for field \"ID\": Value should have length 36. 3 (\"123\") provided\nValidation error for field \"Age\": Value should be more than 18. 12 was provided\n"}, + {in: User{"123456789012345678901234567890123456", "Name", 20, "1gmail.com", "admin", []string{"12345678901"}, []byte(`{"data": "test"}`)}, expectedErrString: fmt.Sprintf("Validation error for field \"Email\": Value should match regexp %q. Value \"1gmail.com\" is not match\n", "^\\w+@\\w+\\.\\w+$")}, + {in: User{"123456789012345678901234567890123456", "Name", 20, "1@gmail.com", "admin2", []string{"12345678901"}, []byte(`{"data": "test"}`)}, expectedErrString: "Validation error for field \"Role\": Value should be in the list \"admin,stuff\". \"admin2\" was provided\n"}, + {in: User{"123456789012345678901234567890123456", "Name", 20, "1@gmail.com", "admin", []string{"123", "1234", "12345678901"}, []byte(`{"data": "test"}`)}, expectedErrString: "Validation error for field \"Phones\": Value should have length 11. 3 (\"123\") provided\nValidation error for field \"Phones\": Value should have length 11. 4 (\"1234\") provided\n"}, + + {in: App{"1234"}, expectedErrString: "Validation error for field \"Version\": Value should have length 5. 4 (\"1234\") provided\n"}, + + {in: Response{401, "Not Authorized"}, expectedErrString: "Validation error for field \"Code\": Value should be in the list \"200,404,500\". 401 was provided\n"}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + tt := tt + t.Parallel() + + err := Validate(tt.in) + require.Equal(t, tt.expectedErrString, err.Error()) + }) + } +} + +func TestValidateSuccess(t *testing.T) { + tests := []struct { + in interface{} + }{ + {in: User{"123456789012345678901234567890123456", "Name", 20, "1@gmail.com", "admin", []string{"12345678901"}, []byte(`{"data": "test"}`)}}, + {in: App{"12345"}}, + {in: Token{[]byte(`"test"`), []byte(`"test"`), []byte(`"test"`)}}, + {in: Response{200, "{}"}}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + tt := tt + t.Parallel() + + err := Validate(tt.in) + require.NoError(t, err) + }) + } +} + +func TestInvalidDefinition(t *testing.T) { tests := []struct { - in interface{} + in interface{} expectedErr error }{ - { - // Place your code here. - }, - // ... - // Place your code here. + {in: InvalidLenIntDefinition{12345}, expectedErr: errors.New("Value should have type string. int provided")}, + {in: InvalidMinStringDefinition{"Name"}, expectedErr: errors.New("Value should have type int. string provided")}, + {in: InvalidMaxStringDefinition{"Name"}, expectedErr: errors.New("Value should have type int. string provided")}, + {in: InvalidRegexpIntDefinition{12}, expectedErr: errors.New("Value should have type string. int provided")}, + {in: InvalidLenDefinition{12345}, expectedErr: errors.New("Value should have type string. int provided")}, + {in: InvalidMinDefinition{123}, expectedErr: errors.New("Error getting int definition for the validator: strconv.Atoi: parsing \"a\": invalid syntax")}, + {in: InvalidMaxDefinition{123}, expectedErr: errors.New("Error getting int definition for the validator: strconv.Atoi: parsing \"a\": invalid syntax")}, } for i, tt := range tests { @@ -53,8 +132,9 @@ func TestValidate(t *testing.T) { tt := tt t.Parallel() - // Place your code here. - _ = tt + err := Validate(tt.in) + require.Error(t, err) + require.Equal(t, tt.expectedErr, err) }) } }