Merge pull request #10 from tiburon-777/hw09_generator_of_validators
Hw09 generator of validatorspull/11/head
commit
04b5e79d0f
|
@ -15,4 +15,5 @@ linters:
|
||||||
- wsl
|
- wsl
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- gosec
|
- gosec
|
||||||
- nlreturn
|
- nlreturn
|
||||||
|
- gocritic
|
|
@ -1,5 +1,187 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func main() {
|
import (
|
||||||
// Place your code here
|
"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
|
||||||
|
}
|
||||||
|
// Если все проверки пройдены, это структура
|
||||||
|
// Итерируем ее поля
|
||||||
|
tmpBuf := new(bytes.Buffer)
|
||||||
|
tmplV := TemplateVars{StructName: t.Name.Name}
|
||||||
|
z := template.Must(template.New("validatorFunctionHeader").Parse(validatorFunctionHeader))
|
||||||
|
if z.Execute(tmpBuf, tmplV) != nil {
|
||||||
|
log.Fatal("ошибка сборки шаблона:")
|
||||||
|
}
|
||||||
|
ok, err = iterStructFields("", s, tmpBuf)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("ошибка в ходе перебора полей структуры:", err.Error())
|
||||||
|
}
|
||||||
|
tmpBuf.Write([]byte(validatorFunctionFooter))
|
||||||
|
if ok {
|
||||||
|
if _, err := tmpBuf.WriteTo(buf); err != nil {
|
||||||
|
log.Fatal("ошибка перекладывания в буфер:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString(validatorFunctions)
|
||||||
|
f, err := os.Create(strings.Split(os.Args[1], ".")[0] + "_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(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.Names[0].Name+".", field.Type.(*ast.StructType), buf); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(field.Names) == 0 {
|
||||||
|
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: name + 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 false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFieldType(field *ast.Field) (bool, string) {
|
||||||
|
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:
|
||||||
|
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)
|
||||||
|
if !ok {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
s, ok := t.Type.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
fieldType = s.Name
|
||||||
|
}
|
||||||
|
case *ast.ArrayType:
|
||||||
|
isSlice = true
|
||||||
|
fieldType = field.Type.(*ast.ArrayType).Elt.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case strings.Contains(fieldType, "int"):
|
||||||
|
return isSlice, "int"
|
||||||
|
case strings.Contains(fieldType, "float"):
|
||||||
|
return isSlice, "float"
|
||||||
|
case fieldType == str:
|
||||||
|
return isSlice, str
|
||||||
|
}
|
||||||
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
const validatorHeader string = `// Code generated by cool go-validate tool; DO NOT EDIT.
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ValidationError struct {
|
||||||
|
Field string
|
||||||
|
Err 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 v<n {
|
||||||
|
res=false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res,nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valMax(i []int, n int) (bool,error) {
|
||||||
|
res := true
|
||||||
|
for _,v :=range i {
|
||||||
|
if v>n {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
`
|
|
@ -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
|
go 1.14
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
|
//go:generate go-validate models.go
|
||||||
type UserRole string
|
type UserRole string
|
||||||
|
|
||||||
// NOTE: Several struct specs in one type declaration are allowed
|
// NOTE: Several struct specs in one type declaration are allowed.
|
||||||
type (
|
type (
|
||||||
User struct {
|
User struct {
|
||||||
ID string `json:"id" validate:"len:36"`
|
ID string `json:"id" validate:"len:36"`
|
||||||
Name string
|
Name string
|
||||||
Age int `validate:"min:18|max:50"`
|
Age int `validate:"min:18|max:50"`
|
||||||
Email string `validate:"regexp:^\\w+@\\w+\\.\\w+$"`
|
Email string `validate:"regexp:^\\w+@\\w+\\.\\w+$"`
|
||||||
Role UserRole `validate:"in:admin,stuff"`
|
Role UserRole `validate:"in:admin,stuff"`
|
||||||
Phones []string `validate:"len:11"`
|
Phones []string `validate:"len:11"`
|
||||||
|
Response struct {
|
||||||
|
Code int `validate:"in:200,404,500"`
|
||||||
|
Body string `json:"omitempty"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
App struct {
|
App struct {
|
||||||
|
|
|
@ -17,11 +17,12 @@ func TestUserValidation(t *testing.T) {
|
||||||
requireValidation(t, User{})
|
requireValidation(t, User{})
|
||||||
|
|
||||||
goodUser := User{
|
goodUser := User{
|
||||||
ID: "0a44d582-9749-11ea-a056-9ff7f30f0608",
|
ID: "0a44d582-9749-11ea-a056-9ff7f30f0608",
|
||||||
Name: "John",
|
Name: "John",
|
||||||
Age: 24,
|
Age: 24,
|
||||||
Email: "john@abrams.com",
|
Email: "john@abrams.com",
|
||||||
Role: "admin",
|
Role: "admin",
|
||||||
|
Response: Response{Code: 200},
|
||||||
}
|
}
|
||||||
requireNoValidationErrors(t, goodUser)
|
requireNoValidationErrors(t, goodUser)
|
||||||
|
|
||||||
|
@ -59,9 +60,49 @@ 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"},
|
||||||
|
Response: Response{Code: 404},
|
||||||
|
}
|
||||||
|
|
||||||
|
errs, err := badUser.Validate()
|
||||||
|
require.Nil(t, err)
|
||||||
|
requireOneFieldErr(t, errs, "Phones")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pass phones slice", 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},
|
||||||
|
}
|
||||||
|
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 := goodUser.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) {
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Code generated by cool go-validate tool; DO NOT EDIT.
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ValidationError struct {
|
||||||
|
Field string
|
||||||
|
Err 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")})}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 v<n {
|
||||||
|
res=false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res,nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valMax(i []int, n int) (bool,error) {
|
||||||
|
res := true
|
||||||
|
for _,v :=range i {
|
||||||
|
if v>n {
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue