-
Notifications
You must be signed in to change notification settings - Fork 0
HW09 is completed #9
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это очень круто, что вы создали тип под возможные типы валидаторов и добавили для них константы. Только тут будет более удобно использовать не числовые константы а строки. Это позволит нам избавится от |
||
| 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<stT.NumField(); i++ { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Перед тем как вызвать |
||
| field := stT.Field(i) | ||
| tag := field.Tag.Get(VALIDATE_TAG_NAME) | ||
| if tag == "" { | ||
| continue | ||
| } | ||
|
|
||
| validators, err := getValidators(tag) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| for _, validator := range validators { | ||
| validationErrors, err := valid(field, reflect.Indirect(stV).FieldByName(field.Name).Interface(), validator) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if validationErrors != nil { | ||
| result = append(result, validationErrors...) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if len(result) == 0 { | ||
| return nil | ||
| } | ||
|
|
||
| return result | ||
| } | ||
|
|
||
| func getValidators(tagValue string) ([]Validator, error) { | ||
| var validator []Validator | ||
|
|
||
| validatorStrings := strings.Split(tagValue, "|") | ||
|
|
||
| for _, str := range validatorStrings { | ||
|
|
||
| delimiterPos := strings.Index(str, ":") | ||
| if delimiterPos == -1 { | ||
| return nil, errors.New("Incorrect validator definition. Semicolon should be present") | ||
| } | ||
|
|
||
| typeString := str[0:delimiterPos] | ||
|
|
||
| ruleString := str[delimiterPos+1:] | ||
| if ruleString == "" { | ||
| return nil, errors.New("Incorrect validator definition. Value should be present after semicolon") | ||
| } | ||
|
|
||
| validator = append(validator, Validator{ValidatorType(typeString), ValidatorValue(ruleString)}) | ||
| } | ||
|
|
||
| if len(validator) == 0 { | ||
| return nil, nil | ||
| } | ||
|
|
||
| return validator, nil | ||
| } | ||
|
|
||
| func valid(f reflect.StructField, fieldValue interface{}, validator Validator) ([]ValidationError, error) { | ||
| switch f.Type.Kind() { | ||
| case reflect.Slice, reflect.Array: | ||
| var result []ValidationError | ||
|
|
||
| array := reflect.ValueOf(fieldValue) | ||
|
|
||
| for i := 0; i < array.Len(); i++ { | ||
| oneEntryResult, err := validOneEntry(f.Name, array.Index(i).Interface(), validator) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| result = append(result, oneEntryResult...) | ||
| } | ||
|
|
||
| return result, nil | ||
| } | ||
|
|
||
| return validOneEntry(f.Name, fieldValue, validator) | ||
| } | ||
|
|
||
| func validOneEntry(fieldName string, fieldValue interface{}, validator Validator) ([]ValidationError, error) { | ||
| switch validator.Type { | ||
| case "min": | ||
| value, typeErr := getInt(fieldValue) | ||
| if typeErr != nil { | ||
| return nil, typeErr | ||
| } | ||
| validationErr, err := validateMin(fieldName, value, validator) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if validationErr != NoValidationError { | ||
| return []ValidationError{validationErr}, nil | ||
| } | ||
| case "max": | ||
| value, typeErr := getInt(fieldValue) | ||
| if typeErr != nil { | ||
| return nil, typeErr | ||
| } | ||
| validationErr, err := validateMax(fieldName, value, validator) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if validationErr != NoValidationError { | ||
| return []ValidationError{validationErr}, nil | ||
| } | ||
| case "len": | ||
| value, typeErr := getString(fieldValue) | ||
| if typeErr != nil { | ||
| return nil, typeErr | ||
| } | ||
| validationErr, err := validateLen(fieldName, value, validator) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if validationErr != NoValidationError { | ||
| return []ValidationError{validationErr}, nil | ||
| } | ||
| case "regexp": | ||
| value, typeErr := getString(fieldValue) | ||
| if typeErr != nil { | ||
| return nil, typeErr | ||
| } | ||
| validationErr, err := validateRegexp(fieldName, value, validator) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if validationErr != NoValidationError { | ||
| return []ValidationError{validationErr}, nil | ||
| } | ||
| case "in": | ||
| switch reflect.TypeOf(fieldValue).Kind() { | ||
| case reflect.Int: | ||
| value, typeErr := getInt(fieldValue) | ||
| if typeErr != nil { | ||
| return nil, typeErr | ||
| } | ||
| validationErr, err := validateInInt(fieldName, value, validator) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if validationErr != NoValidationError { | ||
| return []ValidationError{validationErr}, nil | ||
| } | ||
| case reflect.String: | ||
| value, typeErr := getString(fieldValue) | ||
| if typeErr != nil { | ||
| return nil, typeErr | ||
| } | ||
| validationErr, err := validateInString(fieldName, value, validator) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if validationErr != NoValidationError { | ||
| return []ValidationError{validationErr}, nil | ||
| } | ||
| } | ||
| default: | ||
| return nil, errors.New("Unsupported validation type") | ||
| } | ||
|
|
||
| return nil, nil | ||
| } | ||
|
|
||
| func getInt(v interface{}) (int, error) { | ||
| if reflect.TypeOf(v).Kind() != reflect.Int { | ||
| return 0, fmt.Errorf("Value should have type int. %s provided", reflect.TypeOf(v).Name()) | ||
| } | ||
|
|
||
| return int(reflect.ValueOf(v).Int()), nil | ||
| } | ||
|
|
||
| func getString(v interface{}) (string, error) { | ||
| if reflect.TypeOf(v).Kind() != reflect.String { | ||
| return "", fmt.Errorf("Value should have type string. %s provided", reflect.TypeOf(v).Kind()) | ||
| } | ||
|
|
||
| return reflect.ValueOf(v).String(), nil | ||
| } | ||
|
|
||
| func validateMin(fieldName string, fieldValue int, validator Validator) (ValidationError, error) { | ||
| validatorValue, err := strconv.Atoi(string(validator.Value)) | ||
| if err != nil { | ||
| return NoValidationError, fmt.Errorf("Error getting int definition for the validator: %s", err) | ||
| } | ||
|
|
||
| if fieldValue < validatorValue { | ||
| return ValidationError{fieldName, fmt.Errorf("Value should be more than %d. %d was provided", validatorValue, fieldValue)}, nil | ||
| } | ||
|
|
||
| return NoValidationError, nil | ||
| } | ||
|
|
||
| func validateMax(fieldName string, fieldValue int, validator Validator) (ValidationError, error) { | ||
| validatorValue, err := strconv.Atoi(string(validator.Value)) | ||
| if err != nil { | ||
| return NoValidationError, fmt.Errorf("Error getting int definition for the validator: %s", err) | ||
| } | ||
|
|
||
| if fieldValue > 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 | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Обратите внимание, что данный репозиторий заархивирован и более не поддерживается. Таких зависимостей лучше избегать или быть готовыми их поддерживать своими силами.