Skip to content

Commit

Permalink
Merge branch 'hw09_struct_validator'
Browse files Browse the repository at this point in the history
  • Loading branch information
DimVlas authored and DimVlas committed Oct 7, 2024
2 parents d7fb585 + bc7f5a2 commit bdf36c6
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 194 deletions.
Empty file added hw09_struct_validator/.sync
Empty file.
1 change: 0 additions & 1 deletion hw09_struct_validator/go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module github.com/DimVlas/otus_hw/hw09_struct_validator

go 1.22

require github.com/stretchr/testify v1.9.0

require (
Expand Down
16 changes: 13 additions & 3 deletions hw09_struct_validator/rules/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
- `field:Size int` - в структуре долно быть целочисленное поле с именем 'Size'

## string
- `len:32` - длина строки должна быть ровно 32 символа;
- `regexp:\\d+` - согласно регулярному выражению строка должна состоять из цифр (`\\` - экранирование слэша);
- `in:foo,bar` - строка должна входить в множество строк {"foo", "bar"}.
- len - `len:32` - длина строки должна быть ровно 32 символа;
- regexp - `regexp:\\d+` - согласно регулярному выражению строка должна состоять из цифр (`\\` - экранирование слэша);
- in - `in:foo,bar` - строка должна входить в множество строк {"foo", "bar"}.

# Тесты

## string

### len
- не верное значение -
- не верное условие
- проврека провалена
- проверка успешна
7 changes: 4 additions & 3 deletions hw09_struct_validator/rules/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
)

var (
ErrEmptyRule = errors.New("the rule cannot be empty")
ErrUnknowRule = errors.New("unknow rule")
ErrNotImplement = errors.New("the rule has no implementation")
ErrRequireStruct = errors.New("validate requires structure")
ErrEmptyRule = errors.New("the rule cannot be empty")
ErrUnknowRule = errors.New("unknow rule")
ErrNotImplement = errors.New("the rule has no implementation")
)

// ошибка валидации поля структуры
Expand Down
16 changes: 16 additions & 0 deletions hw09_struct_validator/rules/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rules
import (
"fmt"
"reflect"
"regexp"
"strconv"
"unicode/utf8"
)
Expand Down Expand Up @@ -31,6 +32,21 @@ var Rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string)
return nil
},
"regexp": func(v reflect.Value, condition string) error {
if v.Kind() != reflect.String {
return fmt.Errorf("this rule applies only to the string")
}

pattern, err := regexp.Compile(condition)
if err != nil {
return err
}
if !pattern.MatchString(v.String()) {
return ValidationError{
Field: "",
Err: fmt.Errorf("length of the string not equal to %s", condition),
}
}

return ErrNotImplement
},
"in": func(v reflect.Value, condition string) error {
Expand Down
19 changes: 10 additions & 9 deletions hw09_struct_validator/rules/rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ import (
"github.com/stretchr/testify/require"
)

// тестировани функций-правил для значений типа "string".
func TestStringLen(t *testing.T) {
// Неверный тип значения, передаем int вместо строки
t.Run("len bad value", func(t *testing.T) {
f := Rules[reflect.String]["len"]

err := f(reflect.ValueOf(123), "0")
err := Rules[reflect.String]["len"](reflect.ValueOf(123), "0")

require.EqualError(t, err, "this rule applies only to the string")
})

// неверное значение условия для правила
t.Run("len bad condition", func(t *testing.T) {
f := Rules[reflect.String]["len"]

s := "Мой милый дом!"

err := f(reflect.ValueOf(s), "s")
err := Rules[reflect.String]["len"](reflect.ValueOf(s), "s")

require.EqualError(t, err, "'s' is not a valid condition for the 'len' rule")
})

// проверка провалена
t.Run("len not equal", func(t *testing.T) {
f := Rules[reflect.String]["len"]

Expand All @@ -37,15 +37,16 @@ func TestStringLen(t *testing.T) {

err := f(reflect.ValueOf(s), strconv.Itoa(l))

// TODO: здесь должна быть не просто ошибка, а ошибка ValidationError, это тож надо проверить

require.EqualError(t, err, fmt.Sprintf("length of the string not equal to %d", l))
})

// проверка успешна
t.Run("len success", func(t *testing.T) {
f := Rules[reflect.String]["len"]

s := "Мой милый дом!"

err := f(reflect.ValueOf(s), strconv.Itoa(utf8.RuneCountInString(s)))
err := Rules[reflect.String]["len"](reflect.ValueOf(s), strconv.Itoa(utf8.RuneCountInString(s)))
require.NoError(t, err)
})

Expand Down
163 changes: 163 additions & 0 deletions hw09_struct_validator/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package hw09structvalidator

import (
"fmt"
"reflect"
"strings"

"github.com/DimVlas/otus_hw/hw09_struct_validator/rules"
)

func validate(v reflect.Value, vTag string) error {
switch v.Kind() {
case reflect.Struct:
return validateStruct(v, vTag)
case reflect.String:
return validateString(v, vTag)

default:
return fmt.Errorf("'%s' unknown data type for validation", v.Type())
}
}

// Валидирует структутру.
// Принимает
// v типа reflect.Value струтуры, если v.Kind() != reflect.Struct - panic;
// vTag - тэг с правилами проверки
// Возвращает error, если произошла программная ошибка
// или ValidationErrors, если были ошибки валидации.
// Валидация проходит в 2 этапа
// сначала валидируется целиком структура, согласно правилам описанным в vTag,
// далее запускается валидация каждого публичного поля структуры
func validateStruct(v reflect.Value, vTag string) error {
var errStruct rules.ValidationErrors

err := validStructR(v, vTag)
if err != nil {
var ok bool
if errStruct, ok = err.(rules.ValidationErrors); !ok {
return err
}
}

err = validStructF(v)
if err != nil {
errVaild, ok := err.(rules.ValidationErrors)
if !ok {
return err
}
if errStruct == nil {
return errVaild
}

errStruct = append(errStruct, errVaild...)
return errStruct
}

return err
}

// валидирует структуру "целиком" соглано правилам валидации структуры
// Validation Structure Rules
func validStructR(v reflect.Value, vTag string) error {
if len(vTag) == 0 {
return nil
}

return nil
}

// валидирует поля структуры
// Validation Structure Fields
func validStructF(v reflect.Value) error {
cnt := v.NumField()
if cnt < 1 {
return nil
}

var errStruct = rules.ValidationErrors{}
for i := 0; i < cnt; i++ {
f := v.Type().Field(i)

if f.PkgPath != "" { // приватное поле
continue
}

tag := f.Tag.Get("validate")
if len(tag) == 0 {
continue
}
// рекурсивно выполняем валидацию значения поля
errField := validate(v.Field(i), tag)
if errField != nil {
errValid, ok := errField.(rules.ValidationErrors)
if !ok {
return errField
}
for i := range errValid {
errValid[i].Field = f.Name
}
errStruct = append(errStruct, errValid...)
}
}
if len(errStruct) > 0 {
return errStruct
}
return nil
}

func validateString(v reflect.Value, vTag string) error {
if len(vTag) == 0 {
return rules.ErrEmptyRule
}
rule := strings.Split(vTag, ":")
if len(rule) != 2 {
return rules.ErrUnknowRule
}

f, ok := rules.Rules[v.Kind()][rule[0]]
if !ok {
return rules.ErrUnknowRule
}

return f(v, rule[1])
}

// func checkStruct(v interface{}) error {
// tp := reflect.TypeOf(v)
// vl := reflect.ValueOf(v)

// if tp.Kind() != reflect.Struct {
// return rules.ErrRequireStruct
// }

// cnt := tp.NumField()
// if cnt < 1 {
// return nil
// }

// for i := 0; i < cnt; i++ {
// field := tp.Field(i)
// if field.PkgPath != "" { // приватное поле
// continue
// }
// tag := field.Tag
// tagValidate := tag.Get("validate")
// if tagValidate == "" {
// continue
// }

// fv := vl.FieldByName(field.Name)

// fmt.Println(fv)
// fmt.Printf("field: %s\n", field.Name)
// fmt.Print("rules:\n")
// rs := strings.Split(tagValidate, "|")
// for _, r := range rs {
// fmt.Printf(" %s\n", r)
// }
// fmt.Println()
// }

// return nil
// }
Loading

0 comments on commit bdf36c6

Please sign in to comment.