Skip to content
Open
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
Empty file removed hw09_struct_validator/.sync
Empty file.
7 changes: 6 additions & 1 deletion hw09_struct_validator/go.mod
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Обратите внимание, что данный репозиторий заархивирован и более не поддерживается. Таких зависимостей лучше избегать или быть готовыми их поддерживать своими силами.

github.com/stretchr/testify v1.8.0
)
17 changes: 17 additions & 0 deletions hw09_struct_validator/go.sum
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=
309 changes: 306 additions & 3 deletions hw09_struct_validator/validator.go
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 (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это очень круто, что вы создали тип под возможные типы валидаторов и добавили для них константы. Только тут будет более удобно использовать не числовые константы а строки. Это позволит нам избавится от getValidatorTypeByString.

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
Expand All @@ -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++ {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Перед тем как вызвать NumField нужно убедится что v это структура иначе мы получим панику.

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