HW09 is completed

This commit is contained in:
Andrey Ivanov 2020-08-20 10:20:49 +03:00 committed by Andrey Ivanov
parent ade974b5e2
commit c8e81106d8
6 changed files with 271 additions and 43 deletions

View File

@ -16,3 +16,4 @@ linters:
- gofumpt - gofumpt
- gosec - gosec
- nlreturn - nlreturn
- gocritic

View File

@ -32,7 +32,7 @@ func main() {
if err != nil { if err != nil {
log.Fatal("не удалось открыть файл", err.Error()) log.Fatal("не удалось открыть файл", err.Error())
} }
// Перебираем ноды // Перебираем корневые узлы
for _, f := range node.Decls { for _, f := range node.Decls {
gd, ok := f.(*ast.GenDecl) gd, ok := f.(*ast.GenDecl)
if !ok { if !ok {
@ -50,21 +50,27 @@ func main() {
} }
// Если все проверки пройдены, это структура // Если все проверки пройдены, это структура
// Итерируем ее поля // Итерируем ее поля
tmpBuf := new(bytes.Buffer)
tmplV := TemplateVars{StructName: t.Name.Name} tmplV := TemplateVars{StructName: t.Name.Name}
z := template.Must(template.New("validatorFunctionHeader").Parse(validatorFunctionHeader)) z := template.Must(template.New("validatorFunctionHeader").Parse(validatorFunctionHeader))
if z.Execute(buf, tmplV) != nil { if z.Execute(tmpBuf, tmplV) != nil {
log.Fatal("ошибка сборки шаблона:", err) log.Fatal("ошибка сборки шаблона:")
} }
err = iterStructFields(s, buf) ok, err = iterStructFields("", s, tmpBuf)
if err != nil { if err != nil {
log.Fatal("ошибка в ходе перебора полей структуры:", err.Error()) 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) 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 { if err != nil {
log.Fatal("не удалось создать/открыть файл:", err.Error()) log.Fatal("не удалось создать/открыть файл:", err.Error())
} }
@ -77,33 +83,33 @@ func main() {
if err != nil { if err != nil {
log.Fatal("не удалось записать буфер в файл:", err.Error()) 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 { for _, field := range s.Fields.List {
// Рекурсивный вызов, в случае если поле является структурой // Рекурсивный вызов, в случае если поле является структурой
//switch field.Type.(type) { switch field.Type.(type) {
//case *ast.StructType: case *ast.StructType:
// if err:=iterStructFields(field.Type.(*ast.StructType), buf); err!=nil { log.Fatal("Структура не распарсилась в рекурсии:", err.Error()) } if _, err := iterStructFields("."+field.Names[0].Name, field.Type.(*ast.StructType), buf); err != nil {
//} return false, err
}
}
if len(field.Names) == 0 { if len(field.Names) == 0 {
continue continue
} }
// Достаем тэг поля // Достаем тэг поля
if field.Tag == nil {
continue
}
tag, ok := getTagString(field, "validate") tag, ok := getTagString(field, "validate")
if !ok { if !ok {
continue continue
} }
isValidated = true
//Ищем комбинации //Ищем комбинации
for _, comb := range strings.Split(tag, "|") { for _, comb := range strings.Split(tag, "|") {
k := strings.Split(comb, ":") k := strings.Split(comb, ":")
isSlice, fieldType := getFieldType(field) 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{} z := &template.Template{}
switch k[0] { switch k[0] {
@ -127,14 +133,17 @@ func iterStructFields(s *ast.StructType, buf io.Writer) error {
} }
err := z.Execute(buf, tmplV) err := z.Execute(buf, tmplV)
if err != nil { if err != nil {
return err return false, err
} }
} }
} }
return nil return isValidated, nil
} }
func getTagString(f *ast.Field, tag string) (string, bool) { 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]) t := reflect.StructTag(f.Tag.Value[1 : len(f.Tag.Value)-1])
v := t.Get(tag) v := t.Get(tag)
if v == "" { if v == "" {
@ -144,11 +153,12 @@ func getTagString(f *ast.Field, tag string) (string, bool) {
} }
func getFieldType(field *ast.Field) (bool, string) { func getFieldType(field *ast.Field) (bool, string) {
var fieldSlice bool var isSlice bool
var fieldType string var fieldType string
// Эту конструкцию не пропускал линтер gocritic, под предлогом "typeSwitchVar: 2 cases can benefit from type switch with assignment". Я не вижу тут возможности срабатывания обеих веток. тип ast.Expr не может привестись и к *ast.Ident и к *ast.ArrayType одновременно. Пришлось, отключить старикашку критика.
switch field.Type.(type) { switch field.Type.(type) {
case *ast.Ident: case *ast.Ident:
fieldSlice = false isSlice = false
fieldType = field.Type.(*ast.Ident).Name fieldType = field.Type.(*ast.Ident).Name
if field.Type.(*ast.Ident).Obj != nil { if field.Type.(*ast.Ident).Obj != nil {
t, ok := field.Type.(*ast.Ident).Obj.Decl.(*ast.TypeSpec) t, ok := field.Type.(*ast.Ident).Obj.Decl.(*ast.TypeSpec)
@ -162,16 +172,16 @@ func getFieldType(field *ast.Field) (bool, string) {
fieldType = s.Name fieldType = s.Name
} }
case *ast.ArrayType: case *ast.ArrayType:
fieldSlice = true isSlice = true
fieldType = field.Type.(*ast.ArrayType).Elt.(*ast.Ident).Name fieldType = field.Type.(*ast.ArrayType).Elt.(*ast.Ident).Name
} }
switch { switch {
case strings.Contains(fieldType, "int"): case strings.Contains(fieldType, "int"):
return fieldSlice, "int" return isSlice, "int"
case strings.Contains(fieldType, "float"): case strings.Contains(fieldType, "float"):
return fieldSlice, "float" return isSlice, "float"
case fieldType == str: case fieldType == str:
return fieldSlice, str return isSlice, str
} }
return false, "" return false, ""
} }

View File

@ -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)
})
}
}
}

View File

@ -4,6 +4,7 @@ const validatorHeader string = `// Code generated by cool go-validate tool; DO N
package models package models
import ( import (
"fmt"
"log" "log"
"regexp" "regexp"
"strconv" "strconv"
@ -11,8 +12,8 @@ import (
) )
type ValidationError struct { type ValidationError struct {
Name string Field string
Error error Err error
} }
` `
@ -46,7 +47,7 @@ const validatorFunctions string = `
func valLen(s []string, n int) (bool,error) { func valLen(s []string, n int) (bool,error) {
res := true res := true
for _,v :=range s { for _,v :=range s {
if len(v)>n { if len(v)!=n {
res=false res=false
break break
} }

View File

@ -59,9 +59,34 @@ func TestUserValidation(t *testing.T) {
requireOneFieldErr(t, errs, "Age") requireOneFieldErr(t, errs, "Age")
}) })
t.Run("phones slice", func(t *testing.T) { t.Run("fail phones slice", func(t *testing.T) {
// Write me :) badUser := User{
t.Fail() 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) { t.Run("many errors", func(t *testing.T) {

View File

@ -10,8 +10,8 @@ import (
) )
type ValidationError struct { type ValidationError struct {
Name string Field string
Error error Err error
} }
func (u User) Validate() ([]ValidationError, error) { func (u User) Validate() ([]ValidationError, error) {
@ -37,14 +37,6 @@ func (u App) Validate() ([]ValidationError, error) {
return res, err 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) { func (u Response) Validate() ([]ValidationError, error) {
res := []ValidationError{} res := []ValidationError{}
var err error var err error
@ -58,7 +50,7 @@ func (u Response) Validate() ([]ValidationError, error) {
func valLen(s []string, n int) (bool,error) { func valLen(s []string, n int) (bool,error) {
res := true res := true
for _,v :=range s { for _,v :=range s {
if len(v)>n { if len(v)!=n {
res=false res=false
break break
} }