Skip to content

Commit

Permalink
hw09. moved the logic to a separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
DimVlas authored and DimVlas committed Sep 24, 2024
1 parent 097f7f9 commit 1864dd9
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 160 deletions.
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
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
// }
173 changes: 16 additions & 157 deletions hw09_struct_validator/validator.go
Original file line number Diff line number Diff line change
@@ -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, "")
}

0 comments on commit 1864dd9

Please sign in to comment.