diff --git a/hw09_struct_validator/.sync b/hw09_struct_validator/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw09_struct_validator/go.mod b/hw09_struct_validator/go.mod index c0fbfeb..59e6b42 100644 --- a/hw09_struct_validator/go.mod +++ b/hw09_struct_validator/go.mod @@ -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 ( diff --git a/hw09_struct_validator/rules/RULES.md b/hw09_struct_validator/rules/RULES.md index a0270f6..9e08ec8 100644 --- a/hw09_struct_validator/rules/RULES.md +++ b/hw09_struct_validator/rules/RULES.md @@ -4,6 +4,16 @@ - `field:Size int` - в структуре долно быть целочисленное поле с именем 'Size' ## string -- `len:32` - длина строки должна быть ровно 32 символа; -- `regexp:\\d+` - согласно регулярному выражению строка должна состоять из цифр (`\\` - экранирование слэша); -- `in:foo,bar` - строка должна входить в множество строк {"foo", "bar"}. \ No newline at end of file +- len - `len:32` - длина строки должна быть ровно 32 символа; +- regexp - `regexp:\\d+` - согласно регулярному выражению строка должна состоять из цифр (`\\` - экранирование слэша); +- in - `in:foo,bar` - строка должна входить в множество строк {"foo", "bar"}. + +# Тесты + +## string + +### len +- не верное значение - +- не верное условие +- проврека провалена +- проверка успешна diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index 30b451f..0c42a47 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -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") ) // ошибка валидации поля структуры diff --git a/hw09_struct_validator/rules/rule.go b/hw09_struct_validator/rules/rule.go index cba86cd..6db90dc 100644 --- a/hw09_struct_validator/rules/rule.go +++ b/hw09_struct_validator/rules/rule.go @@ -3,6 +3,7 @@ package rules import ( "fmt" "reflect" + "regexp" "strconv" "unicode/utf8" ) @@ -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 { diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 894db30..05ac004 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -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"] @@ -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) }) diff --git a/hw09_struct_validator/validate.go b/hw09_struct_validator/validate.go new file mode 100644 index 0000000..a37fcf8 --- /dev/null +++ b/hw09_struct_validator/validate.go @@ -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 +// } diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go index 9999023..c9c4e35 100644 --- a/hw09_struct_validator/validator.go +++ b/hw09_struct_validator/validator.go @@ -1,175 +1,34 @@ package hw09structvalidator import ( - "errors" - "fmt" "reflect" - "strings" "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" ) -var ( - ErrRequireStruct = errors.New("validate requires structure") -) - -func Validate(v interface{}) error { - if v == nil { - return nil - } - return validate(reflect.ValueOf(v), "") -} - -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 - } +// implemented in errors.go file +// type ValidationError struct { +// Field string +// Err error +// } - 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]) -} +// type ValidationErrors []ValidationError -func checkStruct(v interface{}) error { - tp := reflect.TypeOf(v) - vl := reflect.ValueOf(v) +// func (v ValidationErrors) Error() string { +// panic("implement me") +// } - if tp.Kind() != reflect.Struct { - return ErrRequireStruct - } - - cnt := tp.NumField() - if cnt < 1 { +func Validate(v interface{}) error { + // nothing to validate + if v == nil { 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) + rval := reflect.ValueOf(v) - 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() + if rval.Kind() != reflect.Struct { + return rules.ErrRequireStruct } - return nil + return validate(rval, "") } diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go index 9319fcb..dd20df1 100644 --- a/hw09_struct_validator/validator_test.go +++ b/hw09_struct_validator/validator_test.go @@ -58,24 +58,3 @@ func TestValidate(t *testing.T) { }) } } - -func TestCheck(t *testing.T) { - // err := checkStruct(struct { - // Id int `validate:"min:1|max:100"` - // Name string `validate:"len:10"` - // }{ - // Id: 55, - // Name: "qwerty", - // }) - - // if err != nil { - // fmt.Println(err) - // } - - err := Validate(nil) - if err != nil { - fmt.Println(err) - } - - t.Fail() -}