From 779d970e5be4671cc3f1393a9c56c569c3bb0d1a Mon Sep 17 00:00:00 2001 From: DimVlas Date: Fri, 8 Nov 2024 23:56:01 +0300 Subject: [PATCH 01/25] HW09. refactoring func validate. add rules.FieldRules --- hw09_struct_validator/go.mod | 3 +- hw09_struct_validator/rules/errors.go | 8 +- hw09_struct_validator/rules/rule.go | 47 ++++++++- hw09_struct_validator/rules/rule_test.go | 9 +- hw09_struct_validator/validate.go | 128 +++++++++++++++-------- hw09_struct_validator/validator.go | 2 +- 6 files changed, 145 insertions(+), 52 deletions(-) diff --git a/hw09_struct_validator/go.mod b/hw09_struct_validator/go.mod index 59e6b42..adf1f29 100644 --- a/hw09_struct_validator/go.mod +++ b/hw09_struct_validator/go.mod @@ -1,10 +1,11 @@ module github.com/DimVlas/otus_hw/hw09_struct_validator go 1.22 + require github.com/stretchr/testify v1.9.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect -) \ No newline at end of file +) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index 0c42a47..aab1aa9 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -6,10 +6,10 @@ import ( ) var ( - 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") + ErrRequireStruct = errors.New("'Validate' requires structure") + ErrEmptyRule = errors.New("the rule cannot be empty") + ErrUnknowRule = errors.New("unknow rule") + ErrRuleNotImplement = errors.New("the rule has no implementation") ) // ошибка валидации поля структуры diff --git a/hw09_struct_validator/rules/rule.go b/hw09_struct_validator/rules/rule.go index 6db90dc..a115d54 100644 --- a/hw09_struct_validator/rules/rule.go +++ b/hw09_struct_validator/rules/rule.go @@ -5,21 +5,62 @@ import ( "reflect" "regexp" "strconv" + "strings" "unicode/utf8" ) +// Описание правила проверки +type RuleInfo struct { + Name string // правило проверки + Cond string // условие правила проверки +} + +// правила проверки поля +type FieldRules struct { + FieldName string // наименование поля + Rules []RuleInfo // слайс правил проверки +} + +func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { + frs := FieldRules{ + FieldName: fieldName, + Rules: []RuleInfo{}, + } + if fieldTag == "" { + return frs, nil + } + + rs := strings.Split(fieldTag, "|") + + if len(rs) < 1 { + return frs, ErrEmptyRule + } + for _, r := range rs { + rule := strings.Split(r, ":") + if len(rule) < 2 { + return frs, ErrUnknowRule + } + + frs.Rules = append(frs.Rules, RuleInfo{Name: rule[0], Cond: rule[1]}) + } + + return frs, nil +} + // типы данных на которые распространяются правила проверки -type TypeValue uint +//type TypeValue uint var Rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string) error{ reflect.String: { // 'len:32' - проверка длины строки должна быть 32 символа "len": func(v reflect.Value, condition string) error { if v.Kind() != reflect.String { + // это правило применимо только к строкам return fmt.Errorf("this rule applies only to the string") } c, err := strconv.Atoi(condition) if err != nil { + // строка не является валидным условием для правила 'len' return fmt.Errorf("'%s' is not a valid condition for the 'len' rule", condition) } @@ -47,10 +88,10 @@ var Rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string) } } - return ErrNotImplement + return ErrRuleNotImplement }, "in": func(v reflect.Value, condition string) error { - return ErrNotImplement + return ErrRuleNotImplement }, }, } diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 05ac004..48d48e1 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -13,11 +13,18 @@ import ( // тестировани функций-правил для значений типа "string". func TestStringLen(t *testing.T) { // Неверный тип значения, передаем int вместо строки - t.Run("len bad value", func(t *testing.T) { + t.Run("len bad int value", func(t *testing.T) { err := Rules[reflect.String]["len"](reflect.ValueOf(123), "0") require.EqualError(t, err, "this rule applies only to the string") }) + // Неверный тип значения, передаем указатель вместо строки + t.Run("len bad &string value", func(t *testing.T) { + var s string = "asd" + err := Rules[reflect.String]["len"](reflect.ValueOf(&s), "0") + + require.EqualError(t, err, "this rule applies only to the string") + }) // неверное значение условия для правила t.Run("len bad condition", func(t *testing.T) { diff --git a/hw09_struct_validator/validate.go b/hw09_struct_validator/validate.go index a37fcf8..2762776 100644 --- a/hw09_struct_validator/validate.go +++ b/hw09_struct_validator/validate.go @@ -1,23 +1,66 @@ 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) +// валидирует структутру. +// возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку +// Паникует, если v не структура +func validate(v reflect.Value) error { + cnt := v.NumField() + if cnt < 1 { + return nil + } + + var errStruct = rules.ValidationErrors{} - default: - return fmt.Errorf("'%s' unknown data type for validation", v.Type()) + for i := range cnt { + f := v.Type().Field(i) + + if !f.IsExported() { // приватное поле + continue + } + fieldRules, err := rules.FieldRulesByTag(f.Name, f.Tag.Get("validate")) + if err != nil { + return err + } + + if len(fieldRules.Rules) < 1 { + continue + } + + for _, r := range fieldRules.Rules { + err := rules.Rules[v.Field(i).Kind()][r.Name](v.Field(i), r.Cond) + if err != nil { + errF, ok := err.(rules.ValidationError) + if !ok { + return err + } + + errF.Field = fieldRules.FieldName + errStruct = append(errStruct, errF) + } + } + } + + if len(errStruct) > 0 { + return errStruct } + + return nil + // 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()) + // } } // Валидирует структутру. @@ -70,39 +113,40 @@ func validStructR(v reflect.Value, vTag string) error { // валидирует поля структуры // 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 - } + // 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.IsExported() { // приватное поле + // 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 } diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go index c9c4e35..85eed18 100644 --- a/hw09_struct_validator/validator.go +++ b/hw09_struct_validator/validator.go @@ -30,5 +30,5 @@ func Validate(v interface{}) error { return rules.ErrRequireStruct } - return validate(rval, "") + return validate(rval) } From 2edccb61b60532737cc0ecf025bbf1bf2569bdeb Mon Sep 17 00:00:00 2001 From: DimVlas Date: Mon, 18 Nov 2024 22:28:08 +0300 Subject: [PATCH 02/25] hw09. rules.ParseRulesTag function --- hw09_struct_validator/rules/rule.go | 28 ++++++++++++++ hw09_struct_validator/rules/rule_test.go | 47 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/hw09_struct_validator/rules/rule.go b/hw09_struct_validator/rules/rule.go index a115d54..58a6e84 100644 --- a/hw09_struct_validator/rules/rule.go +++ b/hw09_struct_validator/rules/rule.go @@ -21,6 +21,34 @@ type FieldRules struct { Rules []RuleInfo // слайс правил проверки } +// парсит полученную строку, возвращая массив структур с описанием правил проверки +// ожидается, что строка имеет вид 'правило:условие|правило:условие|...' +func ParseRulesTag(rulesTag string) ([]RuleInfo, error) { + rulesTag = strings.Trim(rulesTag, " ") + if rulesTag == "" { + return []RuleInfo{}, nil + } + + // Разбили на отдельные описания правила: строки вида 'правило:условие' + rs := strings.Split(rulesTag, "|") + + ri := []RuleInfo{} + // из каждого описания правила выделяем имя правила и условие + for _, r := range rs { + if len(r) == 0 { + return []RuleInfo{}, ErrEmptyRule + } + rule := strings.Split(r, ":") + if len(rule) != 2 { + return []RuleInfo{}, ErrUnknowRule + } + + ri = append(ri, RuleInfo{Name: rule[0], Cond: rule[1]}) + } + + return ri, nil +} + func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { frs := FieldRules{ FieldName: fieldName, diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 48d48e1..94e2fc4 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -10,6 +10,53 @@ import ( "github.com/stretchr/testify/require" ) +func TestParseRulesTag(t *testing.T) { + // пустой тэг + t.Run("empty_tag", func(t *testing.T) { + rules, err := ParseRulesTag("") + + require.Equal(t, []RuleInfo{}, rules, "was expected empty slice RuleInfo") + require.NoError(t, err, "should no error for empty tag") + }) + + // тэг с одним правилом + t.Run("one_rule_tag", func(t *testing.T) { + rules, err := ParseRulesTag("rule:condition") + + require.Equal(t, []RuleInfo{{Name: "rule", Cond: "condition"}}, rules) + require.NoError(t, err, "should no error for empty tag") + }) + + // тэг с двумя правилами + t.Run("two_rule_tag", func(t *testing.T) { + rules, err := ParseRulesTag("rule1:condition1|rule2:condition2") + + exp := []RuleInfo{ + {Name: "rule1", Cond: "condition1"}, + {Name: "rule2", Cond: "condition2"}, + } + + require.Equal(t, exp, rules) + require.NoError(t, err, "should no error for empty tag") + }) + + // ошибка: пустые правила + t.Run("error_empty_rules", func(t *testing.T) { + rules, err := ParseRulesTag("|") + + require.Equal(t, []RuleInfo{}, rules) + require.EqualError(t, err, ErrEmptyRule.Error()) + }) + + // ошибка: некорректное правило + t.Run("error_incorrect_rules", func(t *testing.T) { + rules, err := ParseRulesTag("rule:cond|rule") + + require.Equal(t, []RuleInfo{}, rules) + require.EqualError(t, err, ErrUnknowRule.Error()) + }) +} + // тестировани функций-правил для значений типа "string". func TestStringLen(t *testing.T) { // Неверный тип значения, передаем int вместо строки From e4f82b3f84f023b15357ea069e1589db6cfe9b07 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Wed, 20 Nov 2024 22:52:00 +0300 Subject: [PATCH 03/25] HW09. devel validate and validateField.devel rules.FieldRulesByTag and edit rulesRules map --- hw09_struct_validator/rules/rule.go | 84 ++--------- hw09_struct_validator/rules/rule_test.go | 79 ++++++++-- hw09_struct_validator/validate.go | 182 ++++------------------- 3 files changed, 100 insertions(+), 245 deletions(-) diff --git a/hw09_struct_validator/rules/rule.go b/hw09_struct_validator/rules/rule.go index 58a6e84..0c33ec6 100644 --- a/hw09_struct_validator/rules/rule.go +++ b/hw09_struct_validator/rules/rule.go @@ -23,7 +23,7 @@ type FieldRules struct { // парсит полученную строку, возвращая массив структур с описанием правил проверки // ожидается, что строка имеет вид 'правило:условие|правило:условие|...' -func ParseRulesTag(rulesTag string) ([]RuleInfo, error) { +func parseRulesTag(rulesTag string) ([]RuleInfo, error) { rulesTag = strings.Trim(rulesTag, " ") if rulesTag == "" { return []RuleInfo{}, nil @@ -58,19 +58,12 @@ func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { return frs, nil } - rs := strings.Split(fieldTag, "|") - - if len(rs) < 1 { - return frs, ErrEmptyRule + rls, err := parseRulesTag(fieldTag) + if err != nil { + return frs, err } - for _, r := range rs { - rule := strings.Split(r, ":") - if len(rule) < 2 { - return frs, ErrUnknowRule - } - frs.Rules = append(frs.Rules, RuleInfo{Name: rule[0], Cond: rule[1]}) - } + frs.Rules = rls return frs, nil } @@ -78,10 +71,10 @@ func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { // типы данных на которые распространяются правила проверки //type TypeValue uint -var Rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string) error{ +var Rules = map[reflect.Kind]map[string]func(fName string, v reflect.Value, condition string) error{ reflect.String: { // 'len:32' - проверка длины строки должна быть 32 символа - "len": func(v reflect.Value, condition string) error { + "len": func(fName string, v reflect.Value, condition string) error { if v.Kind() != reflect.String { // это правило применимо только к строкам return fmt.Errorf("this rule applies only to the string") @@ -94,13 +87,13 @@ var Rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string) if utf8.RuneCountInString(v.String()) != c { return ValidationError{ - Field: "", + Field: fName, Err: fmt.Errorf("length of the string not equal to %s", condition), } } return nil }, - "regexp": func(v reflect.Value, condition string) error { + "regexp": func(fName string, v reflect.Value, condition string) error { if v.Kind() != reflect.String { return fmt.Errorf("this rule applies only to the string") } @@ -111,70 +104,15 @@ var Rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string) } if !pattern.MatchString(v.String()) { return ValidationError{ - Field: "", + Field: fName, Err: fmt.Errorf("length of the string not equal to %s", condition), } } return ErrRuleNotImplement }, - "in": func(v reflect.Value, condition string) error { + "in": func(fName string, v reflect.Value, condition string) error { return ErrRuleNotImplement }, }, } - -// const ( -// Int TypeValue = iota -// IntSlice -// String -// StringSlice -// ) - -// type Rule interface { -// Rule() string -// Validate(reflect.Value) error -// } - -// func New(rule string) (Rule, error) { -// if len(strings.Trim(rule, " ")) == 0 { -// return nil, ErrEmptyRule -// } - -// tag := reflect.StructTag(rule) -// r, ok := tag.Lookup("len") -// if !ok { -// return nil, errors.New(" not len rule") -// } - -// l, err := strconv.Atoi(r) -// if err != nil { -// return nil, fmt.Errorf("can't parse the '%s' rule", rule) -// } -// lenS := LenStr{ -// rule: rule, -// len: l, -// } -// return lenS, nil -// } - -// type LenStr struct { -// rule string -// len int -// } - -// func (r LenStr) Rule() string { -// return r.rule -// } -// func (r LenStr) Validate(val reflect.Value) error { -// if val.Kind() == reflect.String { -// s := val.String() - -// if len(s) != r.len { -// return fmt.Errorf("the length should be equal to %d", r.len) -// } -// return nil -// } - -// return fmt.Errorf("the '%s' rule only applies to values of type'%s'", r.rule, "string") -// } diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 94e2fc4..b5c915f 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -13,7 +13,7 @@ import ( func TestParseRulesTag(t *testing.T) { // пустой тэг t.Run("empty_tag", func(t *testing.T) { - rules, err := ParseRulesTag("") + rules, err := parseRulesTag("") require.Equal(t, []RuleInfo{}, rules, "was expected empty slice RuleInfo") require.NoError(t, err, "should no error for empty tag") @@ -21,7 +21,7 @@ func TestParseRulesTag(t *testing.T) { // тэг с одним правилом t.Run("one_rule_tag", func(t *testing.T) { - rules, err := ParseRulesTag("rule:condition") + rules, err := parseRulesTag("rule:condition") require.Equal(t, []RuleInfo{{Name: "rule", Cond: "condition"}}, rules) require.NoError(t, err, "should no error for empty tag") @@ -29,7 +29,7 @@ func TestParseRulesTag(t *testing.T) { // тэг с двумя правилами t.Run("two_rule_tag", func(t *testing.T) { - rules, err := ParseRulesTag("rule1:condition1|rule2:condition2") + rules, err := parseRulesTag("rule1:condition1|rule2:condition2") exp := []RuleInfo{ {Name: "rule1", Cond: "condition1"}, @@ -42,7 +42,7 @@ func TestParseRulesTag(t *testing.T) { // ошибка: пустые правила t.Run("error_empty_rules", func(t *testing.T) { - rules, err := ParseRulesTag("|") + rules, err := parseRulesTag("|") require.Equal(t, []RuleInfo{}, rules) require.EqualError(t, err, ErrEmptyRule.Error()) @@ -50,25 +50,73 @@ func TestParseRulesTag(t *testing.T) { // ошибка: некорректное правило t.Run("error_incorrect_rules", func(t *testing.T) { - rules, err := ParseRulesTag("rule:cond|rule") + rules, err := parseRulesTag("rule:cond|rule") require.Equal(t, []RuleInfo{}, rules) require.EqualError(t, err, ErrUnknowRule.Error()) }) } -// тестировани функций-правил для значений типа "string". +func TestFieldRulesByTag(t *testing.T) { + // пустой тэг + t.Run("empty_tag", func(t *testing.T) { + rules, err := FieldRulesByTag("fieldName", "") + + require.Equal(t, FieldRules{FieldName: "fieldName", Rules: []RuleInfo{}}, rules, "was expected empty slice RuleInfo") + require.NoError(t, err, "should no error for empty tag") + }) + + // тэг с одним правилом + t.Run("one_rule_tag", func(t *testing.T) { + rules, err := FieldRulesByTag("fieldName", "rule:condition") + + require.Equal(t, FieldRules{FieldName: "fieldName", Rules: []RuleInfo{{Name: "rule", Cond: "condition"}}}, rules) + require.NoError(t, err, "should no error for empty tag") + }) + + // тэг с двумя правилами + t.Run("two_rule_tag", func(t *testing.T) { + rules, err := FieldRulesByTag("fieldName", "rule1:condition1|rule2:condition2") + + exp := []RuleInfo{ + {Name: "rule1", Cond: "condition1"}, + {Name: "rule2", Cond: "condition2"}, + } + + require.Equal(t, exp, rules) + require.NoError(t, err, "should no error for empty tag") + }) + + // ошибка: пустые правила + t.Run("error_empty_rules", func(t *testing.T) { + rules, err := FieldRulesByTag("fieldName", "|") + + require.Equal(t, []RuleInfo{}, rules) + require.EqualError(t, err, ErrEmptyRule.Error()) + }) + + // ошибка: некорректное правило + t.Run("error_incorrect_rules", func(t *testing.T) { + rules, err := FieldRulesByTag("fieldName", "rule:cond|rule") + + require.Equal(t, []RuleInfo{}, rules) + require.EqualError(t, err, ErrUnknowRule.Error()) + }) +} + +// тестировани функции-правила 'len' для значений типа "string". func TestStringLen(t *testing.T) { // Неверный тип значения, передаем int вместо строки t.Run("len bad int value", func(t *testing.T) { - err := Rules[reflect.String]["len"](reflect.ValueOf(123), "0") + err := Rules[reflect.String]["len"]("field", reflect.ValueOf(123), "0") require.EqualError(t, err, "this rule applies only to the string") }) + // Неверный тип значения, передаем указатель вместо строки t.Run("len bad &string value", func(t *testing.T) { var s string = "asd" - err := Rules[reflect.String]["len"](reflect.ValueOf(&s), "0") + err := Rules[reflect.String]["len"]("field", reflect.ValueOf(&s), "0") require.EqualError(t, err, "this rule applies only to the string") }) @@ -77,30 +125,29 @@ func TestStringLen(t *testing.T) { t.Run("len bad condition", func(t *testing.T) { s := "Мой милый дом!" - err := Rules[reflect.String]["len"](reflect.ValueOf(s), "s") + err := Rules[reflect.String]["len"]("field", 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"] s := "Мой милый дом!" l := utf8.RuneCountInString(s) - 1 - err := f(reflect.ValueOf(s), strconv.Itoa(l)) - - // TODO: здесь должна быть не просто ошибка, а ошибка ValidationError, это тож надо проверить + err := f("field", reflect.ValueOf(s), strconv.Itoa(l)) - require.EqualError(t, err, fmt.Sprintf("length of the string not equal to %d", l)) + require.IsType(t, ValidationError{}, err) + require.EqualError(t, err, fmt.Sprintf("field: length of the string not equal to %d", l)) }) - // проверка успешна + // проверка успешна - длина соответствует t.Run("len success", func(t *testing.T) { s := "Мой милый дом!" - err := Rules[reflect.String]["len"](reflect.ValueOf(s), strconv.Itoa(utf8.RuneCountInString(s))) + err := Rules[reflect.String]["len"]("field", 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 index 2762776..eab317d 100644 --- a/hw09_struct_validator/validate.go +++ b/hw09_struct_validator/validate.go @@ -2,7 +2,6 @@ package hw09structvalidator import ( "reflect" - "strings" "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" ) @@ -16,7 +15,7 @@ func validate(v reflect.Value) error { return nil } - var errStruct = rules.ValidationErrors{} + var errStructValid = rules.ValidationErrors{} for i := range cnt { f := v.Type().Field(i) @@ -33,175 +32,46 @@ func validate(v reflect.Value) error { continue } - for _, r := range fieldRules.Rules { - err := rules.Rules[v.Field(i).Kind()][r.Name](v.Field(i), r.Cond) - if err != nil { - errF, ok := err.(rules.ValidationError) - if !ok { - return err - } - - errF.Field = fieldRules.FieldName - errStruct = append(errStruct, errF) + err = validateField(v.Field(i), fieldRules) + if err != nil { + switch e := err.(type) { + case rules.ValidationError: + errStructValid = append(errStructValid, e) + default: + return err } } } - if len(errStruct) > 0 { - return errStruct + if len(errStructValid) > 0 { + return errStructValid } return nil - // 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 +// валидируем поле со значение fieldValue, согласно правил описанных fieldRules +func validateField(fieldValue reflect.Value, fieldRules rules.FieldRules) error { + kind := fieldValue.Kind() - err := validStructR(v, vTag) - if err != nil { - var ok bool - if errStruct, ok = err.(rules.ValidationErrors); !ok { - return err - } - } + var errFields = rules.ValidationErrors{} - err = validStructF(v) - if err != nil { - errVaild, ok := err.(rules.ValidationErrors) - if !ok { - return err - } - if errStruct == nil { - return errVaild + for _, rule := range fieldRules.Rules { + f := rules.Rules[kind][rule.Name] + err := f(fieldRules.FieldName, fieldValue, rule.Cond) + if err != nil { + switch e := err.(type) { + case rules.ValidationError: + errFields = append(errFields, e) + default: + return err + } } - - 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 + if len(errFields) > 0 { + return errFields } 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.IsExported() { // приватное поле - // 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 -// } From b866dd91b4bd97c63ce8131ffe14d90e13f4633a Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sun, 24 Nov 2024 22:35:34 +0300 Subject: [PATCH 04/25] HW09. refactoring packcge rule --- hw09_struct_validator/rules/rule_test.go | 14 +-- .../rules/{rule.go => rules.go} | 18 ++-- hw09_struct_validator/rules/validate.go | 85 +++++++++++++++++++ hw09_struct_validator/validate.go | 77 ----------------- hw09_struct_validator/validator.go | 2 +- 5 files changed, 103 insertions(+), 93 deletions(-) rename hw09_struct_validator/rules/{rule.go => rules.go} (84%) create mode 100644 hw09_struct_validator/rules/validate.go delete mode 100644 hw09_struct_validator/validate.go diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index b5c915f..4df4d0c 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -108,7 +108,7 @@ func TestFieldRulesByTag(t *testing.T) { func TestStringLen(t *testing.T) { // Неверный тип значения, передаем int вместо строки t.Run("len bad int value", func(t *testing.T) { - err := Rules[reflect.String]["len"]("field", reflect.ValueOf(123), "0") + err := rules[reflect.String]["len"](reflect.ValueOf(123), "0") require.EqualError(t, err, "this rule applies only to the string") }) @@ -116,7 +116,7 @@ func TestStringLen(t *testing.T) { // Неверный тип значения, передаем указатель вместо строки t.Run("len bad &string value", func(t *testing.T) { var s string = "asd" - err := Rules[reflect.String]["len"]("field", reflect.ValueOf(&s), "0") + err := rules[reflect.String]["len"](reflect.ValueOf(&s), "0") require.EqualError(t, err, "this rule applies only to the string") }) @@ -125,29 +125,29 @@ func TestStringLen(t *testing.T) { t.Run("len bad condition", func(t *testing.T) { s := "Мой милый дом!" - err := Rules[reflect.String]["len"]("field", 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"] + f := rules[reflect.String]["len"] s := "Мой милый дом!" l := utf8.RuneCountInString(s) - 1 - err := f("field", reflect.ValueOf(s), strconv.Itoa(l)) + err := f(reflect.ValueOf(s), strconv.Itoa(l)) require.IsType(t, ValidationError{}, err) - require.EqualError(t, err, fmt.Sprintf("field: length of the string not equal to %d", l)) + require.EqualError(t, err, fmt.Sprintf("length of the string not equal to %d", l)) }) // проверка успешна - длина соответствует t.Run("len success", func(t *testing.T) { s := "Мой милый дом!" - err := Rules[reflect.String]["len"]("field", 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/rules/rule.go b/hw09_struct_validator/rules/rules.go similarity index 84% rename from hw09_struct_validator/rules/rule.go rename to hw09_struct_validator/rules/rules.go index 0c33ec6..4e8a36b 100644 --- a/hw09_struct_validator/rules/rule.go +++ b/hw09_struct_validator/rules/rules.go @@ -71,10 +71,10 @@ func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { // типы данных на которые распространяются правила проверки //type TypeValue uint -var Rules = map[reflect.Kind]map[string]func(fName string, v reflect.Value, condition string) error{ +var rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string) error{ reflect.String: { // 'len:32' - проверка длины строки должна быть 32 символа - "len": func(fName string, v reflect.Value, condition string) error { + "len": func(v reflect.Value, condition string) error { if v.Kind() != reflect.String { // это правило применимо только к строкам return fmt.Errorf("this rule applies only to the string") @@ -87,13 +87,12 @@ var Rules = map[reflect.Kind]map[string]func(fName string, v reflect.Value, cond if utf8.RuneCountInString(v.String()) != c { return ValidationError{ - Field: fName, - Err: fmt.Errorf("length of the string not equal to %s", condition), + Err: fmt.Errorf("length of the string not equal to %s", condition), } } return nil }, - "regexp": func(fName string, v reflect.Value, condition string) error { + "regexp": func(v reflect.Value, condition string) error { if v.Kind() != reflect.String { return fmt.Errorf("this rule applies only to the string") } @@ -104,15 +103,18 @@ var Rules = map[reflect.Kind]map[string]func(fName string, v reflect.Value, cond } if !pattern.MatchString(v.String()) { return ValidationError{ - Field: fName, - Err: fmt.Errorf("length of the string not equal to %s", condition), + Err: fmt.Errorf("length of the string not equal to %s", condition), } } return ErrRuleNotImplement }, - "in": func(fName string, v reflect.Value, condition string) error { + "in": func(v reflect.Value, condition string) error { return ErrRuleNotImplement }, }, } + +func validateFieldRules(value reflect.Value, kind reflect.Kind, rule string, cond string) error { + return rules[kind][rule](value, cond) +} diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go new file mode 100644 index 0000000..bf736c3 --- /dev/null +++ b/hw09_struct_validator/rules/validate.go @@ -0,0 +1,85 @@ +package rules + +import ( + "reflect" +) + +// валидирует структутру. +// возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку +// Паникует, если v не структура +func ValidateStruct(v reflect.Value) error { + cnt := v.NumField() + if cnt < 1 { + return nil + } + + var errStructValid = ValidationErrors{} + + for i := range cnt { + + f := v.Type().Field(i) + + if !f.IsExported() { // приватное поле + continue + } + + if err := validateField(f, v.Field(i)); err != nil { + switch e := err.(type) { + case ValidationErrors: + errStructValid = append(errStructValid, e...) + default: + return err + } + } + } + + if len(errStructValid) > 0 { + return errStructValid + } + + return nil +} + +// валидирует поле структуры +func validateField(fieldInfo reflect.StructField, fieldValue reflect.Value) error { + fieldRules, err := FieldRulesByTag(fieldInfo.Name, fieldInfo.Tag.Get("validate")) + if err != nil { + return err + } + // если не правил, то и проверять нечего + if len(fieldRules.Rules) < 1 { + return nil + } + + return validateFieldValue(fieldValue, fieldRules) +} + +// валидируем поле со значение fieldValue, согласно правил описанных fieldRules +func validateFieldValue(fieldValue reflect.Value, fieldRules FieldRules) error { + var errFields = ValidationErrors{} + + // перебираем все правила + for _, rule := range fieldRules.Rules { + // проверяем fieldValue очередным правилом + err := validateFieldRules(fieldValue, fieldValue.Kind(), rule.Name, rule.Cond) + // если нет ошибок - переходим к следующему правилу + if err == nil { + continue + } + // если ошибка валидации, сохраняем ее в массив ошибок валидации + if vErr, ok := err.(ValidationError); ok { + vErr.Field = fieldRules.FieldName + errFields = append(errFields, vErr) + continue + } + // если программная ошибка - возращаем ее, выходим + return err + } + + // если были ошибки валидации - возращаем их + if len(errFields) > 0 { + return errFields + } + + return nil +} diff --git a/hw09_struct_validator/validate.go b/hw09_struct_validator/validate.go deleted file mode 100644 index eab317d..0000000 --- a/hw09_struct_validator/validate.go +++ /dev/null @@ -1,77 +0,0 @@ -package hw09structvalidator - -import ( - "reflect" - - "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" -) - -// валидирует структутру. -// возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку -// Паникует, если v не структура -func validate(v reflect.Value) error { - cnt := v.NumField() - if cnt < 1 { - return nil - } - - var errStructValid = rules.ValidationErrors{} - - for i := range cnt { - f := v.Type().Field(i) - - if !f.IsExported() { // приватное поле - continue - } - fieldRules, err := rules.FieldRulesByTag(f.Name, f.Tag.Get("validate")) - if err != nil { - return err - } - - if len(fieldRules.Rules) < 1 { - continue - } - - err = validateField(v.Field(i), fieldRules) - if err != nil { - switch e := err.(type) { - case rules.ValidationError: - errStructValid = append(errStructValid, e) - default: - return err - } - } - } - - if len(errStructValid) > 0 { - return errStructValid - } - - return nil -} - -// валидируем поле со значение fieldValue, согласно правил описанных fieldRules -func validateField(fieldValue reflect.Value, fieldRules rules.FieldRules) error { - kind := fieldValue.Kind() - - var errFields = rules.ValidationErrors{} - - for _, rule := range fieldRules.Rules { - f := rules.Rules[kind][rule.Name] - err := f(fieldRules.FieldName, fieldValue, rule.Cond) - if err != nil { - switch e := err.(type) { - case rules.ValidationError: - errFields = append(errFields, e) - default: - return err - } - } - } - - if len(errFields) > 0 { - return errFields - } - - return nil -} diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go index 85eed18..59afe9e 100644 --- a/hw09_struct_validator/validator.go +++ b/hw09_struct_validator/validator.go @@ -30,5 +30,5 @@ func Validate(v interface{}) error { return rules.ErrRequireStruct } - return validate(rval) + return rules.ValidateStruct(rval) } From da64bb6b2f32ba395da9c52f8096547b1ef527bd Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sun, 1 Dec 2024 00:59:57 +0300 Subject: [PATCH 05/25] HW09. refactoring and test funcValidation --- hw09_struct_validator/rules/errors.go | 1 + hw09_struct_validator/rules/rule_test.go | 52 +++++++++++ hw09_struct_validator/rules/rules.go | 114 +++++++++++++---------- hw09_struct_validator/rules/validate.go | 12 ++- 4 files changed, 126 insertions(+), 53 deletions(-) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index aab1aa9..86b1c9f 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -10,6 +10,7 @@ var ( ErrEmptyRule = errors.New("the rule cannot be empty") ErrUnknowRule = errors.New("unknow rule") ErrRuleNotImplement = errors.New("the rule has no implementation") + ErrKindNoRules = errors.New("for this field kind no validation rules") ) // ошибка валидации поля структуры diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 4df4d0c..34dee82 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -10,6 +10,58 @@ import ( "github.com/stretchr/testify/require" ) +func TestFuncValidation(t *testing.T) { + type testData struct { + name string + kind reflect.Kind + cond string + expIsNil bool + err error + } + + tests := []testData{ + { + name: "err_kind_no_rules", + kind: reflect.Invalid, + cond: "rule", + expIsNil: true, + err: fmt.Errorf("'%s' %w", reflect.Invalid, ErrKindNoRules), + }, + { + name: "unknow_rule", + kind: reflect.String, + cond: "rule", + expIsNil: true, + err: fmt.Errorf("'%s' %w", "rule", ErrUnknowRule), + }, + { + name: "success", + kind: reflect.String, + cond: "len", + expIsNil: false, + err: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fn, err := funcValidation(test.kind, test.cond) + + if test.expIsNil { + require.Nil(t, fn) + } else { + require.NotNil(t, fn) + } + + if test.err != nil { + require.EqualError(t, err, test.err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + func TestParseRulesTag(t *testing.T) { // пустой тэг t.Run("empty_tag", func(t *testing.T) { diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 4e8a36b..8877489 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -21,6 +21,68 @@ type FieldRules struct { Rules []RuleInfo // слайс правил проверки } +type ValidateFunc func(v reflect.Value, condition string) error + +// маппа в которой по типам полей содержится маппа с типами правил и функциями проверки для каждого типа правила +var rules = map[reflect.Kind]map[string]ValidateFunc{ + reflect.String: { + // 'len:32' - проверка длины строки должна быть 32 символа + "len": func(v reflect.Value, condition string) error { + if v.Kind() != reflect.String { + // это правило применимо только к строкам + return fmt.Errorf("this rule applies only to the string") + } + c, err := strconv.Atoi(condition) + if err != nil { + // строка не является валидным условием для правила 'len' + return fmt.Errorf("'%s' is not a valid condition for the 'len' rule", condition) + } + + if utf8.RuneCountInString(v.String()) != c { + return ValidationError{ + Err: fmt.Errorf("length of the string not equal to %s", condition), + } + } + 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{ + Err: fmt.Errorf("length of the string not equal to %s", condition), + } + } + + return ErrRuleNotImplement + }, + "in": func(v reflect.Value, condition string) error { + return ErrRuleNotImplement + }, + }, +} + +// возвращает функцию валидации для типа kind и правила rule +func funcValidation(kind reflect.Kind, rule string) (ValidateFunc, error) { + r, ok := rules[kind] + if !ok { + return nil, fmt.Errorf("'%s' %w", kind, ErrKindNoRules) + } + + fv, ok := r[rule] + if !ok { + return nil, fmt.Errorf("'%s' %w", rule, ErrUnknowRule) + } + + return fv, nil +} + // парсит полученную строку, возвращая массив структур с описанием правил проверки // ожидается, что строка имеет вид 'правило:условие|правило:условие|...' func parseRulesTag(rulesTag string) ([]RuleInfo, error) { @@ -49,6 +111,7 @@ func parseRulesTag(rulesTag string) ([]RuleInfo, error) { return ri, nil } +// получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { frs := FieldRules{ FieldName: fieldName, @@ -67,54 +130,3 @@ func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { return frs, nil } - -// типы данных на которые распространяются правила проверки -//type TypeValue uint - -var rules = map[reflect.Kind]map[string]func(v reflect.Value, condition string) error{ - reflect.String: { - // 'len:32' - проверка длины строки должна быть 32 символа - "len": func(v reflect.Value, condition string) error { - if v.Kind() != reflect.String { - // это правило применимо только к строкам - return fmt.Errorf("this rule applies only to the string") - } - c, err := strconv.Atoi(condition) - if err != nil { - // строка не является валидным условием для правила 'len' - return fmt.Errorf("'%s' is not a valid condition for the 'len' rule", condition) - } - - if utf8.RuneCountInString(v.String()) != c { - return ValidationError{ - Err: fmt.Errorf("length of the string not equal to %s", condition), - } - } - 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{ - Err: fmt.Errorf("length of the string not equal to %s", condition), - } - } - - return ErrRuleNotImplement - }, - "in": func(v reflect.Value, condition string) error { - return ErrRuleNotImplement - }, - }, -} - -func validateFieldRules(value reflect.Value, kind reflect.Kind, rule string, cond string) error { - return rules[kind][rule](value, cond) -} diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go index bf736c3..40afbb7 100644 --- a/hw09_struct_validator/rules/validate.go +++ b/hw09_struct_validator/rules/validate.go @@ -15,6 +15,7 @@ func ValidateStruct(v reflect.Value) error { var errStructValid = ValidationErrors{} + // идем по полям структуры for i := range cnt { f := v.Type().Field(i) @@ -23,6 +24,7 @@ func ValidateStruct(v reflect.Value) error { continue } + // валидируем поле структуры if err := validateField(f, v.Field(i)); err != nil { switch e := err.(type) { case ValidationErrors: @@ -60,8 +62,14 @@ func validateFieldValue(fieldValue reflect.Value, fieldRules FieldRules) error { // перебираем все правила for _, rule := range fieldRules.Rules { - // проверяем fieldValue очередным правилом - err := validateFieldRules(fieldValue, fieldValue.Kind(), rule.Name, rule.Cond) + // получаем функцию валидации + vf, err := funcValidation(fieldValue.Kind(), rule.Name) + if err != nil { + return err + } + + // проверяем fieldValue функцией валидации + err = vf(fieldValue, rule.Cond) // если нет ошибок - переходим к следующему правилу if err == nil { continue From d844c2975ca583ba655a89c5175022cd6f97d95b Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sun, 1 Dec 2024 13:26:48 +0300 Subject: [PATCH 06/25] HW09. refactor func TestParseRulesTag --- hw09_struct_validator/rules/rule_test.go | 110 ++++++++++++++--------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 34dee82..ee62e63 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -17,6 +17,7 @@ func TestFuncValidation(t *testing.T) { cond string expIsNil bool err error + mess string } tests := []testData{ @@ -26,6 +27,7 @@ func TestFuncValidation(t *testing.T) { cond: "rule", expIsNil: true, err: fmt.Errorf("'%s' %w", reflect.Invalid, ErrKindNoRules), + mess: "expected error " + fmt.Errorf("'%s' %w", reflect.Invalid, ErrKindNoRules).Error(), }, { name: "unknow_rule", @@ -33,6 +35,7 @@ func TestFuncValidation(t *testing.T) { cond: "rule", expIsNil: true, err: fmt.Errorf("'%s' %w", "rule", ErrUnknowRule), + mess: "expected error " + fmt.Errorf("'%s' %w", "rule", ErrUnknowRule).Error(), }, { name: "success", @@ -40,6 +43,7 @@ func TestFuncValidation(t *testing.T) { cond: "len", expIsNil: false, err: nil, + mess: "expected not nil validation function", }, } @@ -48,65 +52,83 @@ func TestFuncValidation(t *testing.T) { fn, err := funcValidation(test.kind, test.cond) if test.expIsNil { - require.Nil(t, fn) + require.Nil(t, fn, test.mess) } else { - require.NotNil(t, fn) + require.NotNil(t, fn, test.mess) } if test.err != nil { - require.EqualError(t, err, test.err.Error()) + require.EqualError(t, err, test.err.Error(), test.mess) } else { - require.NoError(t, err) + require.NoError(t, err, test.mess) } }) } } func TestParseRulesTag(t *testing.T) { - // пустой тэг - t.Run("empty_tag", func(t *testing.T) { - rules, err := parseRulesTag("") - - require.Equal(t, []RuleInfo{}, rules, "was expected empty slice RuleInfo") - require.NoError(t, err, "should no error for empty tag") - }) - - // тэг с одним правилом - t.Run("one_rule_tag", func(t *testing.T) { - rules, err := parseRulesTag("rule:condition") - - require.Equal(t, []RuleInfo{{Name: "rule", Cond: "condition"}}, rules) - require.NoError(t, err, "should no error for empty tag") - }) - - // тэг с двумя правилами - t.Run("two_rule_tag", func(t *testing.T) { - rules, err := parseRulesTag("rule1:condition1|rule2:condition2") - - exp := []RuleInfo{ - {Name: "rule1", Cond: "condition1"}, - {Name: "rule2", Cond: "condition2"}, - } - - require.Equal(t, exp, rules) - require.NoError(t, err, "should no error for empty tag") - }) + type testData struct { + name string + tag string + exp []RuleInfo + err error + mess string + } - // ошибка: пустые правила - t.Run("error_empty_rules", func(t *testing.T) { - rules, err := parseRulesTag("|") + tests := []testData{ + { + name: "empty_tag", + tag: "", + exp: []RuleInfo{}, + err: nil, + mess: "should no error for empty tag", + }, + { + name: "one_rule_tag", + tag: "rule:condition", + exp: []RuleInfo{{Name: "rule", Cond: "condition"}}, + err: nil, + mess: "should no error for tag with one rule", + }, + { + name: "two_rule_tag", + tag: "rule1:condition1|rule2:condition2", + exp: []RuleInfo{ + {Name: "rule1", Cond: "condition1"}, + {Name: "rule2", Cond: "condition2"}, + }, + err: nil, + mess: "should no error for tag with two rule", + }, + { + name: "error_empty_rules", + tag: "|", + exp: []RuleInfo{}, + err: ErrEmptyRule, + mess: "", + }, + { + name: "error_incorrect_rules", + tag: "rule:cond|rule", + exp: []RuleInfo{}, + err: ErrUnknowRule, + mess: "", + }, + } - require.Equal(t, []RuleInfo{}, rules) - require.EqualError(t, err, ErrEmptyRule.Error()) - }) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + r, err := parseRulesTag(test.tag) - // ошибка: некорректное правило - t.Run("error_incorrect_rules", func(t *testing.T) { - rules, err := parseRulesTag("rule:cond|rule") + require.Equal(t, test.exp, r, test.mess) - require.Equal(t, []RuleInfo{}, rules) - require.EqualError(t, err, ErrUnknowRule.Error()) - }) + if err != nil { + require.EqualError(t, err, test.err.Error(), test.mess) + } else { + require.NoError(t, err, test.mess) + } + }) + } } func TestFieldRulesByTag(t *testing.T) { From c5d1dc29aa525c7842f69ac0d2661cb3cd74eb1e Mon Sep 17 00:00:00 2001 From: DimVlas Date: Mon, 2 Dec 2024 00:03:41 +0300 Subject: [PATCH 07/25] HW09. refactoring rule_test.go --- hw09_struct_validator/rules/errors.go | 14 ++ hw09_struct_validator/rules/rule_test.go | 254 ++++++++++++----------- hw09_struct_validator/rules/rules.go | 59 +++--- hw09_struct_validator/rules/validate.go | 8 +- 4 files changed, 184 insertions(+), 151 deletions(-) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index 86b1c9f..d619bce 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -13,6 +13,20 @@ var ( ErrKindNoRules = errors.New("for this field kind no validation rules") ) +// программные ошибки функций валидации +var ( + // правило применимо только к строкам + ErrOnlyStringRule = errors.New("rule applies only to the string") + // недопустимое условие для правила + ErrInvalidCond = errors.New("invalid condition for the rule") +) + +// ошибки валидации строк +var ( + // длина строки не равна + ErrNotEqualLen = errors.New("length of the string not equal to") +) + // ошибка валидации поля структуры type ValidationError struct { Field string diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index ee62e63..1ca9233 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -3,14 +3,17 @@ package rules import ( "fmt" "reflect" - "strconv" "testing" - "unicode/utf8" "github.com/stretchr/testify/require" ) -func TestFuncValidation(t *testing.T) { +// тестируем только 2 функции пакета rules: fieldRulesByTag и validationFunction, +// и функций валидации из маппы rules +// отстальные функции введены для структуризации кода и не выполняют свои проверки, +// а траслируют результат этих функций + +func TestValidationFunction(t *testing.T) { type testData struct { name string kind reflect.Kind @@ -49,7 +52,7 @@ func TestFuncValidation(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - fn, err := funcValidation(test.kind, test.cond) + fn, err := validationFunction(test.kind, test.cond) if test.expIsNil { require.Nil(t, fn, test.mess) @@ -66,51 +69,69 @@ func TestFuncValidation(t *testing.T) { } } -func TestParseRulesTag(t *testing.T) { +func TestFieldRulesByTag(t *testing.T) { type testData struct { - name string - tag string - exp []RuleInfo - err error - mess string + name string + field string + tag string + exp FieldRules + err error + mess string } tests := []testData{ { - name: "empty_tag", - tag: "", - exp: []RuleInfo{}, + name: "empty_tag", + field: "field", + tag: "", + exp: FieldRules{ + FieldName: "field", + Rules: []RuleInfo{}, + }, err: nil, mess: "should no error for empty tag", }, { - name: "one_rule_tag", - tag: "rule:condition", - exp: []RuleInfo{{Name: "rule", Cond: "condition"}}, + name: "one_rule_tag", + field: "field", + tag: "rule:condition", + exp: FieldRules{ + FieldName: "field", + Rules: []RuleInfo{{Name: "rule", Cond: "condition"}}, + }, err: nil, mess: "should no error for tag with one rule", }, { - name: "two_rule_tag", - tag: "rule1:condition1|rule2:condition2", - exp: []RuleInfo{ - {Name: "rule1", Cond: "condition1"}, - {Name: "rule2", Cond: "condition2"}, - }, + name: "two_rule_tag", + field: "field", + tag: "rule1:condition1|rule2:condition2", + exp: FieldRules{ + FieldName: "field", + Rules: []RuleInfo{ + {Name: "rule1", Cond: "condition1"}, + {Name: "rule2", Cond: "condition2"}, + }}, err: nil, mess: "should no error for tag with two rule", }, { - name: "error_empty_rules", - tag: "|", - exp: []RuleInfo{}, + name: "error_empty_rules", + field: "field", + tag: "|", + exp: FieldRules{ + FieldName: "field", + Rules: []RuleInfo{}}, err: ErrEmptyRule, mess: "", }, { - name: "error_incorrect_rules", - tag: "rule:cond|rule", - exp: []RuleInfo{}, + name: "error_incorrect_rules", + field: "field", + tag: "rule:cond|rule", + exp: FieldRules{ + FieldName: "field", + Rules: []RuleInfo{}}, err: ErrUnknowRule, mess: "", }, @@ -118,7 +139,7 @@ func TestParseRulesTag(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - r, err := parseRulesTag(test.tag) + r, err := fieldRulesByTag(test.field, test.tag) require.Equal(t, test.exp, r, test.mess) @@ -131,98 +152,99 @@ func TestParseRulesTag(t *testing.T) { } } -func TestFieldRulesByTag(t *testing.T) { - // пустой тэг - t.Run("empty_tag", func(t *testing.T) { - rules, err := FieldRulesByTag("fieldName", "") - - require.Equal(t, FieldRules{FieldName: "fieldName", Rules: []RuleInfo{}}, rules, "was expected empty slice RuleInfo") - require.NoError(t, err, "should no error for empty tag") - }) - - // тэг с одним правилом - t.Run("one_rule_tag", func(t *testing.T) { - rules, err := FieldRulesByTag("fieldName", "rule:condition") - - require.Equal(t, FieldRules{FieldName: "fieldName", Rules: []RuleInfo{{Name: "rule", Cond: "condition"}}}, rules) - require.NoError(t, err, "should no error for empty tag") - }) - - // тэг с двумя правилами - t.Run("two_rule_tag", func(t *testing.T) { - rules, err := FieldRulesByTag("fieldName", "rule1:condition1|rule2:condition2") - - exp := []RuleInfo{ - {Name: "rule1", Cond: "condition1"}, - {Name: "rule2", Cond: "condition2"}, - } - - require.Equal(t, exp, rules) - require.NoError(t, err, "should no error for empty tag") - }) - - // ошибка: пустые правила - t.Run("error_empty_rules", func(t *testing.T) { - rules, err := FieldRulesByTag("fieldName", "|") - - require.Equal(t, []RuleInfo{}, rules) - require.EqualError(t, err, ErrEmptyRule.Error()) - }) - - // ошибка: некорректное правило - t.Run("error_incorrect_rules", func(t *testing.T) { - rules, err := FieldRulesByTag("fieldName", "rule:cond|rule") - - require.Equal(t, []RuleInfo{}, rules) - require.EqualError(t, err, ErrUnknowRule.Error()) - }) +type validatorTestData struct { + name string + kind reflect.Kind + rule string + cond string + val reflect.Value + expErr error } -// тестировани функции-правила 'len' для значений типа "string". -func TestStringLen(t *testing.T) { - // Неверный тип значения, передаем int вместо строки - t.Run("len bad int value", func(t *testing.T) { - err := rules[reflect.String]["len"](reflect.ValueOf(123), "0") - - require.EqualError(t, err, "this rule applies only to the string") - }) - - // Неверный тип значения, передаем указатель вместо строки - t.Run("len bad &string value", func(t *testing.T) { - var s string = "asd" - err := rules[reflect.String]["len"](reflect.ValueOf(&s), "0") - - require.EqualError(t, err, "this rule applies only to the string") - }) - - // неверное значение условия для правила - t.Run("len bad condition", func(t *testing.T) { - 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"] - - s := "Мой милый дом!" - l := utf8.RuneCountInString(s) - 1 +func (v *validatorTestData) validatorFunc() Validator { + return validators[v.kind][v.rule] +} - err := f(reflect.ValueOf(s), strconv.Itoa(l)) +var validatorTests = []validatorTestData{ + // Значение типа reflect.String, правило len + { + // неверный тип значения + name: "string_len__err_bad_type_value_int", + kind: reflect.String, + rule: "len", + cond: "0", + val: reflect.ValueOf(123), + expErr: ErrOnlyStringRule, + }, + { + // неверный тип значения + name: "string_len__err_bad_type_value_&string", + kind: reflect.String, + rule: "len", + cond: "0", + val: func() reflect.Value { + s := "abc" + return reflect.ValueOf(&s) + }(), + expErr: ErrOnlyStringRule, + }, + { + // неверное условия для правила + name: "string_len__err_bad_condition", + kind: reflect.String, + rule: "len", + cond: "s", + val: func() reflect.Value { + s := "Мой милый дом!" + return reflect.ValueOf(s) + }(), + expErr: ErrInvalidCond, + }, + { + // проверка провалена - длина не соответствует + name: "string_len__err_validation_len_not_equal", + kind: reflect.String, + rule: "len", + cond: "5", + val: func() reflect.Value { + s := "Мой милый дом!" + return reflect.ValueOf(s) + }(), + expErr: ValidationError{ + Err: fmt.Errorf("%w %s", ErrNotEqualLen, "5"), + }, + }, + { + // проверка провалена - длина не соответствует + name: "string_len__success", + kind: reflect.String, + rule: "len", + cond: "5", + val: func() reflect.Value { + s := "милый" + return reflect.ValueOf(s) + }(), + expErr: nil, + }, +} - require.IsType(t, ValidationError{}, err) - require.EqualError(t, err, fmt.Sprintf("length of the string not equal to %d", l)) - }) +func TestValidator(t *testing.T) { + for _, test := range validatorTests { + t.Run(test.name, func(t *testing.T) { + err := test.validatorFunc()(test.val, test.cond) - // проверка успешна - длина соответствует - t.Run("len success", func(t *testing.T) { - s := "Мой милый дом!" + if test.expErr == nil { + require.NoError(t, err) + return + } - err := rules[reflect.String]["len"](reflect.ValueOf(s), strconv.Itoa(utf8.RuneCountInString(s))) - require.NoError(t, err) - }) + if e, ok := test.expErr.(ValidationError); ok { + require.IsType(t, e, err) + require.ErrorContains(t, err, e.Error()) + return + } + require.ErrorIs(t, err, test.expErr) + }) + } } diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 8877489..06d3df6 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -21,26 +21,27 @@ type FieldRules struct { Rules []RuleInfo // слайс правил проверки } -type ValidateFunc func(v reflect.Value, condition string) error +// описание функции валидации +type Validator func(v reflect.Value, condition string) error -// маппа в которой по типам полей содержится маппа с типами правил и функциями проверки для каждого типа правила -var rules = map[reflect.Kind]map[string]ValidateFunc{ +// маппа в которой по типам полей содержится маппа с типами правил и функциями валидации для каждого типа правила +var validators = map[reflect.Kind]map[string]Validator{ reflect.String: { // 'len:32' - проверка длины строки должна быть 32 символа "len": func(v reflect.Value, condition string) error { if v.Kind() != reflect.String { - // это правило применимо только к строкам - return fmt.Errorf("this rule applies only to the string") + // 'len' правило применимо только к строкам + return fmt.Errorf("'%s' %w", "len", ErrOnlyStringRule) } c, err := strconv.Atoi(condition) if err != nil { - // строка не является валидным условием для правила 'len' - return fmt.Errorf("'%s' is not a valid condition for the 'len' rule", condition) + // 'condition' недопустимое условие для правила 'len' + return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "len") } if utf8.RuneCountInString(v.String()) != c { return ValidationError{ - Err: fmt.Errorf("length of the string not equal to %s", condition), + Err: fmt.Errorf("%w %s", ErrNotEqualLen, condition), } } return nil @@ -69,8 +70,8 @@ var rules = map[reflect.Kind]map[string]ValidateFunc{ } // возвращает функцию валидации для типа kind и правила rule -func funcValidation(kind reflect.Kind, rule string) (ValidateFunc, error) { - r, ok := rules[kind] +func validationFunction(kind reflect.Kind, rule string) (Validator, error) { + r, ok := validators[kind] if !ok { return nil, fmt.Errorf("'%s' %w", kind, ErrKindNoRules) } @@ -83,7 +84,23 @@ func funcValidation(kind reflect.Kind, rule string) (ValidateFunc, error) { return fv, nil } -// парсит полученную строку, возвращая массив структур с описанием правил проверки +// получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName +func fieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { + rls, err := parseRulesTag(fieldTag) + if err != nil { + return FieldRules{ + FieldName: fieldName, + Rules: []RuleInfo{}, + }, err + } + + return FieldRules{ + FieldName: fieldName, + Rules: rls, + }, nil +} + +// парсит полученную строку, возвращая массив структур с описанием правил проверки. // ожидается, что строка имеет вид 'правило:условие|правило:условие|...' func parseRulesTag(rulesTag string) ([]RuleInfo, error) { rulesTag = strings.Trim(rulesTag, " ") @@ -110,23 +127,3 @@ func parseRulesTag(rulesTag string) ([]RuleInfo, error) { return ri, nil } - -// получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName -func FieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { - frs := FieldRules{ - FieldName: fieldName, - Rules: []RuleInfo{}, - } - if fieldTag == "" { - return frs, nil - } - - rls, err := parseRulesTag(fieldTag) - if err != nil { - return frs, err - } - - frs.Rules = rls - - return frs, nil -} diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go index 40afbb7..4e05d6d 100644 --- a/hw09_struct_validator/rules/validate.go +++ b/hw09_struct_validator/rules/validate.go @@ -5,8 +5,8 @@ import ( ) // валидирует структутру. -// возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку -// Паникует, если v не структура +// возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку. +// паникует, если v не структура func ValidateStruct(v reflect.Value) error { cnt := v.NumField() if cnt < 1 { @@ -44,7 +44,7 @@ func ValidateStruct(v reflect.Value) error { // валидирует поле структуры func validateField(fieldInfo reflect.StructField, fieldValue reflect.Value) error { - fieldRules, err := FieldRulesByTag(fieldInfo.Name, fieldInfo.Tag.Get("validate")) + fieldRules, err := fieldRulesByTag(fieldInfo.Name, fieldInfo.Tag.Get("validate")) if err != nil { return err } @@ -63,7 +63,7 @@ func validateFieldValue(fieldValue reflect.Value, fieldRules FieldRules) error { // перебираем все правила for _, rule := range fieldRules.Rules { // получаем функцию валидации - vf, err := funcValidation(fieldValue.Kind(), rule.Name) + vf, err := validationFunction(fieldValue.Kind(), rule.Name) if err != nil { return err } From 9522e304ad67745ad2bb47412e28d620764c5931 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Mon, 2 Dec 2024 23:28:28 +0300 Subject: [PATCH 08/25] HW09. refactoring errors for validator function, tests for string regexp --- hw09_struct_validator/rules/errors.go | 10 ++++- hw09_struct_validator/rules/rule_test.go | 57 +++++++++++++++++++++--- hw09_struct_validator/rules/rules.go | 16 ++++--- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index d619bce..a6ebe01 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -19,12 +19,16 @@ var ( ErrOnlyStringRule = errors.New("rule applies only to the string") // недопустимое условие для правила ErrInvalidCond = errors.New("invalid condition for the rule") + // ошибка компиляции регулярного выражения + ErrRegexpCompile = errors.New("regex compilation error") ) // ошибки валидации строк var ( // длина строки не равна - ErrNotEqualLen = errors.New("length of the string not equal to") + ErrLenNotEqual = errors.New("length of the string not equal to") + // строка не содержит совпадений с регулярным выражением + ErrReExpNotMatch = errors.New("string does not contain any matches to the regular expression") ) // ошибка валидации поля структуры @@ -40,6 +44,10 @@ func (v ValidationError) Error() string { return fmt.Sprintf("%s: %v", v.Field, v.Err) } +func (v ValidationError) Unwrap() error { + return v.Err +} + // слайс ошибок валидации полей структуры type ValidationErrors []ValidationError diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 1ca9233..5105fe3 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -201,7 +201,7 @@ var validatorTests = []validatorTestData{ expErr: ErrInvalidCond, }, { - // проверка провалена - длина не соответствует + // ошибка валидации - длина не соответствует name: "string_len__err_validation_len_not_equal", kind: reflect.String, rule: "len", @@ -211,11 +211,11 @@ var validatorTests = []validatorTestData{ return reflect.ValueOf(s) }(), expErr: ValidationError{ - Err: fmt.Errorf("%w %s", ErrNotEqualLen, "5"), + Err: ErrLenNotEqual, }, }, { - // проверка провалена - длина не соответствует + // успешная валидация name: "string_len__success", kind: reflect.String, rule: "len", @@ -226,6 +226,54 @@ var validatorTests = []validatorTestData{ }(), expErr: nil, }, + // Значение типа reflect.String, правило regexp + { + // неверный тип значения + name: "string_regexp__err_bad_type_value_int", + kind: reflect.String, + rule: "regexp", + cond: "", + val: reflect.ValueOf(123), + expErr: ErrOnlyStringRule, + }, + { + // неверное условия для правила + name: "string_regexp__err_bad_condition", + kind: reflect.String, + rule: "regexp", + cond: "", + val: reflect.ValueOf("Дом, милый дом!"), + expErr: ErrInvalidCond, + }, + { + // неверное регулярное выражение + name: "string_regexp__err_bad_regexp", + kind: reflect.String, + rule: "regexp", + cond: `/[`, + val: reflect.ValueOf("Дом, милый дом!"), + expErr: ErrRegexpCompile, + }, + { + // ошибка валидации - нет совпадения с регулярным выражением + name: "string_regexp__err_validation_regexp_not_match", + kind: reflect.String, + rule: "regexp", + cond: `dam`, + val: reflect.ValueOf("Дом, милый дом!"), + expErr: ValidationError{ + Err: ErrReExpNotMatch, + }, + }, + { + // успешная валидация + name: "string_regexp__success", + kind: reflect.String, + rule: "regexp", + cond: `дом`, + val: reflect.ValueOf("Дом, милый дом!"), + expErr: nil, + }, } func TestValidator(t *testing.T) { @@ -239,8 +287,7 @@ func TestValidator(t *testing.T) { } if e, ok := test.expErr.(ValidationError); ok { - require.IsType(t, e, err) - require.ErrorContains(t, err, e.Error()) + require.ErrorIs(t, err, e.Err) return } diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 06d3df6..3c5475f 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -41,27 +41,31 @@ var validators = map[reflect.Kind]map[string]Validator{ if utf8.RuneCountInString(v.String()) != c { return ValidationError{ - Err: fmt.Errorf("%w %s", ErrNotEqualLen, condition), + Err: fmt.Errorf("%w %s", ErrLenNotEqual, condition), } } 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") + return fmt.Errorf("'%s' %w", "regexp", ErrOnlyStringRule) + } + if condition == "" { + // 'condition' недопустимое условие для правила 'len' + return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "regexp") } pattern, err := regexp.Compile(condition) if err != nil { - return err + return fmt.Errorf("'%s' %w: %w", condition, ErrRegexpCompile, err) } + if !pattern.MatchString(v.String()) { return ValidationError{ - Err: fmt.Errorf("length of the string not equal to %s", condition), + Err: fmt.Errorf("%w '%s'", ErrReExpNotMatch, condition), } } - - return ErrRuleNotImplement + return nil }, "in": func(v reflect.Value, condition string) error { return ErrRuleNotImplement From 04d25561dc3a95c1f9fe92c93bba3231d6b4be25 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Fri, 6 Dec 2024 21:19:22 +0300 Subject: [PATCH 09/25] hw09. rules for int and tests --- hw09_struct_validator/rules/errors.go | 18 +- hw09_struct_validator/rules/rule_test.go | 388 ++++++++++++++++------- hw09_struct_validator/rules/rules.go | 96 +++++- 3 files changed, 385 insertions(+), 117 deletions(-) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index a6ebe01..3ca1c47 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -17,6 +17,8 @@ var ( var ( // правило применимо только к строкам ErrOnlyStringRule = errors.New("rule applies only to the string") + // правило применимо только к целым + ErrOnlyIntRule = errors.New("rule applies only to the int") // недопустимое условие для правила ErrInvalidCond = errors.New("invalid condition for the rule") // ошибка компиляции регулярного выражения @@ -26,9 +28,21 @@ var ( // ошибки валидации строк var ( // длина строки не равна - ErrLenNotEqual = errors.New("length of the string not equal to") + ErrStrLenNotEqual = errors.New("length of the string not equal to") // строка не содержит совпадений с регулярным выражением - ErrReExpNotMatch = errors.New("string does not contain any matches to the regular expression") + ErrStrReExpNotMatch = errors.New("string does not contain any matches to the regular expression") + // строка на входит в список + ErrStrNotIntList = errors.New("string is not in the list") +) + +// ошибки валидации целых +var ( + // целое не может быть меньше условия + ErrIntCantBeLess = errors.New("cannot be less") + // целое не содержит совпадений с регулярным выражением + ErrIntCantBeGreater = errors.New("cannot be greater") + // целое на входит в список + ErrIntNotIntList = errors.New("int is not in the list") ) // ошибка валидации поля структуры diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 5105fe3..a46225a 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -165,119 +165,285 @@ func (v *validatorTestData) validatorFunc() Validator { return validators[v.kind][v.rule] } -var validatorTests = []validatorTestData{ - // Значение типа reflect.String, правило len - { - // неверный тип значения - name: "string_len__err_bad_type_value_int", - kind: reflect.String, - rule: "len", - cond: "0", - val: reflect.ValueOf(123), - expErr: ErrOnlyStringRule, - }, - { - // неверный тип значения - name: "string_len__err_bad_type_value_&string", - kind: reflect.String, - rule: "len", - cond: "0", - val: func() reflect.Value { - s := "abc" - return reflect.ValueOf(&s) - }(), - expErr: ErrOnlyStringRule, - }, - { - // неверное условия для правила - name: "string_len__err_bad_condition", - kind: reflect.String, - rule: "len", - cond: "s", - val: func() reflect.Value { - s := "Мой милый дом!" - return reflect.ValueOf(s) - }(), - expErr: ErrInvalidCond, - }, - { - // ошибка валидации - длина не соответствует - name: "string_len__err_validation_len_not_equal", - kind: reflect.String, - rule: "len", - cond: "5", - val: func() reflect.Value { - s := "Мой милый дом!" - return reflect.ValueOf(s) - }(), - expErr: ValidationError{ - Err: ErrLenNotEqual, - }, - }, - { - // успешная валидация - name: "string_len__success", - kind: reflect.String, - rule: "len", - cond: "5", - val: func() reflect.Value { - s := "милый" - return reflect.ValueOf(s) - }(), - expErr: nil, - }, - // Значение типа reflect.String, правило regexp - { - // неверный тип значения - name: "string_regexp__err_bad_type_value_int", - kind: reflect.String, - rule: "regexp", - cond: "", - val: reflect.ValueOf(123), - expErr: ErrOnlyStringRule, - }, - { - // неверное условия для правила - name: "string_regexp__err_bad_condition", - kind: reflect.String, - rule: "regexp", - cond: "", - val: reflect.ValueOf("Дом, милый дом!"), - expErr: ErrInvalidCond, - }, - { - // неверное регулярное выражение - name: "string_regexp__err_bad_regexp", - kind: reflect.String, - rule: "regexp", - cond: `/[`, - val: reflect.ValueOf("Дом, милый дом!"), - expErr: ErrRegexpCompile, - }, - { - // ошибка валидации - нет совпадения с регулярным выражением - name: "string_regexp__err_validation_regexp_not_match", - kind: reflect.String, - rule: "regexp", - cond: `dam`, - val: reflect.ValueOf("Дом, милый дом!"), - expErr: ValidationError{ - Err: ErrReExpNotMatch, - }, - }, - { - // успешная валидация - name: "string_regexp__success", - kind: reflect.String, - rule: "regexp", - cond: `дом`, - val: reflect.ValueOf("Дом, милый дом!"), - expErr: nil, - }, -} +var ( + // тесты для строк + validatorTestsString = []validatorTestData{ + // len + { + // неверный тип значения + name: "string_len__err_bad_type_value_int", + kind: reflect.String, + rule: "len", + cond: "0", + val: reflect.ValueOf(123), + expErr: ErrOnlyStringRule, + }, + { + // неверный тип значения + name: "string_len__err_bad_type_value_&string", + kind: reflect.String, + rule: "len", + cond: "0", + val: func() reflect.Value { + s := "abc" + return reflect.ValueOf(&s) + }(), + expErr: ErrOnlyStringRule, + }, + { + // неверное условия для правила + name: "string_len__err_bad_condition", + kind: reflect.String, + rule: "len", + cond: "s", + val: func() reflect.Value { + s := "Мой милый дом!" + return reflect.ValueOf(s) + }(), + expErr: ErrInvalidCond, + }, + { + // ошибка валидации - длина не соответствует + name: "string_len__err_validation_len_not_equal", + kind: reflect.String, + rule: "len", + cond: "5", + val: func() reflect.Value { + s := "Мой милый дом!" + return reflect.ValueOf(s) + }(), + expErr: ValidationError{ + Err: ErrStrLenNotEqual, + }, + }, + { + // успешная валидация + name: "string_len__success", + kind: reflect.String, + rule: "len", + cond: "5", + val: func() reflect.Value { + s := "милый" + return reflect.ValueOf(s) + }(), + expErr: nil, + }, + // regexp + { + // неверный тип значения + name: "string_regexp__err_bad_type_value_int", + kind: reflect.String, + rule: "regexp", + cond: "", + val: reflect.ValueOf(123), + expErr: ErrOnlyStringRule, + }, + { + // неверное условия для правила + name: "string_regexp__err_bad_condition", + kind: reflect.String, + rule: "regexp", + cond: "", + val: reflect.ValueOf("Дом, милый дом!"), + expErr: ErrInvalidCond, + }, + { + // неверное регулярное выражение + name: "string_regexp__err_bad_regexp", + kind: reflect.String, + rule: "regexp", + cond: `/[`, + val: reflect.ValueOf("Дом, милый дом!"), + expErr: ErrRegexpCompile, + }, + { + // ошибка валидации - нет совпадения с регулярным выражением + name: "string_regexp__err_validation_regexp_not_match", + kind: reflect.String, + rule: "regexp", + cond: `dam`, + val: reflect.ValueOf("Дом, милый дом!"), + expErr: ValidationError{ + Err: ErrStrReExpNotMatch, + }, + }, + { + // успешная валидация + name: "string_regexp__success", + kind: reflect.String, + rule: "regexp", + cond: `дом`, + val: reflect.ValueOf("Дом, милый дом!"), + expErr: nil, + }, + // in + { + // неверный тип значения + name: "string_in__err_bad_type_value_int", + kind: reflect.String, + rule: "in", + cond: "", + val: reflect.ValueOf(123), + expErr: ErrOnlyStringRule, + }, + { + // неверное условия для правила + name: "string_in__err_bad_condition", + kind: reflect.String, + rule: "in", + cond: "", + val: reflect.ValueOf("милый"), + expErr: ErrInvalidCond, + }, + { + // ошибка валидации - нет значения поля в списке + name: "string_in__err_validation_not_in_list", + kind: reflect.String, + rule: "in", + cond: "sweet,honey", + val: reflect.ValueOf("милый"), + expErr: ValidationError{ + Err: ErrStrNotIntList, + }, + }, + { + // успешная валидация + name: "string_in__success", + kind: reflect.String, + rule: "in", + cond: "sweet,милый", + val: reflect.ValueOf("милый"), + expErr: nil, + }, + } + // тесты для целых + validatorTestsInt = []validatorTestData{ + // min + { + // неверный тип значения + name: "int_min__err_bad_type_value_string", + kind: reflect.Int, + rule: "min", + cond: "10", + val: reflect.ValueOf("123"), + expErr: ErrOnlyIntRule, + }, + { + // неверное условия для правила + name: "int_min__err_bad_condition", + kind: reflect.Int, + rule: "min", + cond: "10,11", + val: reflect.ValueOf(123), + expErr: ErrInvalidCond, + }, + { + // неверное условия для правила + name: "int_min__err_validation_not_less", + kind: reflect.Int, + rule: "min", + cond: "10", + val: reflect.ValueOf(9), + expErr: ValidationError{ + Err: ErrIntCantBeLess, + }, + }, + { + // неверное условия для правила + name: "int_min__succes", + kind: reflect.Int, + rule: "min", + cond: "10", + val: reflect.ValueOf(123), + expErr: nil, + }, + // max + { + // неверный тип значения + name: "int_max__err_bad_type_value_string", + kind: reflect.Int, + rule: "max", + cond: "10", + val: reflect.ValueOf("123"), + expErr: ErrOnlyIntRule, + }, + { + // неверное условия для правила + name: "int_max__err_bad_condition", + kind: reflect.Int, + rule: "max", + cond: " ", + val: reflect.ValueOf(123), + expErr: ErrInvalidCond, + }, + { + // неверное условия для правила + name: "int_max__err_validation_not_less", + kind: reflect.Int, + rule: "max", + cond: "10", + val: reflect.ValueOf(11), + expErr: ValidationError{ + Err: ErrIntCantBeGreater, + }, + }, + { + // неверное условия для правила + name: "int_max__succes", + kind: reflect.Int, + rule: "max", + cond: "10", + val: reflect.ValueOf(9), + expErr: nil, + }, + // in + { + // неверный тип значения + name: "int_in__err_bad_type_value_string", + kind: reflect.Int, + rule: "in", + cond: "10", + val: reflect.ValueOf("123"), + expErr: ErrOnlyIntRule, + }, + { + // неверное условия для правила + name: "int_in__err_bad_condition", + kind: reflect.Int, + rule: "in", + cond: "12,aa,45 ", + val: reflect.ValueOf(123), + expErr: ErrInvalidCond, + }, + { + // провал валидации + name: "int_in__err_validation_not_in_list", + kind: reflect.Int, + rule: "in", + cond: "10,12", + val: reflect.ValueOf(11), + expErr: ValidationError{ + Err: ErrIntNotIntList, + }, + }, + { + // успешная валидация + name: "int_in__succes", + kind: reflect.Int, + rule: "in", + cond: "9,10,11", + val: reflect.ValueOf(9), + expErr: nil, + }, + } +) func TestValidator(t *testing.T) { - for _, test := range validatorTests { + validatorTest := make([]validatorTestData, 0, len(validatorTestsString)+len(validatorTestsInt)) + validatorTest = append(validatorTest, validatorTestsString...) + validatorTest = append(validatorTest, validatorTestsInt...) + + for _, test := range validatorTest { t.Run(test.name, func(t *testing.T) { err := test.validatorFunc()(test.val, test.cond) diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 3c5475f..3ed5689 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -41,7 +41,7 @@ var validators = map[reflect.Kind]map[string]Validator{ if utf8.RuneCountInString(v.String()) != c { return ValidationError{ - Err: fmt.Errorf("%w %s", ErrLenNotEqual, condition), + Err: fmt.Errorf("%w %s", ErrStrLenNotEqual, condition), } } return nil @@ -51,7 +51,7 @@ var validators = map[reflect.Kind]map[string]Validator{ return fmt.Errorf("'%s' %w", "regexp", ErrOnlyStringRule) } if condition == "" { - // 'condition' недопустимое условие для правила 'len' + // 'condition' недопустимое условие для правила 'regexp' return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "regexp") } @@ -62,13 +62,101 @@ var validators = map[reflect.Kind]map[string]Validator{ if !pattern.MatchString(v.String()) { return ValidationError{ - Err: fmt.Errorf("%w '%s'", ErrReExpNotMatch, condition), + Err: fmt.Errorf("%w '%s'", ErrStrReExpNotMatch, condition), } } return nil }, "in": func(v reflect.Value, condition string) error { - return ErrRuleNotImplement + if v.Kind() != reflect.String { + return fmt.Errorf("'%s' %w", "regexp", ErrOnlyStringRule) + } + if condition == "" { + // 'condition' недопустимое условие для правила 'regexp' + return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "in") + } + + if !strings.Contains(condition, v.String()) { + return ValidationError{ + Err: fmt.Errorf("%w '%s'", ErrStrNotIntList, condition), + } + } + + return nil + }, + }, + reflect.Int: { + // 'min:32' - число не может быть меньше 10; + "min": func(v reflect.Value, condition string) error { + if v.Kind() != reflect.Int { + // 'min' правило применимо только к wtksv + return fmt.Errorf("'%s' %w", "min", ErrOnlyIntRule) + } + c, err := strconv.ParseInt(condition, 0, 0) + if err != nil { + // 'condition' недопустимое условие для правила 'min' + return fmt.Errorf("'%s' %w '%s': %w", condition, ErrInvalidCond, "min", err) + } + + if v.Int() < c { + return ValidationError{ + Err: fmt.Errorf("%w %s", ErrIntCantBeLess, condition), + } + } + return nil + }, + // 'max:32' - число не может быть больше 10; + "max": func(v reflect.Value, condition string) error { + if v.Kind() != reflect.Int { + // 'max' правило применимо только к целым + return fmt.Errorf("'%s' %w", "max", ErrOnlyIntRule) + } + c, err := strconv.ParseInt(condition, 0, 0) + if err != nil { + // 'condition' недопустимое условие для правила 'min' + return fmt.Errorf("'%s' %w '%s': %w", condition, ErrInvalidCond, "max", err) + } + + if v.Int() > c { + return ValidationError{ + Err: fmt.Errorf("%w %s", ErrIntCantBeGreater, condition), + } + } + return nil + }, + // 'max:32' - число не может быть больше 10; + "in": func(v reflect.Value, condition string) error { + if v.Kind() != reflect.Int { + // 'in' правило применимо только к целым + return fmt.Errorf("'%s' %w", "in", ErrOnlyIntRule) + } + + cl := strings.Split(condition, ",") + if len(cl) < 1 { + return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "in") + } + + var isValid bool + for _, c := range cl { + i, err := strconv.ParseInt(c, 0, 0) + if err != nil { + // 'condition' недопустимое условие для правила 'in' + return fmt.Errorf("'%s' %w '%s': %w", condition, ErrInvalidCond, "in", err) + } + + if v.Int() == i { + isValid = true + break + } + } + + if !isValid { + return ValidationError{ + Err: fmt.Errorf("%w %s", ErrIntNotIntList, condition), + } + } + + return nil }, }, } From c4294bb6ba5d60e1ae320fa76a6c388c2f848312 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Fri, 6 Dec 2024 23:28:02 +0300 Subject: [PATCH 10/25] hw09. removed kind checking for validation function and tests for these cases --- hw09_struct_validator/rules/errors.go | 8 +-- hw09_struct_validator/rules/rule_test.go | 66 ------------------------ hw09_struct_validator/rules/rules.go | 31 +++-------- 3 files changed, 11 insertions(+), 94 deletions(-) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index 3ca1c47..c0c5805 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -15,10 +15,10 @@ var ( // программные ошибки функций валидации var ( - // правило применимо только к строкам - ErrOnlyStringRule = errors.New("rule applies only to the string") - // правило применимо только к целым - ErrOnlyIntRule = errors.New("rule applies only to the int") + // // правило применимо только к строкам + // ErrOnlyStringRule = errors.New("rule applies only to the string") + // // правило применимо только к целым + // ErrOnlyIntRule = errors.New("rule applies only to the int") // недопустимое условие для правила ErrInvalidCond = errors.New("invalid condition for the rule") // ошибка компиляции регулярного выражения diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index a46225a..75921d9 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -169,27 +169,6 @@ var ( // тесты для строк validatorTestsString = []validatorTestData{ // len - { - // неверный тип значения - name: "string_len__err_bad_type_value_int", - kind: reflect.String, - rule: "len", - cond: "0", - val: reflect.ValueOf(123), - expErr: ErrOnlyStringRule, - }, - { - // неверный тип значения - name: "string_len__err_bad_type_value_&string", - kind: reflect.String, - rule: "len", - cond: "0", - val: func() reflect.Value { - s := "abc" - return reflect.ValueOf(&s) - }(), - expErr: ErrOnlyStringRule, - }, { // неверное условия для правила name: "string_len__err_bad_condition", @@ -229,15 +208,6 @@ var ( expErr: nil, }, // regexp - { - // неверный тип значения - name: "string_regexp__err_bad_type_value_int", - kind: reflect.String, - rule: "regexp", - cond: "", - val: reflect.ValueOf(123), - expErr: ErrOnlyStringRule, - }, { // неверное условия для правила name: "string_regexp__err_bad_condition", @@ -277,15 +247,6 @@ var ( expErr: nil, }, // in - { - // неверный тип значения - name: "string_in__err_bad_type_value_int", - kind: reflect.String, - rule: "in", - cond: "", - val: reflect.ValueOf(123), - expErr: ErrOnlyStringRule, - }, { // неверное условия для правила name: "string_in__err_bad_condition", @@ -319,15 +280,6 @@ var ( // тесты для целых validatorTestsInt = []validatorTestData{ // min - { - // неверный тип значения - name: "int_min__err_bad_type_value_string", - kind: reflect.Int, - rule: "min", - cond: "10", - val: reflect.ValueOf("123"), - expErr: ErrOnlyIntRule, - }, { // неверное условия для правила name: "int_min__err_bad_condition", @@ -358,15 +310,6 @@ var ( expErr: nil, }, // max - { - // неверный тип значения - name: "int_max__err_bad_type_value_string", - kind: reflect.Int, - rule: "max", - cond: "10", - val: reflect.ValueOf("123"), - expErr: ErrOnlyIntRule, - }, { // неверное условия для правила name: "int_max__err_bad_condition", @@ -397,15 +340,6 @@ var ( expErr: nil, }, // in - { - // неверный тип значения - name: "int_in__err_bad_type_value_string", - kind: reflect.Int, - rule: "in", - cond: "10", - val: reflect.ValueOf("123"), - expErr: ErrOnlyIntRule, - }, { // неверное условия для правила name: "int_in__err_bad_condition", diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 3ed5689..70d097b 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -21,7 +21,13 @@ type FieldRules struct { Rules []RuleInfo // слайс правил проверки } -// описание функции валидации +// описание функции валидации. +// функция валидации задается для каждого правила. +// проверяет соответствие значения v условию condition. +// если kind значения v не соответсвует ожидаемому, то скорее всего будет panic; +// если условие condition будет пустым, то функция вернет nil; +// если условие condition будет некорректным, вернет error; +// если занчение v не удовлетворяет условию, вернется ошибка типа ValidationError с пустым полем ValidationError.Field. type Validator func(v reflect.Value, condition string) error // маппа в которой по типам полей содержится маппа с типами правил и функциями валидации для каждого типа правила @@ -29,10 +35,6 @@ var validators = map[reflect.Kind]map[string]Validator{ reflect.String: { // 'len:32' - проверка длины строки должна быть 32 символа "len": func(v reflect.Value, condition string) error { - if v.Kind() != reflect.String { - // 'len' правило применимо только к строкам - return fmt.Errorf("'%s' %w", "len", ErrOnlyStringRule) - } c, err := strconv.Atoi(condition) if err != nil { // 'condition' недопустимое условие для правила 'len' @@ -47,9 +49,6 @@ var validators = map[reflect.Kind]map[string]Validator{ return nil }, "regexp": func(v reflect.Value, condition string) error { - if v.Kind() != reflect.String { - return fmt.Errorf("'%s' %w", "regexp", ErrOnlyStringRule) - } if condition == "" { // 'condition' недопустимое условие для правила 'regexp' return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "regexp") @@ -68,9 +67,6 @@ var validators = map[reflect.Kind]map[string]Validator{ return nil }, "in": func(v reflect.Value, condition string) error { - if v.Kind() != reflect.String { - return fmt.Errorf("'%s' %w", "regexp", ErrOnlyStringRule) - } if condition == "" { // 'condition' недопустимое условие для правила 'regexp' return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "in") @@ -88,10 +84,6 @@ var validators = map[reflect.Kind]map[string]Validator{ reflect.Int: { // 'min:32' - число не может быть меньше 10; "min": func(v reflect.Value, condition string) error { - if v.Kind() != reflect.Int { - // 'min' правило применимо только к wtksv - return fmt.Errorf("'%s' %w", "min", ErrOnlyIntRule) - } c, err := strconv.ParseInt(condition, 0, 0) if err != nil { // 'condition' недопустимое условие для правила 'min' @@ -107,10 +99,6 @@ var validators = map[reflect.Kind]map[string]Validator{ }, // 'max:32' - число не может быть больше 10; "max": func(v reflect.Value, condition string) error { - if v.Kind() != reflect.Int { - // 'max' правило применимо только к целым - return fmt.Errorf("'%s' %w", "max", ErrOnlyIntRule) - } c, err := strconv.ParseInt(condition, 0, 0) if err != nil { // 'condition' недопустимое условие для правила 'min' @@ -126,11 +114,6 @@ var validators = map[reflect.Kind]map[string]Validator{ }, // 'max:32' - число не может быть больше 10; "in": func(v reflect.Value, condition string) error { - if v.Kind() != reflect.Int { - // 'in' правило применимо только к целым - return fmt.Errorf("'%s' %w", "in", ErrOnlyIntRule) - } - cl := strings.Split(condition, ",") if len(cl) < 1 { return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "in") From 0d06673e1239a55c2d3c9bfd9c2a187176a7100f Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sat, 7 Dec 2024 15:00:21 +0300 Subject: [PATCH 11/25] hw09. for golangci-lint --- .golangci.yml | 11 +++++ hw09_struct_validator/rules/errors.go | 26 ++++++------ hw09_struct_validator/rules/rule_test.go | 54 ++++++++++++------------ hw09_struct_validator/rules/rules.go | 36 ++++++++-------- hw09_struct_validator/rules/validate.go | 39 ++++++++--------- 5 files changed, 90 insertions(+), 76 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d38eca5..3cb3aa5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,6 +8,17 @@ linters-settings: funlen: lines: 150 statements: 80 + depguard: + rules: + main: + allow: + - github.com/DimVlas/otus_hw/hw09_struct_validator/rules + - github.com/stretchr/testify/require + - $gostd + gci: + custom-order: true + no-lex-order: true + skip-generated: true issues: exclude-rules: diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index c0c5805..ae98a37 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -13,39 +13,39 @@ var ( ErrKindNoRules = errors.New("for this field kind no validation rules") ) -// программные ошибки функций валидации var ( + // программные ошибки функций валидации. // // правило применимо только к строкам // ErrOnlyStringRule = errors.New("rule applies only to the string") // // правило применимо только к целым // ErrOnlyIntRule = errors.New("rule applies only to the int") - // недопустимое условие для правила + // недопустимое условие для правила. ErrInvalidCond = errors.New("invalid condition for the rule") - // ошибка компиляции регулярного выражения + // ошибка компиляции регулярного выражения. ErrRegexpCompile = errors.New("regex compilation error") ) -// ошибки валидации строк +// ошибки валидации строк. var ( - // длина строки не равна + // длина строки не равна. ErrStrLenNotEqual = errors.New("length of the string not equal to") - // строка не содержит совпадений с регулярным выражением + // строка не содержит совпадений с регулярным выражением. ErrStrReExpNotMatch = errors.New("string does not contain any matches to the regular expression") - // строка на входит в список + // строка на входит в список. ErrStrNotIntList = errors.New("string is not in the list") ) -// ошибки валидации целых +// ошибки валидации целых. var ( - // целое не может быть меньше условия + // целое не может быть меньше условия. ErrIntCantBeLess = errors.New("cannot be less") - // целое не содержит совпадений с регулярным выражением + // целое не содержит совпадений с регулярным выражением. ErrIntCantBeGreater = errors.New("cannot be greater") - // целое на входит в список + // целое на входит в список. ErrIntNotIntList = errors.New("int is not in the list") ) -// ошибка валидации поля структуры +// ошибка валидации поля структуры. type ValidationError struct { Field string Err error @@ -62,7 +62,7 @@ func (v ValidationError) Unwrap() error { return v.Err } -// слайс ошибок валидации полей структуры +// слайс ошибок валидации полей структуры. type ValidationErrors []ValidationError func (v ValidationErrors) Error() string { diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rule_test.go index 75921d9..f09dee2 100644 --- a/hw09_struct_validator/rules/rule_test.go +++ b/hw09_struct_validator/rules/rule_test.go @@ -1,6 +1,7 @@ package rules import ( + "errors" "fmt" "reflect" "testing" @@ -166,11 +167,11 @@ func (v *validatorTestData) validatorFunc() Validator { } var ( - // тесты для строк + // тесты для строк. validatorTestsString = []validatorTestData{ - // len + // len. { - // неверное условия для правила + // неверное условия для правила. name: "string_len__err_bad_condition", kind: reflect.String, rule: "len", @@ -182,7 +183,7 @@ var ( expErr: ErrInvalidCond, }, { - // ошибка валидации - длина не соответствует + // ошибка валидации - длина не соответствует. name: "string_len__err_validation_len_not_equal", kind: reflect.String, rule: "len", @@ -196,7 +197,7 @@ var ( }, }, { - // успешная валидация + // успешная валидация. name: "string_len__success", kind: reflect.String, rule: "len", @@ -207,9 +208,9 @@ var ( }(), expErr: nil, }, - // regexp + // regexp. { - // неверное условия для правила + // неверное условия для правила. name: "string_regexp__err_bad_condition", kind: reflect.String, rule: "regexp", @@ -218,7 +219,7 @@ var ( expErr: ErrInvalidCond, }, { - // неверное регулярное выражение + // неверное регулярное выражение. name: "string_regexp__err_bad_regexp", kind: reflect.String, rule: "regexp", @@ -227,7 +228,7 @@ var ( expErr: ErrRegexpCompile, }, { - // ошибка валидации - нет совпадения с регулярным выражением + // ошибка валидации - нет совпадения с регулярным выражением. name: "string_regexp__err_validation_regexp_not_match", kind: reflect.String, rule: "regexp", @@ -238,7 +239,7 @@ var ( }, }, { - // успешная валидация + // успешная валидация. name: "string_regexp__success", kind: reflect.String, rule: "regexp", @@ -246,9 +247,9 @@ var ( val: reflect.ValueOf("Дом, милый дом!"), expErr: nil, }, - // in + // in. { - // неверное условия для правила + // неверное условия для правила. name: "string_in__err_bad_condition", kind: reflect.String, rule: "in", @@ -257,7 +258,7 @@ var ( expErr: ErrInvalidCond, }, { - // ошибка валидации - нет значения поля в списке + // ошибка валидации - нет значения поля в списке. name: "string_in__err_validation_not_in_list", kind: reflect.String, rule: "in", @@ -268,7 +269,7 @@ var ( }, }, { - // успешная валидация + // успешная валидация. name: "string_in__success", kind: reflect.String, rule: "in", @@ -277,11 +278,11 @@ var ( expErr: nil, }, } - // тесты для целых + // тесты для целых. validatorTestsInt = []validatorTestData{ - // min + // min. { - // неверное условия для правила + // неверное условия для правила. name: "int_min__err_bad_condition", kind: reflect.Int, rule: "min", @@ -290,7 +291,7 @@ var ( expErr: ErrInvalidCond, }, { - // неверное условия для правила + // неверное условия для правила. name: "int_min__err_validation_not_less", kind: reflect.Int, rule: "min", @@ -301,7 +302,7 @@ var ( }, }, { - // неверное условия для правила + // неверное условия для правила. name: "int_min__succes", kind: reflect.Int, rule: "min", @@ -311,7 +312,7 @@ var ( }, // max { - // неверное условия для правила + // неверное условия для правила. name: "int_max__err_bad_condition", kind: reflect.Int, rule: "max", @@ -320,7 +321,7 @@ var ( expErr: ErrInvalidCond, }, { - // неверное условия для правила + // неверное условия для правила. name: "int_max__err_validation_not_less", kind: reflect.Int, rule: "max", @@ -331,7 +332,7 @@ var ( }, }, { - // неверное условия для правила + // неверное условия для правила. name: "int_max__succes", kind: reflect.Int, rule: "max", @@ -341,7 +342,7 @@ var ( }, // in { - // неверное условия для правила + // неверное условия для правила. name: "int_in__err_bad_condition", kind: reflect.Int, rule: "in", @@ -350,7 +351,7 @@ var ( expErr: ErrInvalidCond, }, { - // провал валидации + // провал валидации. name: "int_in__err_validation_not_in_list", kind: reflect.Int, rule: "in", @@ -361,7 +362,7 @@ var ( }, }, { - // успешная валидация + // успешная валидация. name: "int_in__succes", kind: reflect.Int, rule: "in", @@ -386,7 +387,8 @@ func TestValidator(t *testing.T) { return } - if e, ok := test.expErr.(ValidationError); ok { + var e ValidationError + if errors.As(test.expErr, &e) { require.ErrorIs(t, err, e.Err) return } diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 70d097b..1fbe38c 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -9,13 +9,13 @@ import ( "unicode/utf8" ) -// Описание правила проверки +// Описание правила проверки. type RuleInfo struct { Name string // правило проверки Cond string // условие правила проверки } -// правила проверки поля +// правила проверки поля. type FieldRules struct { FieldName string // наименование поля Rules []RuleInfo // слайс правил проверки @@ -30,14 +30,14 @@ type FieldRules struct { // если занчение v не удовлетворяет условию, вернется ошибка типа ValidationError с пустым полем ValidationError.Field. type Validator func(v reflect.Value, condition string) error -// маппа в которой по типам полей содержится маппа с типами правил и функциями валидации для каждого типа правила +// маппа в которой по типам полей содержится маппа с типами правил и функциями валидации для каждого типа правила. var validators = map[reflect.Kind]map[string]Validator{ reflect.String: { - // 'len:32' - проверка длины строки должна быть 32 символа + // 'len:32' - проверка длины строки должна быть 32 символа. "len": func(v reflect.Value, condition string) error { c, err := strconv.Atoi(condition) if err != nil { - // 'condition' недопустимое условие для правила 'len' + // 'condition' недопустимое условие для правила 'len'. return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "len") } @@ -50,7 +50,7 @@ var validators = map[reflect.Kind]map[string]Validator{ }, "regexp": func(v reflect.Value, condition string) error { if condition == "" { - // 'condition' недопустимое условие для правила 'regexp' + // 'condition' недопустимое условие для правила 'regexp'. return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "regexp") } @@ -68,7 +68,7 @@ var validators = map[reflect.Kind]map[string]Validator{ }, "in": func(v reflect.Value, condition string) error { if condition == "" { - // 'condition' недопустимое условие для правила 'regexp' + // 'condition' недопустимое условие для правила 'regexp'. return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "in") } @@ -82,11 +82,11 @@ var validators = map[reflect.Kind]map[string]Validator{ }, }, reflect.Int: { - // 'min:32' - число не может быть меньше 10; + // 'min:32' - число не может быть меньше 10. "min": func(v reflect.Value, condition string) error { c, err := strconv.ParseInt(condition, 0, 0) if err != nil { - // 'condition' недопустимое условие для правила 'min' + // 'condition' недопустимое условие для правила 'min'. return fmt.Errorf("'%s' %w '%s': %w", condition, ErrInvalidCond, "min", err) } @@ -97,11 +97,11 @@ var validators = map[reflect.Kind]map[string]Validator{ } return nil }, - // 'max:32' - число не может быть больше 10; + // 'max:32' - число не может быть больше 10. "max": func(v reflect.Value, condition string) error { c, err := strconv.ParseInt(condition, 0, 0) if err != nil { - // 'condition' недопустимое условие для правила 'min' + // 'condition' недопустимое условие для правила 'min'. return fmt.Errorf("'%s' %w '%s': %w", condition, ErrInvalidCond, "max", err) } @@ -112,7 +112,7 @@ var validators = map[reflect.Kind]map[string]Validator{ } return nil }, - // 'max:32' - число не может быть больше 10; + // 'max:32' - число не может быть больше 10. "in": func(v reflect.Value, condition string) error { cl := strings.Split(condition, ",") if len(cl) < 1 { @@ -123,7 +123,7 @@ var validators = map[reflect.Kind]map[string]Validator{ for _, c := range cl { i, err := strconv.ParseInt(c, 0, 0) if err != nil { - // 'condition' недопустимое условие для правила 'in' + // 'condition' недопустимое условие для правила 'in'. return fmt.Errorf("'%s' %w '%s': %w", condition, ErrInvalidCond, "in", err) } @@ -144,7 +144,7 @@ var validators = map[reflect.Kind]map[string]Validator{ }, } -// возвращает функцию валидации для типа kind и правила rule +// возвращает функцию валидации для типа kind и правила rule. func validationFunction(kind reflect.Kind, rule string) (Validator, error) { r, ok := validators[kind] if !ok { @@ -159,7 +159,7 @@ func validationFunction(kind reflect.Kind, rule string) (Validator, error) { return fv, nil } -// получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName +// получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName. func fieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { rls, err := parseRulesTag(fieldTag) if err != nil { @@ -176,18 +176,18 @@ func fieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { } // парсит полученную строку, возвращая массив структур с описанием правил проверки. -// ожидается, что строка имеет вид 'правило:условие|правило:условие|...' +// ожидается, что строка имеет вид 'правило:условие|правило:условие|...'. func parseRulesTag(rulesTag string) ([]RuleInfo, error) { rulesTag = strings.Trim(rulesTag, " ") if rulesTag == "" { return []RuleInfo{}, nil } - // Разбили на отдельные описания правила: строки вида 'правило:условие' + // Разбили на отдельные описания правила: строки вида 'правило:условие'. rs := strings.Split(rulesTag, "|") ri := []RuleInfo{} - // из каждого описания правила выделяем имя правила и условие + // из каждого описания правила выделяем имя правила и условие. for _, r := range rs { if len(r) == 0 { return []RuleInfo{}, ErrEmptyRule diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go index 4e05d6d..51890c9 100644 --- a/hw09_struct_validator/rules/validate.go +++ b/hw09_struct_validator/rules/validate.go @@ -1,12 +1,13 @@ package rules import ( + "errors" "reflect" ) // валидирует структутру. // возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку. -// паникует, если v не структура +// паникует, если v не структура. func ValidateStruct(v reflect.Value) error { cnt := v.NumField() if cnt < 1 { @@ -17,21 +18,20 @@ func ValidateStruct(v reflect.Value) error { // идем по полям структуры for i := range cnt { - f := v.Type().Field(i) if !f.IsExported() { // приватное поле continue } - // валидируем поле структуры + // валидируем поле структуры. if err := validateField(f, v.Field(i)); err != nil { - switch e := err.(type) { - case ValidationErrors: + var e ValidationErrors + if errors.As(err, &e) { errStructValid = append(errStructValid, e...) - default: - return err + continue } + return err } } @@ -42,13 +42,13 @@ func ValidateStruct(v reflect.Value) error { return nil } -// валидирует поле структуры +// валидирует поле структуры. func validateField(fieldInfo reflect.StructField, fieldValue reflect.Value) error { fieldRules, err := fieldRulesByTag(fieldInfo.Name, fieldInfo.Tag.Get("validate")) if err != nil { return err } - // если не правил, то и проверять нечего + // если не правил, то и проверять нечего. if len(fieldRules.Rules) < 1 { return nil } @@ -56,35 +56,36 @@ func validateField(fieldInfo reflect.StructField, fieldValue reflect.Value) erro return validateFieldValue(fieldValue, fieldRules) } -// валидируем поле со значение fieldValue, согласно правил описанных fieldRules +// валидируем поле со значение fieldValue, согласно правил описанных fieldRules. func validateFieldValue(fieldValue reflect.Value, fieldRules FieldRules) error { var errFields = ValidationErrors{} // перебираем все правила for _, rule := range fieldRules.Rules { - // получаем функцию валидации + // получаем функцию валидации. vf, err := validationFunction(fieldValue.Kind(), rule.Name) if err != nil { return err } - // проверяем fieldValue функцией валидации + // проверяем fieldValue функцией валидации. err = vf(fieldValue, rule.Cond) - // если нет ошибок - переходим к следующему правилу + // если нет ошибок - переходим к следующему правилу. if err == nil { continue } - // если ошибка валидации, сохраняем ее в массив ошибок валидации - if vErr, ok := err.(ValidationError); ok { - vErr.Field = fieldRules.FieldName - errFields = append(errFields, vErr) + // если ошибка валидации, сохраняем ее в массив ошибок валидации. + var e ValidationError + if errors.As(err, &e) { + e.Field = fieldRules.FieldName + errFields = append(errFields, e) continue } - // если программная ошибка - возращаем ее, выходим + // если программная ошибка - возращаем ее, выходим. return err } - // если были ошибки валидации - возращаем их + // если были ошибки валидации - возращаем их. if len(errFields) > 0 { return errFields } From bbdd4582e4015dcaee00c1aa9258aaef47124149 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sun, 8 Dec 2024 22:57:51 +0300 Subject: [PATCH 12/25] hw09. refactor validate.go, add vaildate Slice --- .../rules/{rule_test.go => rules_test.go} | 0 hw09_struct_validator/rules/validate.go | 68 +++++++++++++------ hw09_struct_validator/rules/validate_test.go | 1 + 3 files changed, 48 insertions(+), 21 deletions(-) rename hw09_struct_validator/rules/{rule_test.go => rules_test.go} (100%) create mode 100644 hw09_struct_validator/rules/validate_test.go diff --git a/hw09_struct_validator/rules/rule_test.go b/hw09_struct_validator/rules/rules_test.go similarity index 100% rename from hw09_struct_validator/rules/rule_test.go rename to hw09_struct_validator/rules/rules_test.go diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go index 51890c9..adb32c7 100644 --- a/hw09_struct_validator/rules/validate.go +++ b/hw09_struct_validator/rules/validate.go @@ -24,15 +24,21 @@ func ValidateStruct(v reflect.Value) error { continue } - // валидируем поле структуры. - if err := validateField(f, v.Field(i)); err != nil { - var e ValidationErrors - if errors.As(err, &e) { - errStructValid = append(errStructValid, e...) - continue - } + // получаем набор правил для поля + fieldRules, err := fieldRulesByTag(f.Name, f.Tag.Get("validate")) + if err != nil { + return err + } + // если нет правил, то и проверять нечего. + if len(fieldRules.Rules) < 1 { + return nil + } + + errField, err := validateField(v, fieldRules) + if err != nil { return err } + errStructValid = append(errStructValid, errField...) } if len(errStructValid) > 0 { @@ -43,21 +49,41 @@ func ValidateStruct(v reflect.Value) error { } // валидирует поле структуры. -func validateField(fieldInfo reflect.StructField, fieldValue reflect.Value) error { - fieldRules, err := fieldRulesByTag(fieldInfo.Name, fieldInfo.Tag.Get("validate")) - if err != nil { - return err +func validateField(fieldValue reflect.Value, rules FieldRules) (ValidationErrors, error) { + switch fieldValue.Kind() { + case reflect.Slice, reflect.Array: + return validateSlice(fieldValue, rules) + default: + return validateValue(fieldValue, rules) } - // если не правил, то и проверять нечего. - if len(fieldRules.Rules) < 1 { - return nil +} + +func validateSlice(fieldValue reflect.Value, rules FieldRules) (ValidationErrors, error) { + l := fieldValue.Len() + if l < 1 { + return nil, nil + } + + var vErr = make(ValidationErrors, l) + for i := 0; i < l; i++ { + v, err := validateValue(fieldValue.Index(i), rules) + if err != nil { + return nil, err + } + if v != nil { + vErr = append(vErr, v...) + } + } + + if len(vErr) > 0 { + return vErr, nil } - return validateFieldValue(fieldValue, fieldRules) + return nil, nil } // валидируем поле со значение fieldValue, согласно правил описанных fieldRules. -func validateFieldValue(fieldValue reflect.Value, fieldRules FieldRules) error { +func validateValue(fieldValue reflect.Value, fieldRules FieldRules) (ValidationErrors, error) { var errFields = ValidationErrors{} // перебираем все правила @@ -65,11 +91,11 @@ func validateFieldValue(fieldValue reflect.Value, fieldRules FieldRules) error { // получаем функцию валидации. vf, err := validationFunction(fieldValue.Kind(), rule.Name) if err != nil { - return err + return nil, err } - // проверяем fieldValue функцией валидации. err = vf(fieldValue, rule.Cond) + // если нет ошибок - переходим к следующему правилу. if err == nil { continue @@ -82,13 +108,13 @@ func validateFieldValue(fieldValue reflect.Value, fieldRules FieldRules) error { continue } // если программная ошибка - возращаем ее, выходим. - return err + return nil, err } // если были ошибки валидации - возращаем их. if len(errFields) > 0 { - return errFields + return errFields, nil } - return nil + return nil, nil } diff --git a/hw09_struct_validator/rules/validate_test.go b/hw09_struct_validator/rules/validate_test.go new file mode 100644 index 0000000..1e8c7fa --- /dev/null +++ b/hw09_struct_validator/rules/validate_test.go @@ -0,0 +1 @@ +package rules From 5ad3b5a1a90758138911f4f446e9dc1fa7e0ae4b Mon Sep 17 00:00:00 2001 From: DimVlas Date: Tue, 10 Dec 2024 22:13:57 +0300 Subject: [PATCH 13/25] hw09. add validate nested struct --- hw09_struct_validator/rules/validate.go | 22 ++++++++++++++++++++ hw09_struct_validator/rules/validate_test.go | 1 - 2 files changed, 22 insertions(+), 1 deletion(-) delete mode 100644 hw09_struct_validator/rules/validate_test.go diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go index adb32c7..24a4374 100644 --- a/hw09_struct_validator/rules/validate.go +++ b/hw09_struct_validator/rules/validate.go @@ -53,11 +53,33 @@ func validateField(fieldValue reflect.Value, rules FieldRules) (ValidationErrors switch fieldValue.Kind() { case reflect.Slice, reflect.Array: return validateSlice(fieldValue, rules) + case reflect.Struct: + if len(rules.Rules) == 1 && rules.Rules[0].Name == "nested" { + return validateStruct(fieldValue, rules) + } + return nil, nil default: return validateValue(fieldValue, rules) } } +// валидирует вложенную структуру +func validateStruct(fieldValue reflect.Value, _ FieldRules) (ValidationErrors, error) { + err := ValidateStruct(fieldValue) + + if err != nil { + var e ValidationErrors + if errors.As(err, &e) { + return e, nil + } + + return nil, err + } + + return nil, nil +} + +// валидирует slice или массив func validateSlice(fieldValue reflect.Value, rules FieldRules) (ValidationErrors, error) { l := fieldValue.Len() if l < 1 { diff --git a/hw09_struct_validator/rules/validate_test.go b/hw09_struct_validator/rules/validate_test.go deleted file mode 100644 index 1e8c7fa..0000000 --- a/hw09_struct_validator/rules/validate_test.go +++ /dev/null @@ -1 +0,0 @@ -package rules From dd6eabd2579eec89b112ca99ab93e3c8e4b1829c Mon Sep 17 00:00:00 2001 From: DimVlas Date: Tue, 10 Dec 2024 23:43:37 +0300 Subject: [PATCH 14/25] hw09. add tests for Validate --- hw09_struct_validator/rules/errors.go | 12 +++++- hw09_struct_validator/rules/rules.go | 2 +- hw09_struct_validator/rules/validate.go | 6 ++- hw09_struct_validator/validator_test.go | 57 ++++++++++++++++++++++--- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index ae98a37..9de18fc 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -3,6 +3,7 @@ package rules import ( "errors" "fmt" + "strings" ) var ( @@ -70,5 +71,14 @@ func (v ValidationErrors) Error() string { if cnt < 1 { return "" } - return fmt.Sprintf("%d structure validation errors found", cnt) + + return func() string { + s := strings.Builder{} + for _, e := range v { + s.WriteString(fmt.Sprintf("field %s: %s\n", e.Field, e.Err.Error())) + } + return s.String() + }() + + //fmt.Sprintf("%d structure validation errors found", cnt) } diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 1fbe38c..412afdb 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -112,7 +112,7 @@ var validators = map[reflect.Kind]map[string]Validator{ } return nil }, - // 'max:32' - число не может быть больше 10. + // 'in:32,33' - число не входит в список 32,33. "in": func(v reflect.Value, condition string) error { cl := strings.Split(condition, ",") if len(cl) < 1 { diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go index 24a4374..395f339 100644 --- a/hw09_struct_validator/rules/validate.go +++ b/hw09_struct_validator/rules/validate.go @@ -29,12 +29,14 @@ func ValidateStruct(v reflect.Value) error { if err != nil { return err } + // если нет правил, то и проверять нечего. if len(fieldRules.Rules) < 1 { - return nil + continue } - errField, err := validateField(v, fieldRules) + errField, err := validateField(v.Field(i), fieldRules) + if err != nil { return err } diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go index dd20df1..f9fdd87 100644 --- a/hw09_struct_validator/validator_test.go +++ b/hw09_struct_validator/validator_test.go @@ -4,6 +4,10 @@ import ( "encoding/json" "fmt" "testing" + + "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type UserRole string @@ -42,10 +46,45 @@ func TestValidate(t *testing.T) { expectedErr error }{ { - // Place your code here. + in: Response{ + Code: 200, + Body: "{}", + }, + expectedErr: nil, + }, + { + in: Response{ + Code: 100, + Body: "{}", + }, + expectedErr: rules.ValidationErrors{ + rules.ValidationError{ + Field: "Code", + Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotIntList), + }, + }, + }, + { + in: Token{}, + expectedErr: nil, + }, + { + in: App{ + Version: "qwert", + }, + expectedErr: nil, + }, + { + in: App{ + Version: "qwerty", + }, + expectedErr: rules.ValidationErrors{ + rules.ValidationError{ + Field: "Version", + Err: fmt.Errorf("%w 5", rules.ErrStrLenNotEqual), + }, + }, }, - // ... - // Place your code here. } for i, tt := range tests { @@ -53,8 +92,16 @@ func TestValidate(t *testing.T) { tt := tt t.Parallel() - // Place your code here. - _ = tt + err := Validate(tt.in) + + if tt.expectedErr == nil { + require.NoError(t, err) + return + } + + if assert.Error(t, err) { + require.EqualError(t, err, tt.expectedErr.Error()) + } }) } } From d5329800c9658437c866cff7e2332fb4824f5b4e Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sat, 14 Dec 2024 12:35:09 +0300 Subject: [PATCH 15/25] hw09. add tests --- hw09_struct_validator/rules/errors.go | 4 +-- hw09_struct_validator/rules/rules.go | 4 +-- hw09_struct_validator/rules/rules_test.go | 4 +-- hw09_struct_validator/rules/validate.go | 16 ++++----- hw09_struct_validator/validator_test.go | 43 ++++++++++++++++++++++- 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index 9de18fc..0332c8a 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -33,7 +33,7 @@ var ( // строка не содержит совпадений с регулярным выражением. ErrStrReExpNotMatch = errors.New("string does not contain any matches to the regular expression") // строка на входит в список. - ErrStrNotIntList = errors.New("string is not in the list") + ErrStrNotInList = errors.New("string is not in the list") ) // ошибки валидации целых. @@ -43,7 +43,7 @@ var ( // целое не содержит совпадений с регулярным выражением. ErrIntCantBeGreater = errors.New("cannot be greater") // целое на входит в список. - ErrIntNotIntList = errors.New("int is not in the list") + ErrIntNotInList = errors.New("int is not in the list") ) // ошибка валидации поля структуры. diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 412afdb..5e008b3 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -74,7 +74,7 @@ var validators = map[reflect.Kind]map[string]Validator{ if !strings.Contains(condition, v.String()) { return ValidationError{ - Err: fmt.Errorf("%w '%s'", ErrStrNotIntList, condition), + Err: fmt.Errorf("%w '%s'", ErrStrNotInList, condition), } } @@ -135,7 +135,7 @@ var validators = map[reflect.Kind]map[string]Validator{ if !isValid { return ValidationError{ - Err: fmt.Errorf("%w %s", ErrIntNotIntList, condition), + Err: fmt.Errorf("%w %s", ErrIntNotInList, condition), } } diff --git a/hw09_struct_validator/rules/rules_test.go b/hw09_struct_validator/rules/rules_test.go index f09dee2..9f4eed3 100644 --- a/hw09_struct_validator/rules/rules_test.go +++ b/hw09_struct_validator/rules/rules_test.go @@ -265,7 +265,7 @@ var ( cond: "sweet,honey", val: reflect.ValueOf("милый"), expErr: ValidationError{ - Err: ErrStrNotIntList, + Err: ErrStrNotInList, }, }, { @@ -358,7 +358,7 @@ var ( cond: "10,12", val: reflect.ValueOf(11), expErr: ValidationError{ - Err: ErrIntNotIntList, + Err: ErrIntNotInList, }, }, { diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go index 395f339..eb91816 100644 --- a/hw09_struct_validator/rules/validate.go +++ b/hw09_struct_validator/rules/validate.go @@ -15,7 +15,6 @@ func ValidateStruct(v reflect.Value) error { } var errStructValid = ValidationErrors{} - // идем по полям структуры for i := range cnt { f := v.Type().Field(i) @@ -29,18 +28,18 @@ func ValidateStruct(v reflect.Value) error { if err != nil { return err } - // если нет правил, то и проверять нечего. if len(fieldRules.Rules) < 1 { continue } errField, err := validateField(v.Field(i), fieldRules) - if err != nil { return err } - errStructValid = append(errStructValid, errField...) + if len(errField) > 0 { + errStructValid = append(errStructValid, errField...) + } } if len(errStructValid) > 0 { @@ -83,13 +82,14 @@ func validateStruct(fieldValue reflect.Value, _ FieldRules) (ValidationErrors, e // валидирует slice или массив func validateSlice(fieldValue reflect.Value, rules FieldRules) (ValidationErrors, error) { - l := fieldValue.Len() - if l < 1 { + ln := fieldValue.Len() + if ln < 1 { return nil, nil } - var vErr = make(ValidationErrors, l) - for i := 0; i < l; i++ { + var vErr = make(ValidationErrors, 0) + for i := 0; i < ln; i++ { + v, err := validateValue(fieldValue.Index(i), rules) if err != nil { return nil, err diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go index f9fdd87..a3632d7 100644 --- a/hw09_struct_validator/validator_test.go +++ b/hw09_struct_validator/validator_test.go @@ -60,7 +60,7 @@ func TestValidate(t *testing.T) { expectedErr: rules.ValidationErrors{ rules.ValidationError{ Field: "Code", - Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotIntList), + Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotInList), }, }, }, @@ -85,6 +85,47 @@ func TestValidate(t *testing.T) { }, }, }, + { + in: User{ + ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + Name: "User1", + Age: 18, + Email: "User1@mail.com", + Role: "admin", + Phones: []string{"12345678901", "98765432101"}, + meta: json.RawMessage(``), + }, + expectedErr: nil, + }, + { + in: User{ + ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + Name: "User1", + Age: 16, + Email: "User1@mail.com.dot", + Role: "employee", + Phones: []string{"12345678901", "9876543210"}, + meta: json.RawMessage(``), + }, + expectedErr: rules.ValidationErrors{ + rules.ValidationError{ + Field: "Age", + Err: fmt.Errorf("%w 18", rules.ErrIntCantBeLess), + }, + rules.ValidationError{ + Field: "Email", + Err: fmt.Errorf("%w %s", rules.ErrStrReExpNotMatch, "'^\\w+@\\w+\\.\\w+$'"), + }, + rules.ValidationError{ + Field: "Role", + Err: fmt.Errorf("%w %s", rules.ErrStrNotInList, "'admin,stuff'"), + }, + rules.ValidationError{ + Field: "Phones", + Err: fmt.Errorf("%w %v", rules.ErrStrLenNotEqual, 11), + }, + }, + }, } for i, tt := range tests { From f8e33eeffc75e01fe699afd379cb968c8882bb91 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sat, 14 Dec 2024 22:18:19 +0300 Subject: [PATCH 16/25] hw09. package rules full cover --- hw09_struct_validator/rules/errors.go | 8 +- hw09_struct_validator/rules/errors_test.go | 77 +++++++++++ hw09_struct_validator/rules/rules.go | 8 +- hw09_struct_validator/rules/rules_test.go | 13 +- hw09_struct_validator/rules/validate.go | 144 --------------------- 5 files changed, 93 insertions(+), 157 deletions(-) create mode 100644 hw09_struct_validator/rules/errors_test.go delete mode 100644 hw09_struct_validator/rules/validate.go diff --git a/hw09_struct_validator/rules/errors.go b/hw09_struct_validator/rules/errors.go index 0332c8a..3243b91 100644 --- a/hw09_struct_validator/rules/errors.go +++ b/hw09_struct_validator/rules/errors.go @@ -52,6 +52,9 @@ type ValidationError struct { Err error } +// слайс ошибок валидации полей структуры. +type ValidationErrors []ValidationError + func (v ValidationError) Error() string { if len(v.Field) == 0 { return fmt.Sprintf("%v", v.Err) @@ -63,9 +66,6 @@ func (v ValidationError) Unwrap() error { return v.Err } -// слайс ошибок валидации полей структуры. -type ValidationErrors []ValidationError - func (v ValidationErrors) Error() string { cnt := len(v) if cnt < 1 { @@ -79,6 +79,4 @@ func (v ValidationErrors) Error() string { } return s.String() }() - - //fmt.Sprintf("%d structure validation errors found", cnt) } diff --git a/hw09_struct_validator/rules/errors_test.go b/hw09_struct_validator/rules/errors_test.go new file mode 100644 index 0000000..ac06abb --- /dev/null +++ b/hw09_struct_validator/rules/errors_test.go @@ -0,0 +1,77 @@ +package rules + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidationError(t *testing.T) { + tests := []struct { + name string + data ValidationError + exp string + }{ + { + name: "ValidationError_full", + data: ValidationError{ + Field: "field", + Err: errors.New("test error"), + }, + exp: "field: test error", + }, + { + name: "ValidationError_without_field", + data: ValidationError{ + Field: "", + Err: errors.New("test error"), + }, + exp: "test error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := test.data.Error() + + require.Equal(t, test.exp, s) + }) + } +} + +func TestValidationErrors(t *testing.T) { + tests := []struct { + name string + data ValidationErrors + exp string + }{ + { + name: "ValidationErrors_full", + data: ValidationErrors{ + { + Field: "f1", + Err: errors.New("error1"), + }, + { + Field: "f2", + Err: errors.New("error2"), + }, + }, + exp: "field f1: error1\nfield f2: error2\n", + }, + { + name: "ValidationErrors_empty", + data: ValidationErrors{}, + exp: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := test.data.Error() + + require.Equal(t, test.exp, s) + }) + } +} diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 5e008b3..3635499 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -115,10 +115,6 @@ var validators = map[reflect.Kind]map[string]Validator{ // 'in:32,33' - число не входит в список 32,33. "in": func(v reflect.Value, condition string) error { cl := strings.Split(condition, ",") - if len(cl) < 1 { - return fmt.Errorf("'%s' %w '%s'", condition, ErrInvalidCond, "in") - } - var isValid bool for _, c := range cl { i, err := strconv.ParseInt(c, 0, 0) @@ -145,7 +141,7 @@ var validators = map[reflect.Kind]map[string]Validator{ } // возвращает функцию валидации для типа kind и правила rule. -func validationFunction(kind reflect.Kind, rule string) (Validator, error) { +func ValidationFunction(kind reflect.Kind, rule string) (Validator, error) { r, ok := validators[kind] if !ok { return nil, fmt.Errorf("'%s' %w", kind, ErrKindNoRules) @@ -160,7 +156,7 @@ func validationFunction(kind reflect.Kind, rule string) (Validator, error) { } // получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName. -func fieldRulesByTag(fieldName string, fieldTag string) (FieldRules, error) { +func RulesByTag(fieldName string, fieldTag string) (FieldRules, error) { rls, err := parseRulesTag(fieldTag) if err != nil { return FieldRules{ diff --git a/hw09_struct_validator/rules/rules_test.go b/hw09_struct_validator/rules/rules_test.go index 9f4eed3..8958daa 100644 --- a/hw09_struct_validator/rules/rules_test.go +++ b/hw09_struct_validator/rules/rules_test.go @@ -53,7 +53,7 @@ func TestValidationFunction(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - fn, err := validationFunction(test.kind, test.cond) + fn, err := ValidationFunction(test.kind, test.cond) if test.expIsNil { require.Nil(t, fn, test.mess) @@ -140,7 +140,7 @@ func TestFieldRulesByTag(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - r, err := fieldRulesByTag(test.field, test.tag) + r, err := RulesByTag(test.field, test.tag) require.Equal(t, test.exp, r, test.mess) @@ -350,6 +350,15 @@ var ( val: reflect.ValueOf(123), expErr: ErrInvalidCond, }, + { + // неверное пустое условия для правила. + name: "int_in__err_empty_bad_condition", + kind: reflect.Int, + rule: "in", + cond: "", + val: reflect.ValueOf(123), + expErr: ErrInvalidCond, + }, { // провал валидации. name: "int_in__err_validation_not_in_list", diff --git a/hw09_struct_validator/rules/validate.go b/hw09_struct_validator/rules/validate.go deleted file mode 100644 index eb91816..0000000 --- a/hw09_struct_validator/rules/validate.go +++ /dev/null @@ -1,144 +0,0 @@ -package rules - -import ( - "errors" - "reflect" -) - -// валидирует структутру. -// возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку. -// паникует, если v не структура. -func ValidateStruct(v reflect.Value) error { - cnt := v.NumField() - if cnt < 1 { - return nil - } - - var errStructValid = ValidationErrors{} - // идем по полям структуры - for i := range cnt { - f := v.Type().Field(i) - - if !f.IsExported() { // приватное поле - continue - } - - // получаем набор правил для поля - fieldRules, err := fieldRulesByTag(f.Name, f.Tag.Get("validate")) - if err != nil { - return err - } - // если нет правил, то и проверять нечего. - if len(fieldRules.Rules) < 1 { - continue - } - - errField, err := validateField(v.Field(i), fieldRules) - if err != nil { - return err - } - if len(errField) > 0 { - errStructValid = append(errStructValid, errField...) - } - } - - if len(errStructValid) > 0 { - return errStructValid - } - - return nil -} - -// валидирует поле структуры. -func validateField(fieldValue reflect.Value, rules FieldRules) (ValidationErrors, error) { - switch fieldValue.Kind() { - case reflect.Slice, reflect.Array: - return validateSlice(fieldValue, rules) - case reflect.Struct: - if len(rules.Rules) == 1 && rules.Rules[0].Name == "nested" { - return validateStruct(fieldValue, rules) - } - return nil, nil - default: - return validateValue(fieldValue, rules) - } -} - -// валидирует вложенную структуру -func validateStruct(fieldValue reflect.Value, _ FieldRules) (ValidationErrors, error) { - err := ValidateStruct(fieldValue) - - if err != nil { - var e ValidationErrors - if errors.As(err, &e) { - return e, nil - } - - return nil, err - } - - return nil, nil -} - -// валидирует slice или массив -func validateSlice(fieldValue reflect.Value, rules FieldRules) (ValidationErrors, error) { - ln := fieldValue.Len() - if ln < 1 { - return nil, nil - } - - var vErr = make(ValidationErrors, 0) - for i := 0; i < ln; i++ { - - v, err := validateValue(fieldValue.Index(i), rules) - if err != nil { - return nil, err - } - if v != nil { - vErr = append(vErr, v...) - } - } - - if len(vErr) > 0 { - return vErr, nil - } - - return nil, nil -} - -// валидируем поле со значение fieldValue, согласно правил описанных fieldRules. -func validateValue(fieldValue reflect.Value, fieldRules FieldRules) (ValidationErrors, error) { - var errFields = ValidationErrors{} - - // перебираем все правила - for _, rule := range fieldRules.Rules { - // получаем функцию валидации. - vf, err := validationFunction(fieldValue.Kind(), rule.Name) - if err != nil { - return nil, err - } - // проверяем fieldValue функцией валидации. - err = vf(fieldValue, rule.Cond) - - // если нет ошибок - переходим к следующему правилу. - if err == nil { - continue - } - // если ошибка валидации, сохраняем ее в массив ошибок валидации. - var e ValidationError - if errors.As(err, &e) { - e.Field = fieldRules.FieldName - errFields = append(errFields, e) - continue - } - // если программная ошибка - возращаем ее, выходим. - return nil, err - } - - // если были ошибки валидации - возращаем их. - if len(errFields) > 0 { - return errFields, nil - } - - return nil, nil -} From 6a10595c78191fda97fba5ae7dfade48b5c21043 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Sat, 14 Dec 2024 23:35:24 +0300 Subject: [PATCH 17/25] hw09. validator test cover 77 --- hw09_struct_validator/rules/rules_test.go | 2 +- hw09_struct_validator/validator.go | 148 ++++++++++++++++++- hw09_struct_validator/validator_test.go | 168 +++++++++++++++------- 3 files changed, 266 insertions(+), 52 deletions(-) diff --git a/hw09_struct_validator/rules/rules_test.go b/hw09_struct_validator/rules/rules_test.go index 8958daa..66a65ce 100644 --- a/hw09_struct_validator/rules/rules_test.go +++ b/hw09_struct_validator/rules/rules_test.go @@ -70,7 +70,7 @@ func TestValidationFunction(t *testing.T) { } } -func TestFieldRulesByTag(t *testing.T) { +func TestRulesByTag(t *testing.T) { type testData struct { name string field string diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go index 59afe9e..3908c9d 100644 --- a/hw09_struct_validator/validator.go +++ b/hw09_struct_validator/validator.go @@ -1,9 +1,12 @@ package hw09structvalidator import ( + "errors" + "log" "reflect" "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" + r "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" ) // implemented in errors.go file @@ -30,5 +33,148 @@ func Validate(v interface{}) error { return rules.ErrRequireStruct } - return rules.ValidateStruct(rval) + return validateStruct(rval) +} + +// валидирует структутру. +// возвращает слайс ошибок валидации полей ValidationErrors или програмную ошибку. +// паникует, если v не структура. +func validateStruct(v reflect.Value) error { + cnt := v.NumField() + if cnt < 1 { + return nil + } + + var errStructValid = r.ValidationErrors{} + // идем по полям структуры + for i := range cnt { + f := v.Type().Field(i) + + if !f.IsExported() { // приватное поле + continue + } + + // получаем набор правил для поля + fieldRules, err := r.RulesByTag(f.Name, f.Tag.Get("validate")) + if err != nil { + return err + } + log.Println(fieldRules.Rules) + // если нет правил, то и проверять нечего. + if len(fieldRules.Rules) < 1 { + continue + } + + errField, err := validateField(v.Field(i), fieldRules) + if err != nil { + return err + } + if len(errField) > 0 { + errStructValid = append(errStructValid, errField...) + } + } + + if len(errStructValid) > 0 { + return errStructValid + } + + return nil +} + +// валидирует поле структуры. +func validateField(fieldValue reflect.Value, rules r.FieldRules) (r.ValidationErrors, error) { + switch fieldValue.Kind() { + case reflect.Slice, reflect.Array: + return validateSlice(fieldValue, rules) + case reflect.Struct: + if len(rules.Rules) == 1 && rules.Rules[0].Name == "nested" { + return validateStructF(fieldValue, rules) + } + return nil, nil + default: + return validateValue(fieldValue, rules) + } +} + +// валидирует вложенную структуру +func validateStructF(fieldValue reflect.Value, _ r.FieldRules) (r.ValidationErrors, error) { + err := validateStruct(fieldValue) + + if err != nil { + var e r.ValidationErrors + if errors.As(err, &e) { + return e, nil + } + + return nil, err + } + + return nil, nil +} + +// валидирует slice или массив +func validateSlice(fieldValue reflect.Value, rules r.FieldRules) (r.ValidationErrors, error) { + ln := fieldValue.Len() + if ln < 1 { + return nil, nil + } + + var vErr r.ValidationErrors + for i := 0; i < ln; i++ { + v, err := validateValue(fieldValue.Index(i), rules) + if err != nil { + return nil, err + } + if v != nil { + if len(vErr) > 0 { + vErr = append(vErr, v...) + continue + } + + vErr = v + } + } + + if len(vErr) > 0 { + return vErr, nil + } + + return nil, nil +} + +// валидируем поле со значение fieldValue, согласно правил описанных fieldRules. +func validateValue(fieldValue reflect.Value, fieldRules rules.FieldRules) (rules.ValidationErrors, error) { + var errFields = rules.ValidationErrors{} + + // перебираем все правила + for _, rule := range fieldRules.Rules { + // получаем функцию валидации. + vf, err := rules.ValidationFunction(fieldValue.Kind(), rule.Name) + if err != nil { + return nil, err + } + // проверяем fieldValue функцией валидации. + err = vf(fieldValue, rule.Cond) + + // если нет ошибок - переходим к следующему правилу. + if err == nil { + continue + } + // если ошибка валидации, сохраняем ее в массив ошибок валидации. + var e rules.ValidationError + if errors.As(err, &e) { + e.Field = fieldRules.FieldName + errFields = append(errFields, e) + continue + } + // если программная ошибка - возращаем ее, выходим. + return nil, err + } + + // если были ошибки валидации - возращаем их. + if len(errFields) > 0 { + return errFields, nil + } + + return nil, nil } diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go index a3632d7..daf3fbe 100644 --- a/hw09_struct_validator/validator_test.go +++ b/hw09_struct_validator/validator_test.go @@ -11,6 +11,7 @@ import ( ) type UserRole string +type EmptyStruct struct{} // Test the function on different structures and other types. type ( @@ -42,94 +43,161 @@ type ( func TestValidate(t *testing.T) { tests := []struct { + name string in interface{} expectedErr error }{ { - in: Response{ - Code: 200, - Body: "{}", - }, + name: "validate_struct_nil", + in: nil, expectedErr: nil, }, { - in: Response{ - Code: 100, - Body: "{}", - }, - expectedErr: rules.ValidationErrors{ - rules.ValidationError{ - Field: "Code", - Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotInList), - }, + name: "validate_not_struct", + in: "test", + expectedErr: rules.ErrRequireStruct, + }, + { + name: "validate_struct_empty", + in: EmptyStruct{}, + expectedErr: nil, + }, + { + name: "validate_rule_empty", + in: struct { + Field string `validate:"rule:cond|rule"` + }{ + Field: "qwert", }, + expectedErr: rules.ErrUnknowRule, }, { - in: Token{}, + name: "validate_field_private", + in: struct { + field string `validate:"rule:cond|rule"` + }{ + field: "qwert", + }, expectedErr: nil, }, { - in: App{ - Version: "qwert", + name: "validate_field_no_rules", + in: struct { + Field string + }{ + Field: "qwert", }, expectedErr: nil, }, { - in: App{ - Version: "qwerty", + name: "validate_err_validate_func", + in: struct { + Field string `validate:"rule:cond"` + }{ + Field: "qwert", }, - expectedErr: rules.ValidationErrors{ - rules.ValidationError{ - Field: "Version", - Err: fmt.Errorf("%w 5", rules.ErrStrLenNotEqual), - }, + expectedErr: fmt.Errorf("'%s' %w", "rule", rules.ErrUnknowRule), + }, + { + name: "validate_err_bad_condition", + in: struct { + Field string `validate:"len:cond"` + }{ + Field: "qwert", }, + expectedErr: fmt.Errorf("'%s' %w '%s'", "cond", rules.ErrInvalidCond, "len"), }, { + name: "validate_valid_err_cant_be_greate", in: User{ ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", Name: "User1", - Age: 18, + Age: 51, Email: "User1@mail.com", Role: "admin", Phones: []string{"12345678901", "98765432101"}, meta: json.RawMessage(``), }, - expectedErr: nil, - }, - { - in: User{ - ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", - Name: "User1", - Age: 16, - Email: "User1@mail.com.dot", - Role: "employee", - Phones: []string{"12345678901", "9876543210"}, - meta: json.RawMessage(``), - }, expectedErr: rules.ValidationErrors{ rules.ValidationError{ Field: "Age", - Err: fmt.Errorf("%w 18", rules.ErrIntCantBeLess), - }, - rules.ValidationError{ - Field: "Email", - Err: fmt.Errorf("%w %s", rules.ErrStrReExpNotMatch, "'^\\w+@\\w+\\.\\w+$'"), - }, - rules.ValidationError{ - Field: "Role", - Err: fmt.Errorf("%w %s", rules.ErrStrNotInList, "'admin,stuff'"), - }, - rules.ValidationError{ - Field: "Phones", - Err: fmt.Errorf("%w %v", rules.ErrStrLenNotEqual, 11), + Err: fmt.Errorf("%w 50", rules.ErrIntCantBeGreater), }, }, }, + // { + // in: Response{ + // Code: 200, + // Body: "{}", + // }, + // expectedErr: nil, + // }, + // { + // in: Response{ + // Code: 100, + // Body: "{}", + // }, + // expectedErr: rules.ValidationErrors{ + // rules.ValidationError{ + // Field: "Code", + // Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotInList), + // }, + // }, + // }, + // { + // in: Token{}, + // expectedErr: nil, + // }, + // { + // in: App{ + // Version: "qwert", + // }, + // expectedErr: nil, + // }, + // { + // in: App{ + // Version: "qwerty", + // }, + // expectedErr: rules.ValidationErrors{ + // rules.ValidationError{ + // Field: "Version", + // Err: fmt.Errorf("%w 5", rules.ErrStrLenNotEqual), + // }, + // }, + // }, + // { + // in: User{ + // ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + // Name: "User1", + // Age: 16, + // Email: "User1@mail.com.dot", + // Role: "employee", + // Phones: []string{"12345678901", "9876543210"}, + // meta: json.RawMessage(``), + // }, + // expectedErr: rules.ValidationErrors{ + // rules.ValidationError{ + // Field: "Age", + // Err: fmt.Errorf("%w 18", rules.ErrIntCantBeLess), + // }, + // rules.ValidationError{ + // Field: "Email", + // Err: fmt.Errorf("%w %s", rules.ErrStrReExpNotMatch, "'^\\w+@\\w+\\.\\w+$'"), + // }, + // rules.ValidationError{ + // Field: "Role", + // Err: fmt.Errorf("%w %s", rules.ErrStrNotInList, "'admin,stuff'"), + // }, + // rules.ValidationError{ + // Field: "Phones", + // Err: fmt.Errorf("%w %v", rules.ErrStrLenNotEqual, 11), + // }, + // }, + // }, } for i, tt := range tests { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("case %d: %s", i, tt.name), func(t *testing.T) { tt := tt t.Parallel() From fb21e58551a6114850f939b441645285a83e480d Mon Sep 17 00:00:00 2001 From: DimVlas Date: Mon, 16 Dec 2024 20:26:43 +0000 Subject: [PATCH 18/25] hw09. linter check --- .golangci.yml | 3 + hw09_struct_validator/rules/rules.go | 2 +- hw09_struct_validator/rules/rules_test.go | 11 +- hw09_struct_validator/validator.go | 21 +- hw09_struct_validator/validator_test.go | 303 +++++++++++----------- 5 files changed, 172 insertions(+), 168 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 3cb3aa5..824c997 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,7 +14,10 @@ linters-settings: allow: - github.com/DimVlas/otus_hw/hw09_struct_validator/rules - github.com/stretchr/testify/require + - github.com/stretchr/testify/assert - $gostd + exhaustive: + explicit-exhaustive-switch: true gci: custom-order: true no-lex-order: true diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 3635499..05b7ef8 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -156,7 +156,7 @@ func ValidationFunction(kind reflect.Kind, rule string) (Validator, error) { } // получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName. -func RulesByTag(fieldName string, fieldTag string) (FieldRules, error) { +func TagRules(fieldName string, fieldTag string) (FieldRules, error) { rls, err := parseRulesTag(fieldTag) if err != nil { return FieldRules{ diff --git a/hw09_struct_validator/rules/rules_test.go b/hw09_struct_validator/rules/rules_test.go index 66a65ce..54b7e2e 100644 --- a/hw09_struct_validator/rules/rules_test.go +++ b/hw09_struct_validator/rules/rules_test.go @@ -112,7 +112,8 @@ func TestRulesByTag(t *testing.T) { Rules: []RuleInfo{ {Name: "rule1", Cond: "condition1"}, {Name: "rule2", Cond: "condition2"}, - }}, + }, + }, err: nil, mess: "should no error for tag with two rule", }, @@ -122,7 +123,8 @@ func TestRulesByTag(t *testing.T) { tag: "|", exp: FieldRules{ FieldName: "field", - Rules: []RuleInfo{}}, + Rules: []RuleInfo{}, + }, err: ErrEmptyRule, mess: "", }, @@ -132,7 +134,8 @@ func TestRulesByTag(t *testing.T) { tag: "rule:cond|rule", exp: FieldRules{ FieldName: "field", - Rules: []RuleInfo{}}, + Rules: []RuleInfo{}, + }, err: ErrUnknowRule, mess: "", }, @@ -140,7 +143,7 @@ func TestRulesByTag(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - r, err := RulesByTag(test.field, test.tag) + r, err := TagRules(test.field, test.tag) require.Equal(t, test.exp, r, test.mess) diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go index 3908c9d..25ae6b3 100644 --- a/hw09_struct_validator/validator.go +++ b/hw09_struct_validator/validator.go @@ -5,7 +5,6 @@ import ( "log" "reflect" - "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" r "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" ) @@ -30,7 +29,7 @@ func Validate(v interface{}) error { rval := reflect.ValueOf(v) if rval.Kind() != reflect.Struct { - return rules.ErrRequireStruct + return r.ErrRequireStruct } return validateStruct(rval) @@ -45,7 +44,7 @@ func validateStruct(v reflect.Value) error { return nil } - var errStructValid = r.ValidationErrors{} + var errStructValid r.ValidationErrors // идем по полям структуры for i := range cnt { f := v.Type().Field(i) @@ -55,7 +54,7 @@ func validateStruct(v reflect.Value) error { } // получаем набор правил для поля - fieldRules, err := r.RulesByTag(f.Name, f.Tag.Get("validate")) + fieldRules, err := r.TagRules(f.Name, f.Tag.Get("validate")) if err != nil { return err } @@ -96,10 +95,9 @@ func validateField(fieldValue reflect.Value, rules r.FieldRules) (r.ValidationEr } } -// валидирует вложенную структуру +// валидирует вложенную структуру. func validateStructF(fieldValue reflect.Value, _ r.FieldRules) (r.ValidationErrors, error) { err := validateStruct(fieldValue) - if err != nil { var e r.ValidationErrors if errors.As(err, &e) { @@ -112,7 +110,7 @@ func validateStructF(fieldValue reflect.Value, _ r.FieldRules) (r.ValidationErro return nil, nil } -// валидирует slice или массив +// валидирует slice или массив. func validateSlice(fieldValue reflect.Value, rules r.FieldRules) (r.ValidationErrors, error) { ln := fieldValue.Len() if ln < 1 { @@ -143,13 +141,12 @@ func validateSlice(fieldValue reflect.Value, rules r.FieldRules) (r.ValidationEr } // валидируем поле со значение fieldValue, согласно правил описанных fieldRules. -func validateValue(fieldValue reflect.Value, fieldRules rules.FieldRules) (rules.ValidationErrors, error) { - var errFields = rules.ValidationErrors{} - +func validateValue(fieldValue reflect.Value, fieldRules r.FieldRules) (r.ValidationErrors, error) { + var errFields r.ValidationErrors // перебираем все правила for _, rule := range fieldRules.Rules { // получаем функцию валидации. - vf, err := rules.ValidationFunction(fieldValue.Kind(), rule.Name) + vf, err := r.ValidationFunction(fieldValue.Kind(), rule.Name) if err != nil { return nil, err } @@ -161,7 +158,7 @@ func validateValue(fieldValue reflect.Value, fieldRules rules.FieldRules) (rules continue } // если ошибка валидации, сохраняем ее в массив ошибок валидации. - var e rules.ValidationError + var e r.ValidationError if errors.As(err, &e) { e.Field = fieldRules.FieldName errFields = append(errFields, e) diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go index daf3fbe..f15fbd6 100644 --- a/hw09_struct_validator/validator_test.go +++ b/hw09_struct_validator/validator_test.go @@ -11,6 +11,7 @@ import ( ) type UserRole string + type EmptyStruct struct{} // Test the function on different structures and other types. @@ -18,11 +19,11 @@ type ( User struct { ID string `json:"id" validate:"len:36"` Name string - Age int `validate:"min:18|max:50"` - Email string `validate:"regexp:^\\w+@\\w+\\.\\w+$"` - Role UserRole `validate:"in:admin,stuff"` - Phones []string `validate:"len:11"` - meta json.RawMessage //nolint:unused + Age int `validate:"min:18|max:50"` + Email string `validate:"regexp:^\\w+@\\w+\\.\\w+$"` + Role UserRole `validate:"in:admin,stuff"` + Phones []string `validate:"len:11"` + meta json.RawMessage } App struct { @@ -41,161 +42,161 @@ type ( } ) -func TestValidate(t *testing.T) { - tests := []struct { - name string - in interface{} - expectedErr error - }{ - { - name: "validate_struct_nil", - in: nil, - expectedErr: nil, +var tests = []struct { + name string + in interface{} + expectedErr error +}{ + { + name: "validate_struct_nil", + in: nil, + expectedErr: nil, + }, + { + name: "validate_not_struct", + in: "test", + expectedErr: rules.ErrRequireStruct, + }, + { + name: "validate_struct_empty", + in: EmptyStruct{}, + expectedErr: nil, + }, + { + name: "validate_rule_empty", + in: struct { + Field string `validate:"rule:cond|rule"` + }{ + Field: "qwert", }, - { - name: "validate_not_struct", - in: "test", - expectedErr: rules.ErrRequireStruct, + expectedErr: rules.ErrUnknowRule, + }, + { + name: "validate_field_private", + in: struct { + field string `validate:"rule:cond|rule"` + }{ + field: "qwert", }, - { - name: "validate_struct_empty", - in: EmptyStruct{}, - expectedErr: nil, + expectedErr: nil, + }, + { + name: "validate_field_no_rules", + in: struct { + Field string + }{ + Field: "qwert", }, - { - name: "validate_rule_empty", - in: struct { - Field string `validate:"rule:cond|rule"` - }{ - Field: "qwert", - }, - expectedErr: rules.ErrUnknowRule, + expectedErr: nil, + }, + { + name: "validate_err_validate_func", + in: struct { + Field string `validate:"rule:cond"` + }{ + Field: "qwert", }, - { - name: "validate_field_private", - in: struct { - field string `validate:"rule:cond|rule"` - }{ - field: "qwert", - }, - expectedErr: nil, + expectedErr: fmt.Errorf("'%s' %w", "rule", rules.ErrUnknowRule), + }, + { + name: "validate_err_bad_condition", + in: struct { + Field string `validate:"len:cond"` + }{ + Field: "qwert", }, - { - name: "validate_field_no_rules", - in: struct { - Field string - }{ - Field: "qwert", - }, - expectedErr: nil, - }, - { - name: "validate_err_validate_func", - in: struct { - Field string `validate:"rule:cond"` - }{ - Field: "qwert", - }, - expectedErr: fmt.Errorf("'%s' %w", "rule", rules.ErrUnknowRule), + expectedErr: fmt.Errorf("'%s' %w '%s'", "cond", rules.ErrInvalidCond, "len"), + }, + { + name: "validate_valid_err_cant_be_greate", + in: User{ + ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + Name: "User1", + Age: 51, + Email: "User1@mail.com", + Role: "admin", + Phones: []string{"12345678901", "98765432101"}, + meta: json.RawMessage(``), }, - { - name: "validate_err_bad_condition", - in: struct { - Field string `validate:"len:cond"` - }{ - Field: "qwert", + expectedErr: rules.ValidationErrors{ + rules.ValidationError{ + Field: "Age", + Err: fmt.Errorf("%w 50", rules.ErrIntCantBeGreater), }, - expectedErr: fmt.Errorf("'%s' %w '%s'", "cond", rules.ErrInvalidCond, "len"), }, - { - name: "validate_valid_err_cant_be_greate", - in: User{ - ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", - Name: "User1", - Age: 51, - Email: "User1@mail.com", - Role: "admin", - Phones: []string{"12345678901", "98765432101"}, - meta: json.RawMessage(``), - }, - expectedErr: rules.ValidationErrors{ - rules.ValidationError{ - Field: "Age", - Err: fmt.Errorf("%w 50", rules.ErrIntCantBeGreater), - }, - }, - }, - // { - // in: Response{ - // Code: 200, - // Body: "{}", - // }, - // expectedErr: nil, - // }, - // { - // in: Response{ - // Code: 100, - // Body: "{}", - // }, - // expectedErr: rules.ValidationErrors{ - // rules.ValidationError{ - // Field: "Code", - // Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotInList), - // }, - // }, - // }, - // { - // in: Token{}, - // expectedErr: nil, - // }, - // { - // in: App{ - // Version: "qwert", - // }, - // expectedErr: nil, - // }, - // { - // in: App{ - // Version: "qwerty", - // }, - // expectedErr: rules.ValidationErrors{ - // rules.ValidationError{ - // Field: "Version", - // Err: fmt.Errorf("%w 5", rules.ErrStrLenNotEqual), - // }, - // }, - // }, - // { - // in: User{ - // ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", - // Name: "User1", - // Age: 16, - // Email: "User1@mail.com.dot", - // Role: "employee", - // Phones: []string{"12345678901", "9876543210"}, - // meta: json.RawMessage(``), - // }, - // expectedErr: rules.ValidationErrors{ - // rules.ValidationError{ - // Field: "Age", - // Err: fmt.Errorf("%w 18", rules.ErrIntCantBeLess), - // }, - // rules.ValidationError{ - // Field: "Email", - // Err: fmt.Errorf("%w %s", rules.ErrStrReExpNotMatch, "'^\\w+@\\w+\\.\\w+$'"), - // }, - // rules.ValidationError{ - // Field: "Role", - // Err: fmt.Errorf("%w %s", rules.ErrStrNotInList, "'admin,stuff'"), - // }, - // rules.ValidationError{ - // Field: "Phones", - // Err: fmt.Errorf("%w %v", rules.ErrStrLenNotEqual, 11), - // }, - // }, - // }, - } + }, + // { + // in: Response{ + // Code: 200, + // Body: "{}", + // }, + // expectedErr: nil, + // }, + // { + // in: Response{ + // Code: 100, + // Body: "{}", + // }, + // expectedErr: rules.ValidationErrors{ + // rules.ValidationError{ + // Field: "Code", + // Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotInList), + // }, + // }, + // }, + // { + // in: Token{}, + // expectedErr: nil, + // }, + // { + // in: App{ + // Version: "qwert", + // }, + // expectedErr: nil, + // }, + // { + // in: App{ + // Version: "qwerty", + // }, + // expectedErr: rules.ValidationErrors{ + // rules.ValidationError{ + // Field: "Version", + // Err: fmt.Errorf("%w 5", rules.ErrStrLenNotEqual), + // }, + // }, + // }, + // { + // in: User{ + // ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + // Name: "User1", + // Age: 16, + // Email: "User1@mail.com.dot", + // Role: "employee", + // Phones: []string{"12345678901", "9876543210"}, + // meta: json.RawMessage(``), + // }, + // expectedErr: rules.ValidationErrors{ + // rules.ValidationError{ + // Field: "Age", + // Err: fmt.Errorf("%w 18", rules.ErrIntCantBeLess), + // }, + // rules.ValidationError{ + // Field: "Email", + // Err: fmt.Errorf("%w %s", rules.ErrStrReExpNotMatch, "'^\\w+@\\w+\\.\\w+$'"), + // }, + // rules.ValidationError{ + // Field: "Role", + // Err: fmt.Errorf("%w %s", rules.ErrStrNotInList, "'admin,stuff'"), + // }, + // rules.ValidationError{ + // Field: "Phones", + // Err: fmt.Errorf("%w %v", rules.ErrStrLenNotEqual, 11), + // }, + // }, + // }, +} +func TestValidate(t *testing.T) { for i, tt := range tests { t.Run(fmt.Sprintf("case %d: %s", i, tt.name), func(t *testing.T) { tt := tt From a154d26430f52cd4f369c68819c9fe3632455325 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Tue, 17 Dec 2024 09:22:37 +0000 Subject: [PATCH 19/25] hw09. add tag 'nested' and test --- hw09_struct_validator/rules/rules.go | 16 +++++++++++----- hw09_struct_validator/rules/rules_test.go | 13 +++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/hw09_struct_validator/rules/rules.go b/hw09_struct_validator/rules/rules.go index 05b7ef8..0c11320 100644 --- a/hw09_struct_validator/rules/rules.go +++ b/hw09_struct_validator/rules/rules.go @@ -157,7 +157,7 @@ func ValidationFunction(kind reflect.Kind, rule string) (Validator, error) { // получает из тэга fieldTag струтуру FieldRules с правилами валидации для поля с именем fieldName. func TagRules(fieldName string, fieldTag string) (FieldRules, error) { - rls, err := parseRulesTag(fieldTag) + rls, err := parseTagRules(fieldTag) if err != nil { return FieldRules{ FieldName: fieldName, @@ -173,7 +173,7 @@ func TagRules(fieldName string, fieldTag string) (FieldRules, error) { // парсит полученную строку, возвращая массив структур с описанием правил проверки. // ожидается, что строка имеет вид 'правило:условие|правило:условие|...'. -func parseRulesTag(rulesTag string) ([]RuleInfo, error) { +func parseTagRules(rulesTag string) ([]RuleInfo, error) { rulesTag = strings.Trim(rulesTag, " ") if rulesTag == "" { return []RuleInfo{}, nil @@ -188,12 +188,18 @@ func parseRulesTag(rulesTag string) ([]RuleInfo, error) { if len(r) == 0 { return []RuleInfo{}, ErrEmptyRule } + rule := strings.Split(r, ":") - if len(rule) != 2 { - return []RuleInfo{}, ErrUnknowRule + if len(rule) == 2 { + ri = append(ri, RuleInfo{Name: rule[0], Cond: rule[1]}) + continue + } + if len(rule) == 1 && rule[0] == "nested" { + ri = append(ri, RuleInfo{Name: rule[0], Cond: ""}) + continue } - ri = append(ri, RuleInfo{Name: rule[0], Cond: rule[1]}) + return []RuleInfo{}, ErrUnknowRule } return ri, nil diff --git a/hw09_struct_validator/rules/rules_test.go b/hw09_struct_validator/rules/rules_test.go index 54b7e2e..85e6f44 100644 --- a/hw09_struct_validator/rules/rules_test.go +++ b/hw09_struct_validator/rules/rules_test.go @@ -139,6 +139,19 @@ func TestRulesByTag(t *testing.T) { err: ErrUnknowRule, mess: "", }, + { + name: "struct_nested_tag", + field: "field", + tag: "nested", + exp: FieldRules{ + FieldName: "field", + Rules: []RuleInfo{ + {Name: "nested", Cond: ""}, + }, + }, + err: nil, + mess: "", + }, } for _, test := range tests { From e9a47d063c4e2ff4cb34a36e7df6659414baaf13 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Tue, 17 Dec 2024 10:57:43 +0000 Subject: [PATCH 20/25] HW09. Validator tests --- hw09_struct_validator/validator.go | 12 +- hw09_struct_validator/validator_test.go | 193 ++++++++++++++---------- 2 files changed, 126 insertions(+), 79 deletions(-) diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go index 25ae6b3..3bf2ff1 100644 --- a/hw09_struct_validator/validator.go +++ b/hw09_struct_validator/validator.go @@ -2,7 +2,6 @@ package hw09structvalidator import ( "errors" - "log" "reflect" r "github.com/DimVlas/otus_hw/hw09_struct_validator/rules" @@ -58,7 +57,6 @@ func validateStruct(v reflect.Value) error { if err != nil { return err } - log.Println(fieldRules.Rules) // если нет правил, то и проверять нечего. if len(fieldRules.Rules) < 1 { continue @@ -86,7 +84,15 @@ func validateField(fieldValue reflect.Value, rules r.FieldRules) (r.ValidationEr case reflect.Slice, reflect.Array: return validateSlice(fieldValue, rules) case reflect.Struct: - if len(rules.Rules) == 1 && rules.Rules[0].Name == "nested" { + var nested bool + for _, r := range rules.Rules { + if r.Name == "nested" { + nested = true + break + } + } + + if nested { return validateStructF(fieldValue, rules) } return nil, nil diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go index f15fbd6..92d33eb 100644 --- a/hw09_struct_validator/validator_test.go +++ b/hw09_struct_validator/validator_test.go @@ -10,12 +10,12 @@ import ( "github.com/stretchr/testify/require" ) -type UserRole string - -type EmptyStruct struct{} - // Test the function on different structures and other types. type ( + UserRole string + + EmptyStruct struct{} + User struct { ID string `json:"id" validate:"len:36"` Name string @@ -27,6 +27,7 @@ type ( } App struct { + Name string `validate:"min:5"` Version string `validate:"len:5"` } @@ -40,6 +41,25 @@ type ( Code int `validate:"in:200,404,500"` Body string `json:"omitempty"` } + + Product struct { + Price float32 `validate:"min:1"` + Name string `validate:"len:10"` + } + + UserResponses struct { + User User `validate:"min:1|nested"` + Responses []Response + } + + UserTags struct { + User User `validate:"min:1"` + Tags string + } + UserApp struct { + User User + App App `validate:"nested"` + } ) var tests = []struct { @@ -47,6 +67,72 @@ var tests = []struct { in interface{} expectedErr error }{ + { + name: "validate_slice_err", + in: struct { + Codes []int `validate:"len:5"` + }{ + Codes: []int{1, 2, 3}, + }, + expectedErr: fmt.Errorf("'len' %w", rules.ErrUnknowRule), + }, + { + name: "validate_nested_struct_slice_err_validation", + in: UserResponses{ + User: User{ + ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + Name: "User1", + Age: 33, + Email: "User1@mail.com", + Role: "admin", + Phones: []string{"1234567890", "9876543210"}, + meta: json.RawMessage(``), + }, + Responses: []Response{}, + }, + expectedErr: rules.ValidationErrors{ + rules.ValidationError{ + Field: "Phones", + Err: fmt.Errorf("%w 11", rules.ErrStrLenNotEqual), + }, + rules.ValidationError{ + Field: "Phones", + Err: fmt.Errorf("%w 11", rules.ErrStrLenNotEqual), + }, + }, + }, + { + name: "validate_nested_struct", + in: UserResponses{ + User: User{ + ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + Name: "User1", + Age: 33, + Email: "User1@mail.com", + Role: "admin", + Phones: []string{"12345678901", "98765432101"}, + meta: json.RawMessage(``), + }, + Responses: []Response{}, + }, + expectedErr: nil, + }, + { + name: "validate_no_validate_nested_struct", + in: UserTags{ + User: User{ + ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + Name: "User1", + Age: 33, + Email: "User1@mail.com", + Role: "admin", + Phones: []string{"12345678901", "98765432101"}, + meta: json.RawMessage(``), + }, + Tags: "", + }, + expectedErr: nil, + }, { name: "validate_struct_nil", in: nil, @@ -74,7 +160,7 @@ var tests = []struct { { name: "validate_field_private", in: struct { - field string `validate:"rule:cond|rule"` + field string `validate:"rule:cond"` }{ field: "qwert", }, @@ -90,7 +176,7 @@ var tests = []struct { expectedErr: nil, }, { - name: "validate_err_validate_func", + name: "validate_err_unknow_rule_func", in: struct { Field string `validate:"rule:cond"` }{ @@ -115,7 +201,7 @@ var tests = []struct { Age: 51, Email: "User1@mail.com", Role: "admin", - Phones: []string{"12345678901", "98765432101"}, + Phones: []string{}, meta: json.RawMessage(``), }, expectedErr: rules.ValidationErrors{ @@ -125,75 +211,30 @@ var tests = []struct { }, }, }, - // { - // in: Response{ - // Code: 200, - // Body: "{}", - // }, - // expectedErr: nil, - // }, - // { - // in: Response{ - // Code: 100, - // Body: "{}", - // }, - // expectedErr: rules.ValidationErrors{ - // rules.ValidationError{ - // Field: "Code", - // Err: fmt.Errorf("%w 200,404,500", rules.ErrIntNotInList), - // }, - // }, - // }, - // { - // in: Token{}, - // expectedErr: nil, - // }, - // { - // in: App{ - // Version: "qwert", - // }, - // expectedErr: nil, - // }, - // { - // in: App{ - // Version: "qwerty", - // }, - // expectedErr: rules.ValidationErrors{ - // rules.ValidationError{ - // Field: "Version", - // Err: fmt.Errorf("%w 5", rules.ErrStrLenNotEqual), - // }, - // }, - // }, - // { - // in: User{ - // ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", - // Name: "User1", - // Age: 16, - // Email: "User1@mail.com.dot", - // Role: "employee", - // Phones: []string{"12345678901", "9876543210"}, - // meta: json.RawMessage(``), - // }, - // expectedErr: rules.ValidationErrors{ - // rules.ValidationError{ - // Field: "Age", - // Err: fmt.Errorf("%w 18", rules.ErrIntCantBeLess), - // }, - // rules.ValidationError{ - // Field: "Email", - // Err: fmt.Errorf("%w %s", rules.ErrStrReExpNotMatch, "'^\\w+@\\w+\\.\\w+$'"), - // }, - // rules.ValidationError{ - // Field: "Role", - // Err: fmt.Errorf("%w %s", rules.ErrStrNotInList, "'admin,stuff'"), - // }, - // rules.ValidationError{ - // Field: "Phones", - // Err: fmt.Errorf("%w %v", rules.ErrStrLenNotEqual, 11), - // }, - // }, - // }, + { + name: "validate_valid_check_arr", + in: User{ + ID: "pD4tNeo-t0OGE_ooz3WqxAcyFeuF6AUk6mQf", + Name: "User1", + Age: 25, + Email: "User1@mail.dot", + Role: "admin", + Phones: []string{"12345678901", "98765432101"}, + meta: json.RawMessage(``), + }, + expectedErr: nil, + }, + { + name: "validate_nested_structerr_err", + in: UserApp{ + User: User{}, + App: App{ + Name: "App1", + Version: "qwert", + }, + }, + expectedErr: fmt.Errorf("'min' %w", rules.ErrUnknowRule), + }, } func TestValidate(t *testing.T) { From 4454c88875095b80ccfbb4c3e456475bb05b3c93 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Tue, 17 Dec 2024 11:02:18 +0000 Subject: [PATCH 21/25] HW09 is completed --- hw09_struct_validator/.sync | 0 hw09_struct_validator/rules/RULES.md | 19 ------------------- 2 files changed, 19 deletions(-) delete mode 100644 hw09_struct_validator/.sync delete mode 100644 hw09_struct_validator/rules/RULES.md diff --git a/hw09_struct_validator/.sync b/hw09_struct_validator/.sync deleted file mode 100644 index e69de29..0000000 diff --git a/hw09_struct_validator/rules/RULES.md b/hw09_struct_validator/rules/RULES.md deleted file mode 100644 index 9e08ec8..0000000 --- a/hw09_struct_validator/rules/RULES.md +++ /dev/null @@ -1,19 +0,0 @@ -# Правила проверок - -## struct -- `field:Size int` - в структуре долно быть целочисленное поле с именем 'Size' - -## string -- len - `len:32` - длина строки должна быть ровно 32 символа; -- regexp - `regexp:\\d+` - согласно регулярному выражению строка должна состоять из цифр (`\\` - экранирование слэша); -- in - `in:foo,bar` - строка должна входить в множество строк {"foo", "bar"}. - -# Тесты - -## string - -### len -- не верное значение - -- не верное условие -- проврека провалена -- проверка успешна From a829e4858cdd1b05111bff5f34fdcc039bec5cd7 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Wed, 25 Dec 2024 19:52:04 +0000 Subject: [PATCH 22/25] HW10 is completed --- hw10_program_optimization/.sync | 0 hw10_program_optimization/bench_new.txt | 16 +++ hw10_program_optimization/bench_old.txt | 16 +++ hw10_program_optimization/bench_stat.txt | 15 +++ hw10_program_optimization/go.mod | 10 +- hw10_program_optimization/go.sum | 8 ++ hw10_program_optimization/stats.go | 63 +++++----- hw10_program_optimization/stats_easyjson.go | 127 ++++++++++++++++++++ hw10_program_optimization/stats_test.go | 28 +++++ 9 files changed, 246 insertions(+), 37 deletions(-) delete mode 100644 hw10_program_optimization/.sync create mode 100644 hw10_program_optimization/bench_new.txt create mode 100644 hw10_program_optimization/bench_old.txt create mode 100644 hw10_program_optimization/bench_stat.txt create mode 100644 hw10_program_optimization/stats_easyjson.go diff --git a/hw10_program_optimization/.sync b/hw10_program_optimization/.sync deleted file mode 100644 index e69de29..0000000 diff --git a/hw10_program_optimization/bench_new.txt b/hw10_program_optimization/bench_new.txt new file mode 100644 index 0000000..21023fa --- /dev/null +++ b/hw10_program_optimization/bench_new.txt @@ -0,0 +1,16 @@ +goos: linux +goarch: amd64 +pkg: github.com/DimVlas/otus_hw/hw10_program_optimization +cpu: AMD Ryzen 5 3500X 6-Core Processor +BenchmarkGetDomainStat-6 6 180000872 ns/op 29449422 B/op 723161 allocs/op +BenchmarkGetDomainStat-6 6 179475854 ns/op 29449080 B/op 723159 allocs/op +BenchmarkGetDomainStat-6 6 179211242 ns/op 29449562 B/op 723161 allocs/op +BenchmarkGetDomainStat-6 6 179568154 ns/op 29449693 B/op 723162 allocs/op +BenchmarkGetDomainStat-6 6 183618851 ns/op 29449421 B/op 723160 allocs/op +BenchmarkGetDomainStat-6 6 186144564 ns/op 29449717 B/op 723163 allocs/op +BenchmarkGetDomainStat-6 6 179541616 ns/op 29449549 B/op 723161 allocs/op +BenchmarkGetDomainStat-6 6 179762574 ns/op 29449098 B/op 723159 allocs/op +BenchmarkGetDomainStat-6 6 179707295 ns/op 29449813 B/op 723162 allocs/op +BenchmarkGetDomainStat-6 6 179366661 ns/op 29449816 B/op 723162 allocs/op +PASS +ok github.com/DimVlas/otus_hw/hw10_program_optimization 12.673s diff --git a/hw10_program_optimization/bench_old.txt b/hw10_program_optimization/bench_old.txt new file mode 100644 index 0000000..277102e --- /dev/null +++ b/hw10_program_optimization/bench_old.txt @@ -0,0 +1,16 @@ +goos: linux +goarch: amd64 +pkg: github.com/DimVlas/otus_hw/hw10_program_optimization +cpu: AMD Ryzen 5 3500X 6-Core Processor +BenchmarkGetDomainStat-6 2 552224837 ns/op 316920320 B/op 2845406 allocs/op +BenchmarkGetDomainStat-6 2 554340655 ns/op 316939656 B/op 2845410 allocs/op +BenchmarkGetDomainStat-6 2 548643641 ns/op 316883512 B/op 2845403 allocs/op +BenchmarkGetDomainStat-6 2 541981412 ns/op 316864984 B/op 2845401 allocs/op +BenchmarkGetDomainStat-6 2 551823022 ns/op 316993964 B/op 2845409 allocs/op +BenchmarkGetDomainStat-6 2 556719799 ns/op 316884424 B/op 2845408 allocs/op +BenchmarkGetDomainStat-6 2 543636506 ns/op 316920640 B/op 2845407 allocs/op +BenchmarkGetDomainStat-6 2 553364894 ns/op 316957360 B/op 2845408 allocs/op +BenchmarkGetDomainStat-6 2 550641171 ns/op 316939488 B/op 2845408 allocs/op +BenchmarkGetDomainStat-6 2 555527000 ns/op 316957552 B/op 2845409 allocs/op +PASS +ok github.com/DimVlas/otus_hw/hw10_program_optimization 16.542s diff --git a/hw10_program_optimization/bench_stat.txt b/hw10_program_optimization/bench_stat.txt new file mode 100644 index 0000000..d0be7bd --- /dev/null +++ b/hw10_program_optimization/bench_stat.txt @@ -0,0 +1,15 @@ +goos: linux +goarch: amd64 +pkg: github.com/DimVlas/otus_hw/hw10_program_optimization +cpu: AMD Ryzen 5 3500X 6-Core Processor + │ bench_old.txt │ bench_new.txt │ + │ sec/op │ sec/op vs base │ +GetDomainStat-6 552.0m ± 2% 179.6m ± 2% -67.46% (p=0.000 n=10) + + │ bench_old.txt │ bench_new.txt │ + │ B/op │ B/op vs base │ +GetDomainStat-6 302.25Mi ± 0% 28.09Mi ± 0% -90.71% (p=0.000 n=10) + + │ bench_old.txt │ bench_new.txt │ + │ allocs/op │ allocs/op vs base │ +GetDomainStat-6 2845.4k ± 0% 723.2k ± 0% -74.58% (p=0.000 n=10) diff --git a/hw10_program_optimization/go.mod b/hw10_program_optimization/go.mod index 8bccd17..0521407 100644 --- a/hw10_program_optimization/go.mod +++ b/hw10_program_optimization/go.mod @@ -1,11 +1,17 @@ -module github.com/fixme_my_friend/hw10_program_optimization +module github.com/DimVlas/otus_hw/hw10_program_optimization go 1.22 -require github.com/stretchr/testify v1.7.0 +require ( + github.com/mailru/easyjson v0.9.0 + github.com/stretchr/testify v1.7.0 +) require ( + github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/perf v0.0.0-20241204221936-711ff2ab7231 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/hw10_program_optimization/go.sum b/hw10_program_optimization/go.sum index c221f64..7469e4f 100644 --- a/hw10_program_optimization/go.sum +++ b/hw10_program_optimization/go.sum @@ -1,11 +1,19 @@ +github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 h1:xlwdaKcTNVW4PtpQb8aKA4Pjy0CdJHEqvFbAnvR5m2g= +github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/perf v0.0.0-20241204221936-711ff2ab7231 h1:LrlW0UtZ/i5iwthL3HvtiFwcGwgtopumPLfOwdutbzQ= +golang.org/x/perf v0.0.0-20241204221936-711ff2ab7231/go.mod h1:Ha9gd2gpRTtN8P+nXRQCFfD8JnfyIrp8vL362lG7AnU= 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= diff --git a/hw10_program_optimization/stats.go b/hw10_program_optimization/stats.go index affb108..014c7d2 100644 --- a/hw10_program_optimization/stats.go +++ b/hw10_program_optimization/stats.go @@ -1,11 +1,11 @@ package hw10programoptimization import ( - "encoding/json" - "fmt" + "bufio" "io" - "regexp" "strings" + + easyjson "github.com/mailru/easyjson" ) type User struct { @@ -21,46 +21,39 @@ type User struct { type DomainStat map[string]int func GetDomainStat(r io.Reader, domain string) (DomainStat, error) { - u, err := getUsers(r) - if err != nil { - return nil, fmt.Errorf("get users error: %w", err) - } - return countDomains(u, domain) + return domainsStat(r, domain) } -type users [100_000]User - -func getUsers(r io.Reader) (result users, err error) { - content, err := io.ReadAll(r) - if err != nil { - return - } +func domainsStat(r io.Reader, domain string) (DomainStat, error) { + rd := bufio.NewReader(r) + var br bool + var user User + stat := make(DomainStat) - lines := strings.Split(string(content), "\n") - for i, line := range lines { - var user User - if err = json.Unmarshal([]byte(line), &user); err != nil { - return + for { + line, err := rd.ReadBytes('\n') + if err != nil { + switch err { + case io.EOF: + br = true + default: + return nil, err + } } - result[i] = user - } - return -} - -func countDomains(u users, domain string) (DomainStat, error) { - result := make(DomainStat) - for _, user := range u { - matched, err := regexp.Match("\\."+domain, []byte(user.Email)) - if err != nil { + if err = easyjson.Unmarshal(line, &user); err != nil { return nil, err } - if matched { - num := result[strings.ToLower(strings.SplitN(user.Email, "@", 2)[1])] - num++ - result[strings.ToLower(strings.SplitN(user.Email, "@", 2)[1])] = num + if strings.HasSuffix(user.Email, "."+domain) { + d := strings.ToLower(strings.SplitN(user.Email, "@", 2)[1]) + stat[d]++ + } + + if br { + break } } - return result, nil + + return stat, nil } diff --git a/hw10_program_optimization/stats_easyjson.go b/hw10_program_optimization/stats_easyjson.go new file mode 100644 index 0000000..2bccabf --- /dev/null +++ b/hw10_program_optimization/stats_easyjson.go @@ -0,0 +1,127 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package hw10programoptimization + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonE3ab7953DecodeGithubComFixmeMyFriendHw10ProgramOptimization(in *jlexer.Lexer, out *User) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "ID": + out.ID = int(in.Int()) + case "Name": + out.Name = string(in.String()) + case "Username": + out.Username = string(in.String()) + case "Email": + out.Email = string(in.String()) + case "Phone": + out.Phone = string(in.String()) + case "Password": + out.Password = string(in.String()) + case "Address": + out.Address = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE3ab7953EncodeGithubComFixmeMyFriendHw10ProgramOptimization(out *jwriter.Writer, in User) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"ID\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"Name\":" + out.RawString(prefix) + out.String(string(in.Name)) + } + { + const prefix string = ",\"Username\":" + out.RawString(prefix) + out.String(string(in.Username)) + } + { + const prefix string = ",\"Email\":" + out.RawString(prefix) + out.String(string(in.Email)) + } + { + const prefix string = ",\"Phone\":" + out.RawString(prefix) + out.String(string(in.Phone)) + } + { + const prefix string = ",\"Password\":" + out.RawString(prefix) + out.String(string(in.Password)) + } + { + const prefix string = ",\"Address\":" + out.RawString(prefix) + out.String(string(in.Address)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v User) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE3ab7953EncodeGithubComFixmeMyFriendHw10ProgramOptimization(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v User) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE3ab7953EncodeGithubComFixmeMyFriendHw10ProgramOptimization(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *User) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE3ab7953DecodeGithubComFixmeMyFriendHw10ProgramOptimization(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *User) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE3ab7953DecodeGithubComFixmeMyFriendHw10ProgramOptimization(l, v) +} diff --git a/hw10_program_optimization/stats_test.go b/hw10_program_optimization/stats_test.go index f2c20a7..5e4a4e5 100644 --- a/hw10_program_optimization/stats_test.go +++ b/hw10_program_optimization/stats_test.go @@ -1,9 +1,12 @@ +//go:build !bench // +build !bench package hw10programoptimization import ( + "archive/zip" "bytes" + "log" "testing" "github.com/stretchr/testify/require" @@ -37,3 +40,28 @@ func TestGetDomainStat(t *testing.T) { require.Equal(t, DomainStat{}, result) }) } + +func BenchmarkGetDomainStat(b *testing.B) { + z, err := zip.OpenReader("testdata/users.dat.zip") + if err != nil { + log.Println(err) + return + } + defer z.Close() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + func() { + b.StopTimer() + r, err := z.File[0].Open() + if err != nil { + log.Println(err) + return + } + defer r.Close() + + b.StartTimer() + _, _ = GetDomainStat(r, "biz") + }() + } +} From 804a6ce0ac34ad4a3c2a02542bdcfe923e4371ec Mon Sep 17 00:00:00 2001 From: DimVlas Date: Wed, 25 Dec 2024 19:58:44 +0000 Subject: [PATCH 23/25] HW10 linter --- .golangci.yml | 1 + hw10_program_optimization/stats.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 824c997..6ceb3f9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,7 @@ linters-settings: - github.com/DimVlas/otus_hw/hw09_struct_validator/rules - github.com/stretchr/testify/require - github.com/stretchr/testify/assert + - github.com/mailru/easyjson - $gostd exhaustive: explicit-exhaustive-switch: true diff --git a/hw10_program_optimization/stats.go b/hw10_program_optimization/stats.go index 014c7d2..860ae80 100644 --- a/hw10_program_optimization/stats.go +++ b/hw10_program_optimization/stats.go @@ -2,6 +2,7 @@ package hw10programoptimization import ( "bufio" + "errors" "io" "strings" @@ -33,10 +34,9 @@ func domainsStat(r io.Reader, domain string) (DomainStat, error) { for { line, err := rd.ReadBytes('\n') if err != nil { - switch err { - case io.EOF: + if !errors.Is(err, io.EOF) { br = true - default: + } else { return nil, err } } From d8a90f29dcddacde2979722158e2c12984b1ede7 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Wed, 25 Dec 2024 20:02:19 +0000 Subject: [PATCH 24/25] HW10 tests --- hw10_program_optimization/stats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw10_program_optimization/stats.go b/hw10_program_optimization/stats.go index 860ae80..892641c 100644 --- a/hw10_program_optimization/stats.go +++ b/hw10_program_optimization/stats.go @@ -34,7 +34,7 @@ func domainsStat(r io.Reader, domain string) (DomainStat, error) { for { line, err := rd.ReadBytes('\n') if err != nil { - if !errors.Is(err, io.EOF) { + if errors.Is(err, io.EOF) { br = true } else { return nil, err From ac0de95a1810acad2b6afd52994a37932d6fce33 Mon Sep 17 00:00:00 2001 From: DimVlas Date: Wed, 25 Dec 2024 20:05:04 +0000 Subject: [PATCH 25/25] HW10 mod tidy --- hw10_program_optimization/go.mod | 2 -- hw10_program_optimization/go.sum | 4 ---- 2 files changed, 6 deletions(-) diff --git a/hw10_program_optimization/go.mod b/hw10_program_optimization/go.mod index 0521407..57c7414 100644 --- a/hw10_program_optimization/go.mod +++ b/hw10_program_optimization/go.mod @@ -8,10 +8,8 @@ require ( ) require ( - github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/perf v0.0.0-20241204221936-711ff2ab7231 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/hw10_program_optimization/go.sum b/hw10_program_optimization/go.sum index 7469e4f..7d979c2 100644 --- a/hw10_program_optimization/go.sum +++ b/hw10_program_optimization/go.sum @@ -1,5 +1,3 @@ -github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 h1:xlwdaKcTNVW4PtpQb8aKA4Pjy0CdJHEqvFbAnvR5m2g= -github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= 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= @@ -12,8 +10,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/perf v0.0.0-20241204221936-711ff2ab7231 h1:LrlW0UtZ/i5iwthL3HvtiFwcGwgtopumPLfOwdutbzQ= -golang.org/x/perf v0.0.0-20241204221936-711ff2ab7231/go.mod h1:Ha9gd2gpRTtN8P+nXRQCFfD8JnfyIrp8vL362lG7AnU= 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=