From c683ffa4e94d4a5ef9ef6e6448fca46a3fa280fd Mon Sep 17 00:00:00 2001 From: Andrey Ivanov Date: Wed, 19 Aug 2020 20:11:31 +0300 Subject: [PATCH 1/4] HW09 is completed --- .../go-validate/main.go | 176 +++++++++++++++++- .../go-validate/templates.go | 120 ++++++++++++ hw09_generator_of_validators/go.mod | 2 +- hw09_generator_of_validators/models/models.go | 3 +- .../models/models_validation_generated.go | 131 +++++++++++++ 5 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 hw09_generator_of_validators/go-validate/templates.go create mode 100644 hw09_generator_of_validators/models/models_validation_generated.go diff --git a/hw09_generator_of_validators/go-validate/main.go b/hw09_generator_of_validators/go-validate/main.go index 441440a..4da8838 100644 --- a/hw09_generator_of_validators/go-validate/main.go +++ b/hw09_generator_of_validators/go-validate/main.go @@ -1,5 +1,177 @@ package main -func main() { - // Place your code here +import ( + "bytes" + "go/ast" + "go/parser" + "go/token" + "io" + "log" + "os" + "reflect" + "strings" + "text/template" +) + +type TemplateVars struct { + StructName string + FieldName string + FieldType string + ValidatorValue string + IsSlice bool +} + +const str string = "string" + +func main() { + buf := new(bytes.Buffer) + buf.WriteString(validatorHeader) + fset := token.NewFileSet() + //node, err := parser.ParseFile(fset, os.Args[1], nil, parser.ParseComments) + node, err := parser.ParseFile(fset, "models/models.go", nil, parser.ParseComments) + if err != nil { + log.Fatal("не удалось открыть файл", err.Error()) + } + // Перебираем ноды + for _, f := range node.Decls { + gd, ok := f.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range gd.Specs { + // Ищем структуры + t, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + s, ok := t.Type.(*ast.StructType) + if !ok { + continue + } + // Если все проверки пройдены, это структура + // Итерируем ее поля + + tmplV := TemplateVars{StructName: t.Name.Name} + z := template.Must(template.New("validatorFunctionHeader").Parse(validatorFunctionHeader)) + if z.Execute(buf, tmplV) != nil { + log.Fatal("ошибка сборки шаблона:", err) + } + err = iterStructFields(s, buf) + if err != nil { + log.Fatal("ошибка в ходе перебора полей структуры:", err.Error()) + } + buf.WriteString(validatorFunctionFooter) + } + } + buf.WriteString(validatorFunctions) + f, err := os.Create("models/models_validation_generated.go") + if err != nil { + log.Fatal("не удалось создать/открыть файл:", err.Error()) + } + defer func() { + if f.Close() != nil { + panic("Не удается захлопнуть файл!") + } + }() + _, err = buf.WriteTo(f) + if err != nil { + log.Fatal("не удалось записать буфер в файл:", err.Error()) + } + // Тут нужно открыть файл, записать в него байтбуфер и закрыть файд +} + +func iterStructFields(s *ast.StructType, buf io.Writer) error { + for _, field := range s.Fields.List { + // Рекурсивный вызов, в случае если поле является структурой + //switch field.Type.(type) { + //case *ast.StructType: + // if err:=iterStructFields(field.Type.(*ast.StructType), buf); err!=nil { log.Fatal("Структура не распарсилась в рекурсии:", err.Error()) } + //} + if len(field.Names) == 0 { + continue + } + // Достаем тэг поля + if field.Tag == nil { + continue + } + tag, ok := getTagString(field, "validate") + if !ok { + continue + } + //Ищем комбинации + for _, comb := range strings.Split(tag, "|") { + k := strings.Split(comb, ":") + + isSlice, fieldType := getFieldType(field) + tmplV := TemplateVars{FieldName: field.Names[0].Name, FieldType: fieldType, ValidatorValue: k[1], IsSlice: isSlice} + + z := &template.Template{} + switch k[0] { + case "min": + z = template.Must(template.New("validatorMin").Parse(validatorMin)) + case "max": + z = template.Must(template.New("validatorMax").Parse(validatorMax)) + case "in": + switch fieldType { + case "int": + z = template.Must(template.New("validatorInInt").Parse(validatorInInt)) + case str: + z = template.Must(template.New("validatorInStr").Parse(validatorInStr)) + } + case "len": + z = template.Must(template.New("validatorLen").Parse(validatorLen)) + case "regexp": + z = template.Must(template.New("validatorRegexp").Parse(validatorRegexp)) + default: + log.Fatal("Неизвестный параметр тега validate") + } + err := z.Execute(buf, tmplV) + if err != nil { + return err + } + } + } + return nil +} + +func getTagString(f *ast.Field, tag string) (string, bool) { + t := reflect.StructTag(f.Tag.Value[1 : len(f.Tag.Value)-1]) + v := t.Get(tag) + if v == "" { + return "", false + } + return v, true +} + +func getFieldType(field *ast.Field) (bool, string) { + var fieldSlice bool + var fieldType string + switch field.Type.(type) { + case *ast.Ident: + fieldSlice = false + fieldType = field.Type.(*ast.Ident).Name + if field.Type.(*ast.Ident).Obj != nil { + t, ok := field.Type.(*ast.Ident).Obj.Decl.(*ast.TypeSpec) + if !ok { + return false, "" + } + s, ok := t.Type.(*ast.Ident) + if !ok { + return false, "" + } + fieldType = s.Name + } + case *ast.ArrayType: + fieldSlice = true + fieldType = field.Type.(*ast.ArrayType).Elt.(*ast.Ident).Name + } + switch { + case strings.Contains(fieldType, "int"): + return fieldSlice, "int" + case strings.Contains(fieldType, "float"): + return fieldSlice, "float" + case fieldType == str: + return fieldSlice, str + } + return false, "" } diff --git a/hw09_generator_of_validators/go-validate/templates.go b/hw09_generator_of_validators/go-validate/templates.go new file mode 100644 index 0000000..ab2812c --- /dev/null +++ b/hw09_generator_of_validators/go-validate/templates.go @@ -0,0 +1,120 @@ +package main + +const validatorHeader string = `// Code generated by cool go-validate tool; DO NOT EDIT. +package models + +import ( + "log" + "regexp" + "strconv" + "strings" +) + +type ValidationError struct { + Name string + Error error +} +` + +const validatorFunctionHeader string = ` +func (u {{.StructName}}) Validate() ([]ValidationError, error) { + res := []ValidationError{} + var err error + var ok bool` + +const validatorFunctionFooter string = ` + log.Println(ok) + return res, err +} +` + +const validatorLen string = ` + if ok, err = valLen({{if .IsSlice}}u.{{.FieldName}}{{- else}}[]string{string(u.{{.FieldName}})}{{- end}},{{.ValidatorValue}}); !ok { res=append(res,ValidationError{"{{.FieldName}}",fmt.Errorf("len:{{.ValidatorValue}}")})}` +const validatorMin string = ` + if ok, err = valMin({{if .IsSlice}}u.{{.FieldName}}{{- else}}[]int{int(u.{{.FieldName}})}{{- end}},{{.ValidatorValue}}); !ok { res=append(res,ValidationError{"{{.FieldName}}",fmt.Errorf("min:{{.ValidatorValue}}")})}` +const validatorMax string = ` + if ok, err = valMax({{if .IsSlice}}u.{{.FieldName}}{{- else}}[]int{int(u.{{.FieldName}})}{{- end}},{{.ValidatorValue}}); !ok { res=append(res,ValidationError{"{{.FieldName}}",fmt.Errorf("max:{{.ValidatorValue}}")})}` +const validatorRegexp string = ` + if ok, err = valRegexp({{if .IsSlice}}u.{{.FieldName}}{{- else}}[]string{string(u.{{.FieldName}})}{{- end}},` + "`{{.ValidatorValue}}`" + `); !ok { res=append(res,ValidationError{"{{.FieldName}}",fmt.Errorf(` + "`regexp:{{.ValidatorValue}}`" + `)})}` +const validatorInStr string = ` + if ok, err = valInString({{if .IsSlice}}u.{{.FieldName}}{{- else}}[]string{string(u.{{.FieldName}})}{{- end}},` + "`{{.ValidatorValue}}`" + `); !ok { res=append(res,ValidationError{"{{.FieldName}}",fmt.Errorf(` + "`in:{{.ValidatorValue}}`" + `)})}` +const validatorInInt string = ` + if ok, err = valInInt({{if .IsSlice}}u.{{.FieldName}}{{- else}}[]int{int(u.{{.FieldName}})}{{- end}},` + "`{{.ValidatorValue}}`" + `); !ok { res=append(res,ValidationError{"{{.FieldName}}",fmt.Errorf("in:{{.ValidatorValue}}")})}` + +const validatorFunctions string = ` + +func valLen(s []string, n int) (bool,error) { + res := true + for _,v :=range s { + if len(v)>n { + res=false + break + } + } + return res,nil +} + +func valMin(i []int, n int) (bool,error) { + res := true + for _,v :=range i { + if vn { + res=false + break + } + } + return res,nil +} + +func valRegexp(s []string, r string) (bool,error) { + res := true + var err error + rg := regexp.MustCompile(r) + for _,v :=range s { + if !rg.MatchString(v) { + res=false + break + } + } + return res,err +} + +func valInString(s []string, r string) (bool,error) { + i := false + for _,k :=range s { + i = false + for _, v := range strings.Split(r, ",") { + if k == v { + i = true + } + } + if !i { break } + } + return i, nil +} + +func valInInt(s []int, r string) (bool,error) { + i := false + var err error + var m int + for _,k :=range s { + i = false + for _, v := range strings.Split(r, ",") { + m,err=strconv.Atoi(v) + if k == m { i = true } + } + if !i { break } + } + return i, err +} +` diff --git a/hw09_generator_of_validators/go.mod b/hw09_generator_of_validators/go.mod index b305fde..12039f5 100644 --- a/hw09_generator_of_validators/go.mod +++ b/hw09_generator_of_validators/go.mod @@ -1,4 +1,4 @@ -module github.com/fixme_my_friend/hw09_generator_of_validators +module github.com/tiburon-777/HW_OTUS/hw09_generator_of_validators go 1.14 diff --git a/hw09_generator_of_validators/models/models.go b/hw09_generator_of_validators/models/models.go index d8ee989..2700eb6 100644 --- a/hw09_generator_of_validators/models/models.go +++ b/hw09_generator_of_validators/models/models.go @@ -1,8 +1,9 @@ package models +//go:generate go-validate models.go type UserRole string -// NOTE: Several struct specs in one type declaration are allowed +// NOTE: Several struct specs in one type declaration are allowed. type ( User struct { ID string `json:"id" validate:"len:36"` diff --git a/hw09_generator_of_validators/models/models_validation_generated.go b/hw09_generator_of_validators/models/models_validation_generated.go new file mode 100644 index 0000000..dfd14cb --- /dev/null +++ b/hw09_generator_of_validators/models/models_validation_generated.go @@ -0,0 +1,131 @@ +// Code generated by cool go-validate tool; DO NOT EDIT. +package models + +import ( + "fmt" + "log" + "regexp" + "strconv" + "strings" +) + +type ValidationError struct { + Name string + Error error +} + +func (u User) Validate() ([]ValidationError, error) { + res := []ValidationError{} + var err error + var ok bool + if ok, err = valLen([]string{string(u.ID)},36); !ok { res=append(res,ValidationError{"ID",fmt.Errorf("len:36")})} + if ok, err = valMin([]int{int(u.Age)},18); !ok { res=append(res,ValidationError{"Age",fmt.Errorf("min:18")})} + if ok, err = valMax([]int{int(u.Age)},50); !ok { res=append(res,ValidationError{"Age",fmt.Errorf("max:50")})} + if ok, err = valRegexp([]string{string(u.Email)},`^\w+@\w+\.\w+$`); !ok { res=append(res,ValidationError{"Email",fmt.Errorf(`regexp:^\w+@\w+\.\w+$`)})} + if ok, err = valInString([]string{string(u.Role)},`admin,stuff`); !ok { res=append(res,ValidationError{"Role",fmt.Errorf(`in:admin,stuff`)})} + if ok, err = valLen(u.Phones,11); !ok { res=append(res,ValidationError{"Phones",fmt.Errorf("len:11")})} + log.Println(ok) + return res, err +} + +func (u App) Validate() ([]ValidationError, error) { + res := []ValidationError{} + var err error + var ok bool + if ok, err = valLen([]string{string(u.Version)},5); !ok { res=append(res,ValidationError{"Version",fmt.Errorf("len:5")})} + log.Println(ok) + return res, err +} + +func (u Token) Validate() ([]ValidationError, error) { + res := []ValidationError{} + var err error + var ok bool + log.Println(ok) + return res, err +} + +func (u Response) Validate() ([]ValidationError, error) { + res := []ValidationError{} + var err error + var ok bool + if ok, err = valInInt([]int{int(u.Code)},`200,404,500`); !ok { res=append(res,ValidationError{"Code",fmt.Errorf("in:200,404,500")})} + log.Println(ok) + return res, err +} + + +func valLen(s []string, n int) (bool,error) { + res := true + for _,v :=range s { + if len(v)>n { + res=false + break + } + } + return res,nil +} + +func valMin(i []int, n int) (bool,error) { + res := true + for _,v :=range i { + if vn { + res=false + break + } + } + return res,nil +} + +func valRegexp(s []string, r string) (bool,error) { + res := true + var err error + rg := regexp.MustCompile(r) + for _,v :=range s { + if !rg.MatchString(v) { + res=false + break + } + } + return res,err +} + +func valInString(s []string, r string) (bool,error) { + i := false + for _,k :=range s { + i = false + for _, v := range strings.Split(r, ",") { + if k == v { + i = true + } + } + if !i { break } + } + return i, nil +} + +func valInInt(s []int, r string) (bool,error) { + i := false + var err error + var m int + for _,k :=range s { + i = false + for _, v := range strings.Split(r, ",") { + m,err=strconv.Atoi(v) + if k == m { i = true } + } + if !i { break } + } + return i, err +} From ade974b5e23e3e5d133c1edb04070cbe5fec9a67 Mon Sep 17 00:00:00 2001 From: Andrey Ivanov Date: Wed, 19 Aug 2020 20:14:07 +0300 Subject: [PATCH 2/4] HW09 is completed --- hw09_generator_of_validators/go-validate/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw09_generator_of_validators/go-validate/main.go b/hw09_generator_of_validators/go-validate/main.go index 4da8838..5b29ada 100644 --- a/hw09_generator_of_validators/go-validate/main.go +++ b/hw09_generator_of_validators/go-validate/main.go @@ -27,8 +27,8 @@ func main() { buf := new(bytes.Buffer) buf.WriteString(validatorHeader) fset := token.NewFileSet() - //node, err := parser.ParseFile(fset, os.Args[1], nil, parser.ParseComments) - node, err := parser.ParseFile(fset, "models/models.go", nil, parser.ParseComments) + node, err := parser.ParseFile(fset, os.Args[1], nil, parser.ParseComments) + //node, err := parser.ParseFile(fset, "models/models.go", nil, parser.ParseComments) if err != nil { log.Fatal("не удалось открыть файл", err.Error()) } From c8e81106d8203b53be5fee547a1038ce76341459 Mon Sep 17 00:00:00 2001 From: Andrey Ivanov Date: Thu, 20 Aug 2020 10:20:49 +0300 Subject: [PATCH 3/4] HW09 is completed --- .golangci.yml | 3 +- .../go-validate/main.go | 60 +++--- .../go-validate/main_test.go | 199 ++++++++++++++++++ .../go-validate/templates.go | 7 +- .../models/models_test.go | 31 ++- .../models/models_validation_generated.go | 14 +- 6 files changed, 271 insertions(+), 43 deletions(-) create mode 100644 hw09_generator_of_validators/go-validate/main_test.go diff --git a/.golangci.yml b/.golangci.yml index a353259..edebcda 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,4 +15,5 @@ linters: - wsl - gofumpt - gosec - - nlreturn \ No newline at end of file + - nlreturn + - gocritic \ No newline at end of file diff --git a/hw09_generator_of_validators/go-validate/main.go b/hw09_generator_of_validators/go-validate/main.go index 5b29ada..d47af70 100644 --- a/hw09_generator_of_validators/go-validate/main.go +++ b/hw09_generator_of_validators/go-validate/main.go @@ -32,7 +32,7 @@ func main() { if err != nil { log.Fatal("не удалось открыть файл", err.Error()) } - // Перебираем ноды + // Перебираем корневые узлы for _, f := range node.Decls { gd, ok := f.(*ast.GenDecl) if !ok { @@ -50,21 +50,27 @@ func main() { } // Если все проверки пройдены, это структура // Итерируем ее поля - + tmpBuf := new(bytes.Buffer) tmplV := TemplateVars{StructName: t.Name.Name} z := template.Must(template.New("validatorFunctionHeader").Parse(validatorFunctionHeader)) - if z.Execute(buf, tmplV) != nil { - log.Fatal("ошибка сборки шаблона:", err) + if z.Execute(tmpBuf, tmplV) != nil { + log.Fatal("ошибка сборки шаблона:") } - err = iterStructFields(s, buf) + ok, err = iterStructFields("", s, tmpBuf) + if err != nil { log.Fatal("ошибка в ходе перебора полей структуры:", err.Error()) } - buf.WriteString(validatorFunctionFooter) + tmpBuf.Write([]byte(validatorFunctionFooter)) + if ok { + if _, err := tmpBuf.WriteTo(buf); err != nil { + log.Fatal("ошибка перекладывания в буфер:", err.Error()) + } + } } } buf.WriteString(validatorFunctions) - f, err := os.Create("models/models_validation_generated.go") + f, err := os.Create(strings.Split(os.Args[1], ".")[0] + "_validation_generated.go") if err != nil { log.Fatal("не удалось создать/открыть файл:", err.Error()) } @@ -77,33 +83,33 @@ func main() { if err != nil { log.Fatal("не удалось записать буфер в файл:", err.Error()) } - // Тут нужно открыть файл, записать в него байтбуфер и закрыть файд } -func iterStructFields(s *ast.StructType, buf io.Writer) error { +func iterStructFields(name string, s *ast.StructType, buf io.Writer) (bool, error) { + var isValidated bool for _, field := range s.Fields.List { // Рекурсивный вызов, в случае если поле является структурой - //switch field.Type.(type) { - //case *ast.StructType: - // if err:=iterStructFields(field.Type.(*ast.StructType), buf); err!=nil { log.Fatal("Структура не распарсилась в рекурсии:", err.Error()) } - //} + switch field.Type.(type) { + case *ast.StructType: + if _, err := iterStructFields("."+field.Names[0].Name, field.Type.(*ast.StructType), buf); err != nil { + return false, err + } + } if len(field.Names) == 0 { continue } // Достаем тэг поля - if field.Tag == nil { - continue - } tag, ok := getTagString(field, "validate") if !ok { continue } + isValidated = true //Ищем комбинации for _, comb := range strings.Split(tag, "|") { k := strings.Split(comb, ":") isSlice, fieldType := getFieldType(field) - tmplV := TemplateVars{FieldName: field.Names[0].Name, FieldType: fieldType, ValidatorValue: k[1], IsSlice: isSlice} + tmplV := TemplateVars{FieldName: name + field.Names[0].Name, FieldType: fieldType, ValidatorValue: k[1], IsSlice: isSlice} z := &template.Template{} switch k[0] { @@ -127,14 +133,17 @@ func iterStructFields(s *ast.StructType, buf io.Writer) error { } err := z.Execute(buf, tmplV) if err != nil { - return err + return false, err } } } - return nil + return isValidated, nil } func getTagString(f *ast.Field, tag string) (string, bool) { + if f.Tag == nil { + return "", false + } t := reflect.StructTag(f.Tag.Value[1 : len(f.Tag.Value)-1]) v := t.Get(tag) if v == "" { @@ -144,11 +153,12 @@ func getTagString(f *ast.Field, tag string) (string, bool) { } func getFieldType(field *ast.Field) (bool, string) { - var fieldSlice bool + var isSlice bool var fieldType string + // Эту конструкцию не пропускал линтер gocritic, под предлогом "typeSwitchVar: 2 cases can benefit from type switch with assignment". Я не вижу тут возможности срабатывания обеих веток. тип ast.Expr не может привестись и к *ast.Ident и к *ast.ArrayType одновременно. Пришлось, отключить старикашку критика. switch field.Type.(type) { case *ast.Ident: - fieldSlice = false + isSlice = false fieldType = field.Type.(*ast.Ident).Name if field.Type.(*ast.Ident).Obj != nil { t, ok := field.Type.(*ast.Ident).Obj.Decl.(*ast.TypeSpec) @@ -162,16 +172,16 @@ func getFieldType(field *ast.Field) (bool, string) { fieldType = s.Name } case *ast.ArrayType: - fieldSlice = true + isSlice = true fieldType = field.Type.(*ast.ArrayType).Elt.(*ast.Ident).Name } switch { case strings.Contains(fieldType, "int"): - return fieldSlice, "int" + return isSlice, "int" case strings.Contains(fieldType, "float"): - return fieldSlice, "float" + return isSlice, "float" case fieldType == str: - return fieldSlice, str + return isSlice, str } return false, "" } diff --git a/hw09_generator_of_validators/go-validate/main_test.go b/hw09_generator_of_validators/go-validate/main_test.go new file mode 100644 index 0000000..e68c2e8 --- /dev/null +++ b/hw09_generator_of_validators/go-validate/main_test.go @@ -0,0 +1,199 @@ +package main + +import ( + "bytes" + "github.com/stretchr/testify/require" + "go/ast" + "go/parser" + "go/token" + "strings" + "testing" +) + +const TestModel string = ` +package testmodel +type ( + TestSingle struct { + TstIntMinMax int ` + "`" + `validate:"min:18|max:50"` + "`" + ` + TstIntIn int ` + "`" + `validate:"in:45,21,57,12"` + "`" + ` + TstStringLen string ` + "`" + `condidate:"len:36"` + "`" + ` + TstStringRegexp string + TstStringIn string ` + "`" + `validate:"in:admin,stuff"` + "`" + ` + } +) +type ( + TestSlice struct { + TstIntMinMax []int ` + "`" + `validate:"min:18|max:50"` + "`" + ` + TstIntIn []int ` + "`" + `validate:"in:45,21,57,12"` + "`" + ` + TstStringLen []string ` + "`" + `json:"len:36"` + "`" + ` + TstStringRegexp []string ` + "`" + `validate:"regexp:^\\w+@\\w+\\.\\w+$"` + "`" + ` + TstStringIn []string ` + "`" + `xml:"in:admin,stuff"` + "`" + ` + } +)` +const FineStructure string = ` +package testmodel +type ( + TestSingle struct { + TstIntMinMax int ` + "`" + `validate:"min:18|max:50"` + "`" + ` + TstIntIn int ` + "`" + `validate:"in:45,21,57,12"` + "`" + ` + TstStringLen string ` + "`" + `validate:"len:36"` + "`" + ` + TstStringIn string ` + "`" + `validate:"in:admin,stuff"` + "`" + ` + } +)` + +const WrongStructure string = ` +package testmodel +type ( + TestSingle struct { + TstIntMinMax int ` + "`" + `validate:"min:WRONG|max:#$&^%"` + "`" + ` + TstIntIn int ` + "`" + `validate:"in:45,STRING,57,12.5"` + "`" + ` + TstStringLen string ` + "`" + `validate:"len:"` + "`" + ` + TstStringIn string ` + "`" + `validate:"in:admin,stuff,,,"` + "`" + ` + } +)` + +const FineStructureExpcted string = "\n\tif ok, err = valMin([]int{int(u.TstIntMinMax)},18); !ok { res=append(res,ValidationError{\"TstIntMinMax\",fmt.Errorf(\"min:18\")})}\n\tif ok, err = valMax([]int{int(u.TstIntMinMax)},50); !ok { res=append(res,ValidationError{\"TstIntMinMax\",fmt.Errorf(\"max:50\")})}\n\tif ok, err = valInInt([]int{int(u.TstIntIn)},`45,21,57,12`); !ok { res=append(res,ValidationError{\"TstIntIn\",fmt.Errorf(\"in:45,21,57,12\")})}\n\tif ok, err = valLen([]string{string(u.TstStringLen)},36); !ok { res=append(res,ValidationError{\"TstStringLen\",fmt.Errorf(\"len:36\")})}\n\tif ok, err = valInString([]string{string(u.TstStringIn)},`admin,stuff`); !ok { res=append(res,ValidationError{\"TstStringIn\",fmt.Errorf(`in:admin,stuff`)})}" + +const WrongStructureExpcted string = "\n\tif ok, err = valMin([]int{int(u.TstIntMinMax)},WRONG); !ok { res=append(res,ValidationError{\"TstIntMinMax\",fmt.Errorf(\"min:WRONG\")})}\n\tif ok, err = valMax([]int{int(u.TstIntMinMax)},#$&^%); !ok { res=append(res,ValidationError{\"TstIntMinMax\",fmt.Errorf(\"max:#$&^%\")})}\n\tif ok, err = valInInt([]int{int(u.TstIntIn)},`45,STRING,57,12.5`); !ok { res=append(res,ValidationError{\"TstIntIn\",fmt.Errorf(\"in:45,STRING,57,12.5\")})}\n\tif ok, err = valLen([]string{string(u.TstStringLen)},); !ok { res=append(res,ValidationError{\"TstStringLen\",fmt.Errorf(\"len:\")})}\n\tif ok, err = valInString([]string{string(u.TstStringIn)},`admin,stuff,,,`); !ok { res=append(res,ValidationError{\"TstStringIn\",fmt.Errorf(`in:admin,stuff,,,`)})}" + +func TestGetTagString(T *testing.T) { + var tags = []string{"min:18|max:50", "in:45,21,57,12", "", "", "in:admin,stuff", "min:18|max:50", "in:45,21,57,12", "", `regexp:^\w+@\w+\.\w+$`, ""} + var oks = []bool{true, true, false, false, true, true, true, false, true, false} + fset := token.NewFileSet() + node, _ := parser.ParseFile(fset, "", []byte(TestModel), parser.ParseComments) + for _, f := range node.Decls { + gd, ok := f.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range gd.Specs { + t, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + s, ok := t.Type.(*ast.StructType) + if !ok { + continue + } + for _, field := range s.Fields.List { + T.Run("Single int field "+field.Names[0].Name, func(t *testing.T) { + tagString, ok := getTagString(field, "validate") + require.Equal(t, oks[0], ok) + require.Equal(t, tags[0], tagString) + tags = tags[1:] + oks = oks[1:] + }) + } + } + } +} + +func TestGetFieldType(T *testing.T) { + fset := token.NewFileSet() + node, _ := parser.ParseFile(fset, "", []byte(TestModel), parser.ParseComments) + for _, f := range node.Decls { + gd, ok := f.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range gd.Specs { + t, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + s, ok := t.Type.(*ast.StructType) + if !ok { + continue + } + if t.Name.Name == "TestSingle" { + for _, field := range s.Fields.List { + if strings.Index(field.Names[0].Name, "Int") > 0 { + T.Run("Single int field "+field.Names[0].Name, func(t *testing.T) { + isSlice, fType := getFieldType(field) + require.Equal(t, false, isSlice) + require.Equal(t, "int", fType) + }) + } + if strings.Index(field.Names[0].Name, "String") > 0 { + T.Run("Single string field "+field.Names[0].Name, func(t *testing.T) { + isSlice, fType := getFieldType(field) + require.Equal(t, false, isSlice) + require.Equal(t, "string", fType) + }) + } + } + } + if t.Name.Name == "TestSlice" { + for _, field := range s.Fields.List { + if strings.Index(field.Names[0].Name, "Int") > 0 { + T.Run("Slice int field "+field.Names[0].Name, func(t *testing.T) { + isSlice, fType := getFieldType(field) + require.Equal(t, true, isSlice) + require.Equal(t, "int", fType) + }) + } + if strings.Index(field.Names[0].Name, "String") > 0 { + T.Run("Slice string field "+field.Names[0].Name, func(t *testing.T) { + isSlice, fType := getFieldType(field) + require.Equal(t, true, isSlice) + require.Equal(t, "string", fType) + }) + } + } + } + } + } +} + +func TestIterStructFields(T *testing.T) { + fset := token.NewFileSet() + node, _ := parser.ParseFile(fset, "", []byte(FineStructure), parser.ParseComments) + for _, f := range node.Decls { + gd, ok := f.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range gd.Specs { + t, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + s, ok := t.Type.(*ast.StructType) + if !ok { + continue + } + T.Run("Fine structure", func(t *testing.T) { + buf := bytes.NewBufferString("") + err := iterStructFields(s, buf) + require.Equal(t, FineStructureExpcted, buf.String()) + require.NoError(t, err) + }) + } + } + + fset1 := token.NewFileSet() + node1, _ := parser.ParseFile(fset1, "", []byte(WrongStructure), parser.ParseComments) + for _, f1 := range node1.Decls { + gd1, ok := f1.(*ast.GenDecl) + if !ok { + continue + } + for _, spec1 := range gd1.Specs { + t, ok := spec1.(*ast.TypeSpec) + if !ok { + continue + } + s1, ok := t.Type.(*ast.StructType) + if !ok { + continue + } + T.Run("Wrong structure", func(t *testing.T) { + buf1 := bytes.NewBufferString("") + err := iterStructFields(s1, buf1) + require.Equal(t, WrongStructureExpcted, buf1.String()) + require.NoError(t, err) + }) + } + } + +} diff --git a/hw09_generator_of_validators/go-validate/templates.go b/hw09_generator_of_validators/go-validate/templates.go index ab2812c..20e627a 100644 --- a/hw09_generator_of_validators/go-validate/templates.go +++ b/hw09_generator_of_validators/go-validate/templates.go @@ -4,6 +4,7 @@ const validatorHeader string = `// Code generated by cool go-validate tool; DO N package models import ( + "fmt" "log" "regexp" "strconv" @@ -11,8 +12,8 @@ import ( ) type ValidationError struct { - Name string - Error error + Field string + Err error } ` @@ -46,7 +47,7 @@ const validatorFunctions string = ` func valLen(s []string, n int) (bool,error) { res := true for _,v :=range s { - if len(v)>n { + if len(v)!=n { res=false break } diff --git a/hw09_generator_of_validators/models/models_test.go b/hw09_generator_of_validators/models/models_test.go index e3a5dc2..9522ac3 100644 --- a/hw09_generator_of_validators/models/models_test.go +++ b/hw09_generator_of_validators/models/models_test.go @@ -59,9 +59,34 @@ func TestUserValidation(t *testing.T) { requireOneFieldErr(t, errs, "Age") }) - t.Run("phones slice", func(t *testing.T) { - // Write me :) - t.Fail() + t.Run("fail phones slice", func(t *testing.T) { + badUser := User{ + ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", + Name: "John", + Age: 24, + Email: "john@abrams.com", + Role: "admin", + Phones: []string{"+12dfwdf343242343", "898298741293", "fdsf"}, + } + + errs, err := badUser.Validate() + require.Nil(t, err) + requireOneFieldErr(t, errs, "Phones") + }) + + t.Run("pass phones slice", func(t *testing.T) { + badUser := User{ + ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", + Name: "John", + Age: 24, + Email: "john@abrams.com", + Role: "admin", + Phones: []string{"12345678901", "qazxswedcvf", "............"}, + } + + errs, err := badUser.Validate() + require.Nil(t, err) + requireOneFieldErr(t, errs, "Phones") }) t.Run("many errors", func(t *testing.T) { diff --git a/hw09_generator_of_validators/models/models_validation_generated.go b/hw09_generator_of_validators/models/models_validation_generated.go index dfd14cb..f1589ec 100644 --- a/hw09_generator_of_validators/models/models_validation_generated.go +++ b/hw09_generator_of_validators/models/models_validation_generated.go @@ -10,8 +10,8 @@ import ( ) type ValidationError struct { - Name string - Error error + Field string + Err error } func (u User) Validate() ([]ValidationError, error) { @@ -37,14 +37,6 @@ func (u App) Validate() ([]ValidationError, error) { return res, err } -func (u Token) Validate() ([]ValidationError, error) { - res := []ValidationError{} - var err error - var ok bool - log.Println(ok) - return res, err -} - func (u Response) Validate() ([]ValidationError, error) { res := []ValidationError{} var err error @@ -58,7 +50,7 @@ func (u Response) Validate() ([]ValidationError, error) { func valLen(s []string, n int) (bool,error) { res := true for _,v :=range s { - if len(v)>n { + if len(v)!=n { res=false break } From a20db7fc16738ea08271a80d9dba57231bee7ec1 Mon Sep 17 00:00:00 2001 From: Andrey Ivanov Date: Thu, 20 Aug 2020 10:41:44 +0300 Subject: [PATCH 4/4] HW09 is completed --- .../go-validate/main.go | 2 +- .../go-validate/main_test.go | 4 +- hw09_generator_of_validators/models/models.go | 16 +++--- .../models/models_test.go | 54 ++++++++++++------- .../models/models_validation_generated.go | 1 + 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/hw09_generator_of_validators/go-validate/main.go b/hw09_generator_of_validators/go-validate/main.go index d47af70..3d44e20 100644 --- a/hw09_generator_of_validators/go-validate/main.go +++ b/hw09_generator_of_validators/go-validate/main.go @@ -91,7 +91,7 @@ func iterStructFields(name string, s *ast.StructType, buf io.Writer) (bool, erro // Рекурсивный вызов, в случае если поле является структурой switch field.Type.(type) { case *ast.StructType: - if _, err := iterStructFields("."+field.Names[0].Name, field.Type.(*ast.StructType), buf); err != nil { + if _, err := iterStructFields(field.Names[0].Name+".", field.Type.(*ast.StructType), buf); err != nil { return false, err } } diff --git a/hw09_generator_of_validators/go-validate/main_test.go b/hw09_generator_of_validators/go-validate/main_test.go index e68c2e8..c9db0d2 100644 --- a/hw09_generator_of_validators/go-validate/main_test.go +++ b/hw09_generator_of_validators/go-validate/main_test.go @@ -164,7 +164,7 @@ func TestIterStructFields(T *testing.T) { } T.Run("Fine structure", func(t *testing.T) { buf := bytes.NewBufferString("") - err := iterStructFields(s, buf) + _, err := iterStructFields("", s, buf) require.Equal(t, FineStructureExpcted, buf.String()) require.NoError(t, err) }) @@ -189,7 +189,7 @@ func TestIterStructFields(T *testing.T) { } T.Run("Wrong structure", func(t *testing.T) { buf1 := bytes.NewBufferString("") - err := iterStructFields(s1, buf1) + _, err := iterStructFields("", s1, buf1) require.Equal(t, WrongStructureExpcted, buf1.String()) require.NoError(t, err) }) diff --git a/hw09_generator_of_validators/models/models.go b/hw09_generator_of_validators/models/models.go index 2700eb6..3f603f1 100644 --- a/hw09_generator_of_validators/models/models.go +++ b/hw09_generator_of_validators/models/models.go @@ -6,12 +6,16 @@ type UserRole string // NOTE: Several struct specs in one type declaration are allowed. 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"` + 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"` + Response struct { + Code int `validate:"in:200,404,500"` + Body string `json:"omitempty"` + } } App struct { diff --git a/hw09_generator_of_validators/models/models_test.go b/hw09_generator_of_validators/models/models_test.go index 9522ac3..99ba068 100644 --- a/hw09_generator_of_validators/models/models_test.go +++ b/hw09_generator_of_validators/models/models_test.go @@ -17,11 +17,12 @@ func TestUserValidation(t *testing.T) { requireValidation(t, User{}) goodUser := User{ - ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", - Name: "John", - Age: 24, - Email: "john@abrams.com", - Role: "admin", + ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", + Name: "John", + Age: 24, + Email: "john@abrams.com", + Role: "admin", + Response: Response{Code: 200}, } requireNoValidationErrors(t, goodUser) @@ -61,12 +62,13 @@ func TestUserValidation(t *testing.T) { t.Run("fail phones slice", func(t *testing.T) { badUser := User{ - ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", - Name: "John", - Age: 24, - Email: "john@abrams.com", - Role: "admin", - Phones: []string{"+12dfwdf343242343", "898298741293", "fdsf"}, + ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", + Name: "John", + Age: 24, + Email: "john@abrams.com", + Role: "admin", + Phones: []string{"+12dfwdf343242343", "898298741293", "fdsf"}, + Response: Response{Code: 404}, } errs, err := badUser.Validate() @@ -75,16 +77,30 @@ func TestUserValidation(t *testing.T) { }) t.Run("pass phones slice", func(t *testing.T) { - badUser := User{ - ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", - Name: "John", - Age: 24, - Email: "john@abrams.com", - Role: "admin", - Phones: []string{"12345678901", "qazxswedcvf", "............"}, + goodUser := User{ + ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", + Name: "John", + Age: 24, + Email: "john@abrams.com", + Role: "admin", + Phones: []string{"12345678901", "qazxswedcvf", "..........."}, + Response: Response{Code: 500}, + } + requireNoValidationErrors(t, goodUser) + }) + + t.Run("embeded structure", func(t *testing.T) { + goodUser := User{ + ID: "0a44d582-9749-11ea-a056-9ff7f30f0608", + Name: "John", + Age: 24, + Email: "john@abrams.com", + Role: "admin", + Phones: []string{"12345678901", "qazxswedcvf", "............"}, + Response: Response{Code: 500}, } - errs, err := badUser.Validate() + errs, err := goodUser.Validate() require.Nil(t, err) requireOneFieldErr(t, errs, "Phones") }) diff --git a/hw09_generator_of_validators/models/models_validation_generated.go b/hw09_generator_of_validators/models/models_validation_generated.go index f1589ec..acec527 100644 --- a/hw09_generator_of_validators/models/models_validation_generated.go +++ b/hw09_generator_of_validators/models/models_validation_generated.go @@ -24,6 +24,7 @@ func (u User) Validate() ([]ValidationError, error) { if ok, err = valRegexp([]string{string(u.Email)},`^\w+@\w+\.\w+$`); !ok { res=append(res,ValidationError{"Email",fmt.Errorf(`regexp:^\w+@\w+\.\w+$`)})} if ok, err = valInString([]string{string(u.Role)},`admin,stuff`); !ok { res=append(res,ValidationError{"Role",fmt.Errorf(`in:admin,stuff`)})} if ok, err = valLen(u.Phones,11); !ok { res=append(res,ValidationError{"Phones",fmt.Errorf("len:11")})} + if ok, err = valInInt([]int{int(u.Response.Code)},`200,404,500`); !ok { res=append(res,ValidationError{"Response.Code",fmt.Errorf("in:200,404,500")})} log.Println(ok) return res, err }