diff --git a/entities.go b/entities.go index b349faf..fd93699 100644 --- a/entities.go +++ b/entities.go @@ -109,7 +109,8 @@ type FieldDef struct { fieldType types.TypeDef } -func NewField(fieldNames types.FieldTagsNames, fieldType types.TypeDef, validateTags []types.ValidatableTag) (*FieldDef, error) { +func NewField(fieldNames types.FieldTagsNames, fieldType types.TypeDef, validateTags types.ValidatableTags) (*FieldDef, error) { + fieldType = toPrimitiveType(fieldType, validateTags) for _, t := range validateTags { if err := fieldType.SetValidateTag(t); err != nil { return nil, fmt.Errorf("set validateTags failed, field %s, tag: %+v, err: %s", @@ -125,3 +126,31 @@ func NewField(fieldNames types.FieldTagsNames, fieldType types.TypeDef, validate fieldType: fieldType, }, nil } + +func toPrimitiveType(fieldType types.TypeDef, validateTags types.ValidatableTags) types.TypeDef { + if validateTags.Empty() { + return fieldType + } + + if len(validateTags) == 1 && + (validateTags.ContainsTag(types.SimpleTag{Name: types.PointerNotNullKey})) || + (validateTags.ContainsTag(types.SimpleTag{Name: types.StructFuncKey})) { + return fieldType + } + + if fieldType.Expr() == nil { + return fieldType + } + + underlyingPrimitive := parsePrimitiveType(fieldType.Expr(), fmt.Sprintf("field type %s", fieldType.Type())) + if underlyingPrimitive != nil { + // case: pointer to custom type with not_null: ( "Name *SimpleStringType `validate:not_null,min_len=1`") + p, isPtr := fieldType.(*types.TypePointer) + if isPtr { + return p.SetInnerType(underlyingPrimitive) + } + return underlyingPrimitive + } + + return fieldType +} diff --git a/examples/aliases/entities.go b/examples/aliases/entities.go new file mode 100644 index 0000000..b723000 --- /dev/null +++ b/examples/aliases/entities.go @@ -0,0 +1,40 @@ +package aliases + +import ( + "errors" + "unicode/utf8" +) + +// StringType validator will be overridden by validation tags in-place +type StringType string + +func (t StringType) Validate() error { + if utf8.RuneCountInString(string(t)) < 3 { + return errors.New("shorter than 3 chars") + } + if utf8.RuneCountInString(string(t)) > 64 { + return errors.New("longer than 64 chars") + } + return nil +} + +func (t StringType) ValidateNotEmpty() error { + if t == "" { + return errors.New("string is empty") + } + return nil +} + +type IntType int +type FloatType float64 +type MapType map[string]int + +type User struct { + FirstName StringType `validate:"min_len=2,max_len=15"` + LastName string `validate:"min_len=1,max_len=15"` + NonEmptyString StringType `validate:"func=.ValidateNotEmpty"` + FamilyMembers IntType `validate:"min=1,max=100"` + SomeFloat FloatType `validate:"min=2.55,max=99.99"` + SomeMap MapType `validate:"min_items=2,key=[max_len=64],value=[min=-35,max=34]"` + SomePointer *StringType `validate:"not_null,min_len=20,max_len=150"` +} diff --git a/examples/aliases/entities_test.go b/examples/aliases/entities_test.go new file mode 100644 index 0000000..5f764a3 --- /dev/null +++ b/examples/aliases/entities_test.go @@ -0,0 +1,79 @@ +package aliases + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_User_Validate(t *testing.T) { + t.Parallel() + + someString := StringType("aaaaaaaaaaaaaaaaaaaab") + validUser := User{ + FirstName: "firstName", + LastName: "lastName", + NonEmptyString: "test", + FamilyMembers: 99, + SomeFloat: 5.55, + SomeMap: map[string]int{ + "test": 1, + "test2": 2, + }, + SomePointer: &someString, + } + + t.Run("valid", func(t *testing.T) { + assert.NoError(t, validUser.Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + + t.Run("first_name: too short, using overridden alias validator", func(t *testing.T) { + r := validUser + r.FirstName = "aa" + + err := r.Validate() + require.NoError(t, err) + }) + + t.Run("first_name: too short, using alias validator", func(t *testing.T) { + r := validUser + r.FirstName = "aa" + + err := r.FirstName.Validate() + require.NotNil(t, err) + assert.Equal(t, `shorter than 3 chars`, err.Error()) + }) + + t.Run("some_pointer: not_null rule", func(t *testing.T) { + r := validUser + r.SomePointer = nil + r.NonEmptyString = "" + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[NonEmptyString: string is empty, SomePointer: cannot be nil]`, err.Error()) + }) + + t.Run("some_pointer: using overridden alias validator", func(t *testing.T) { + r := validUser + someString := StringType("aaa") + r.SomePointer = &someString + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[SomePointer: shorter than 20 chars]`, err.Error()) + }) + + t.Run("non_empty_string: using func validator", func(t *testing.T) { + r := validUser + r.NonEmptyString = "" + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[NonEmptyString: string is empty]`, err.Error()) + }) + }) +} diff --git a/examples/aliases/validators.go b/examples/aliases/validators.go new file mode 100644 index 0000000..aeda6bf --- /dev/null +++ b/examples/aliases/validators.go @@ -0,0 +1,99 @@ +//This file was automatically generated by the genval generator v1.4 +//Please don't modify it manually. Edit your entity tags and then +//run go generate + +package aliases + +import ( + "fmt" + + "github.com/gojuno/genval/errlist" + + "unicode/utf8" +) + +type validatable interface { + Validate() error +} + +func validate(i interface{}) error { + if v, ok := i.(validatable); ok { + return v.Validate() + } + return nil +} + +// Validate validates FloatType +func (r FloatType) Validate() error { + return nil +} + +// Validate validates IntType +func (r IntType) Validate() error { + return nil +} + +// Validate validates MapType +func (r MapType) Validate() error { + var errs errlist.List + return errs.ErrorOrNil() +} + +// Validate validates User +func (r User) Validate() error { + var errs errlist.List + if utf8.RuneCountInString(string(r.FirstName)) < 2 { + errs.AddFieldf("FirstName", "shorter than 2 chars") + } + if utf8.RuneCountInString(string(r.FirstName)) > 15 { + errs.AddFieldf("FirstName", "longer than 15 chars") + } + if utf8.RuneCountInString(string(r.LastName)) < 1 { + errs.AddFieldf("LastName", "shorter than 1 chars") + } + if utf8.RuneCountInString(string(r.LastName)) > 15 { + errs.AddFieldf("LastName", "longer than 15 chars") + } + if err := r.NonEmptyString.ValidateNotEmpty(); err != nil { + errs.AddField("NonEmptyString", err) + } + if r.FamilyMembers < 1 { + errs.AddFieldf("FamilyMembers", "less than 1") + } + if r.FamilyMembers > 100 { + errs.AddFieldf("FamilyMembers", "more than 100") + } + if r.SomeFloat < 2.55 { + errs.AddFieldf("SomeFloat", "less than 2.55") + } + if r.SomeFloat > 99.99 { + errs.AddFieldf("SomeFloat", "more than 99.99") + } + if len(r.SomeMap) < 2 { + errs.AddFieldf("SomeMap", "less items than 2") + } + for SomeMapKey, SomeMapValue := range r.SomeMap { + _ = SomeMapKey + _ = SomeMapValue + if utf8.RuneCountInString(string(SomeMapKey)) > 64 { + errs.AddFieldf(fmt.Sprintf("SomeMap"+".%v", SomeMapKey), "longer than 64 chars") + } + if SomeMapValue < -35 { + errs.AddFieldf(fmt.Sprintf("SomeMap"+".%v", SomeMapKey), "less than -35") + } + if SomeMapValue > 34 { + errs.AddFieldf(fmt.Sprintf("SomeMap"+".%v", SomeMapKey), "more than 34") + } + } + if r.SomePointer == nil { + errs.AddFieldf("SomePointer", "cannot be nil") + } else { + if utf8.RuneCountInString(string(*r.SomePointer)) < 20 { + errs.AddFieldf("SomePointer", "shorter than 20 chars") + } + if utf8.RuneCountInString(string(*r.SomePointer)) > 150 { + errs.AddFieldf("SomePointer", "longer than 150 chars") + } + } + return errs.ErrorOrNil() +} diff --git a/examples/complicated/entities.go b/examples/complicated/entities.go index 0792d00..de48e71 100644 --- a/examples/complicated/entities.go +++ b/examples/complicated/entities.go @@ -68,11 +68,3 @@ func (r AliasOnDogsMapAlias) ValidateAlias() error { } return nil } - -type AliasString string - -type AliasArray []string - -type AliasFunc func() string - -type AliasChan <-chan string diff --git a/examples/complicated/entities_test.go b/examples/complicated/entities_test.go new file mode 100644 index 0000000..bb85191 --- /dev/null +++ b/examples/complicated/entities_test.go @@ -0,0 +1,133 @@ +package complicated + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Request1_Validate(t *testing.T) { + t.Parallel() + + userLastName := "LastN" + childrenCount := 10 + validUser := User{ + Name: "TestUserName", + LastName: &userLastName, + Age: 35, + ChildrenCount: &childrenCount, + Float: 1.11, + Dog: Dog{ + Name: "Dog", + }, + Urls: []string{ + "someURL", + }, + Dogs: []*Dog{ + { + Name: "Dog", + }, + }, + SomeArray: make([]interface{}, 1), + Dict: map[string]int{ + "test": 1, + "test_2": 2, + }, + } + + t.Run("valid", func(t *testing.T) { + assert.NoError(t, validUser.Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + + t.Run("name: too short", func(t *testing.T) { + r := validUser + r.Name = "a" + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Name: shorter than 3 chars]`, err.Error()) + }) + + t.Run("last_name: too short", func(t *testing.T) { + r := validUser + lastName := "" + r.LastName = &lastName + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[LastName: shorter than 1 chars]`, err.Error()) + }) + + t.Run("last_name: empty", func(t *testing.T) { + r := validUser + r.LastName = nil + + err := r.Validate() + require.NoError(t, err) + }) + + t.Run("age: too young", func(t *testing.T) { + r := validUser + r.Age = 1 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: less than 18]`, err.Error()) + }) + + t.Run("children_count: not_null rule", func(t *testing.T) { + r := validUser + r.ChildrenCount = nil + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[ChildrenCount: cannot be nil]`, err.Error()) + }) + + t.Run("children_count: too much", func(t *testing.T) { + r := validUser + childrenCount := 100 + r.ChildrenCount = &childrenCount + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[ChildrenCount: more than 15]`, err.Error()) + }) + + t.Run("dog_pointer: nil is ok", func(t *testing.T) { + r := validUser + r.DogPointer = nil + + err := r.Validate() + require.NoError(t, err) + }) + + t.Run("dog_pointer: custom type validator", func(t *testing.T) { + r := validUser + r.DogPointer = &Dog{} + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[DogPointer.Name: shorter than 1 chars]`, err.Error()) + }) + + t.Run("alias: alias type validator", func(t *testing.T) { + r := validUser + r.Alias = DogsMapAlias(map[string]Dog{}) + + err := r.Validate() + require.NoError(t, err) + }) + + t.Run("alias_on_alias: alias of alias type validator", func(t *testing.T) { + r := validUser + r.AliasOnAlias = AliasOnDogsMapAlias(map[string]Dog{}) + + err := r.Validate() + require.NoError(t, err) + }) + }) +} diff --git a/examples/complicated/validators.go b/examples/complicated/validators.go index ad74283..d463f7e 100644 --- a/examples/complicated/validators.go +++ b/examples/complicated/validators.go @@ -1,4 +1,4 @@ -//This file was automatically generated by the genval generator v1.3 +//This file was automatically generated by the genval generator v1.4 //Please don't modify it manually. Edit your entity tags and then //run go generate @@ -23,22 +23,6 @@ func validate(i interface{}) error { return nil } -// Validate validates AliasArray -func (r AliasArray) Validate() error { - var errs errlist.List - return errs.ErrorOrNil() -} - -// Validate validates AliasChan -func (r AliasChan) Validate() error { - return nil -} - -// Validate validates AliasFunc -func (r AliasFunc) Validate() error { - return nil -} - // Validate validates AliasOnDogsMapAlias func (r AliasOnDogsMapAlias) Validate() error { if err := DogsMapAlias(r).Validate(); err != nil { @@ -47,18 +31,13 @@ func (r AliasOnDogsMapAlias) Validate() error { return nil } -// Validate validates AliasString -func (r AliasString) Validate() error { - return nil -} - // Validate validates Dog func (r Dog) Validate() error { var errs errlist.List - if utf8.RuneCountInString(r.Name) < 1 { + if utf8.RuneCountInString(string(r.Name)) < 1 { errs.AddFieldf("Name", "shorter than 1 chars") } - if utf8.RuneCountInString(r.Name) > 64 { + if utf8.RuneCountInString(string(r.Name)) > 64 { errs.AddFieldf("Name", "longer than 64 chars") } return errs.ErrorOrNil() @@ -93,17 +72,17 @@ func (r Status) Validate() error { // Validate validates User func (r User) Validate() error { var errs errlist.List - if utf8.RuneCountInString(r.Name) < 3 { + if utf8.RuneCountInString(string(r.Name)) < 3 { errs.AddFieldf("Name", "shorter than 3 chars") } - if utf8.RuneCountInString(r.Name) > 64 { + if utf8.RuneCountInString(string(r.Name)) > 64 { errs.AddFieldf("Name", "longer than 64 chars") } if r.LastName != nil { - if utf8.RuneCountInString(*r.LastName) < 1 { + if utf8.RuneCountInString(string(*r.LastName)) < 1 { errs.AddFieldf("LastName", "shorter than 1 chars") } - if utf8.RuneCountInString(*r.LastName) > 5 { + if utf8.RuneCountInString(string(*r.LastName)) > 5 { errs.AddFieldf("LastName", "longer than 5 chars") } } @@ -146,7 +125,7 @@ func (r User) Validate() error { for i, x := range r.Urls { _ = i _ = x - if utf8.RuneCountInString(x) > 256 { + if utf8.RuneCountInString(string(x)) > 256 { errs.AddFieldf(fmt.Sprintf("Urls.%v", i), "longer than 256 chars") } } @@ -193,7 +172,7 @@ func (r User) Validate() error { for DictKey, DictValue := range r.Dict { _ = DictKey _ = DictValue - if utf8.RuneCountInString(DictKey) > 64 { + if utf8.RuneCountInString(string(DictKey)) > 64 { errs.AddFieldf(fmt.Sprintf("Dict"+".%v", DictKey), "longer than 64 chars") } if DictValue < -35 { @@ -231,7 +210,7 @@ func (r User) Validate() error { for MapOfMapValueKey, MapOfMapValueValue := range MapOfMapValue { _ = MapOfMapValueKey _ = MapOfMapValueValue - if utf8.RuneCountInString(MapOfMapValueValue) < 3 { + if utf8.RuneCountInString(string(MapOfMapValueValue)) < 3 { errs.AddFieldf(fmt.Sprintf(fmt.Sprintf("MapOfMap"+".%v", MapOfMapKey)+".%v", MapOfMapValueKey), "shorter than 3 chars") } } diff --git a/examples/empty/validators.go b/examples/empty/validators.go index fdcf8f5..7133804 100644 --- a/examples/empty/validators.go +++ b/examples/empty/validators.go @@ -1,4 +1,4 @@ -//This file was automatically generated by the genval generator v1.3 +//This file was automatically generated by the genval generator v1.4 //Please don't modify it manually. Edit your entity tags and then //run go generate diff --git a/examples/overriding/ex1_full.go b/examples/overriding/ex1_full.go index d68ab77..16eaf52 100644 --- a/examples/overriding/ex1_full.go +++ b/examples/overriding/ex1_full.go @@ -15,16 +15,16 @@ type Age1 struct { func (r Request1) Validate() error { if r.Age.Value < 10 { // 3 was override on 10 - return fmt.Errorf("field Age is less than 10 ") + return fmt.Errorf("field Age is less than 10") } if r.Age.Value > 64 { - return fmt.Errorf("field Age is more than 64 ") + return fmt.Errorf("field Age is more than 64") } if r.Some < 3 { - return fmt.Errorf("field Some is less than 3 ") + return fmt.Errorf("field Some is less than 3") } if r.Some > 64 { - return fmt.Errorf("field Some is more than 64 ") + return fmt.Errorf("field Some is more than 64") } return nil } diff --git a/examples/overriding/ex1_full_test.go b/examples/overriding/ex1_full_test.go new file mode 100644 index 0000000..0745d4d --- /dev/null +++ b/examples/overriding/ex1_full_test.go @@ -0,0 +1,69 @@ +package overriding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Request1_Validate(t *testing.T) { + t.Parallel() + + validRequest := Request1{ + Age: Age1{Value: 10}, + Some: 10, + } + + t.Run("valid", func(t *testing.T) { + assert.NoError(t, validRequest.Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + t.Run("too young, use overridden rule", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `field Age is less than 10`, err.Error()) + }) + + t.Run("too old, using generated validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `field Age is more than 64`, err.Error()) + }) + + t.Run("too old, using generated type validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Age.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Value: more than 64]`, err.Error()) + }) + + t.Run("check min.", func(t *testing.T) { + r := validRequest + r.Some = 1 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `field Some is less than 3`, err.Error()) + }) + + t.Run("check max.", func(t *testing.T) { + r := validRequest + r.Some = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `field Some is more than 64`, err.Error()) + }) + + }) +} diff --git a/examples/overriding/ex2_by_method.go b/examples/overriding/ex2_by_method.go index 5c9760c..8aae9f3 100644 --- a/examples/overriding/ex2_by_method.go +++ b/examples/overriding/ex2_by_method.go @@ -15,10 +15,10 @@ type Age2 struct { func (r Age2) ValidateMin10() error { if r.Value < 10 { // 3 was override on 10 - return fmt.Errorf("field Age is less than 10 ") + return fmt.Errorf("field Age is less than 10") } if r.Value > 64 { - return fmt.Errorf("field Age is more than 64 ") + return fmt.Errorf("field Age is more than 64") } return nil } diff --git a/examples/overriding/ex2_by_method_test.go b/examples/overriding/ex2_by_method_test.go new file mode 100644 index 0000000..9634287 --- /dev/null +++ b/examples/overriding/ex2_by_method_test.go @@ -0,0 +1,86 @@ +package overriding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Request2_Validate(t *testing.T) { + t.Parallel() + + validRequest := Request2{ + Age: Age2{Value: 10}, + Some: 10, + } + + t.Run("valid", func(t *testing.T) { + assert.NoError(t, validRequest.Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + t.Run("too young, using overridden rule", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: field Age is less than 10]`, err.Error()) + }) + + t.Run("too young, using generated type validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Age.Validate() + require.NoError(t, err) + }) + + t.Run("too young, using func validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Age.ValidateMin10() + require.NotNil(t, err) + assert.Equal(t, `field Age is less than 10`, err.Error()) + }) + + t.Run("too old, using func validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Age.ValidateMin10() + require.NotNil(t, err) + assert.Equal(t, `field Age is more than 64`, err.Error()) + }) + + t.Run("too old, using generated validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: field Age is more than 64]`, err.Error()) + }) + + t.Run("check min.", func(t *testing.T) { + r := validRequest + r.Some = 1 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: less than 3]`, err.Error()) + }) + + t.Run("check max.", func(t *testing.T) { + r := validRequest + r.Some = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: more than 64]`, err.Error()) + }) + + }) +} diff --git a/examples/overriding/ex3_by_func.go b/examples/overriding/ex3_by_func.go index c875f6c..6d4a335 100644 --- a/examples/overriding/ex3_by_func.go +++ b/examples/overriding/ex3_by_func.go @@ -15,10 +15,10 @@ type Age3 struct { func validateMin10(r Age3) error { if r.Value < 10 { // 3 was override on 10 - return fmt.Errorf("field Age is less than 10 ") + return fmt.Errorf("field Age is less than 10") } if r.Value > 64 { - return fmt.Errorf("field Age is more than 64 ") + return fmt.Errorf("field Age is more than 64") } return nil } diff --git a/examples/overriding/ex3_by_func_test.go b/examples/overriding/ex3_by_func_test.go new file mode 100644 index 0000000..2daeb55 --- /dev/null +++ b/examples/overriding/ex3_by_func_test.go @@ -0,0 +1,77 @@ +package overriding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Request3_Validate(t *testing.T) { + t.Parallel() + + validRequest := Request3{ + Age: Age3{Value: 10}, + Some: 10, + } + + t.Run("valid", func(t *testing.T) { + assert.NoError(t, validRequest.Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + t.Run("too young, using overridden rule", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: field Age is less than 10]`, err.Error()) + }) + + t.Run("too old, using overridden rule", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: field Age is more than 64]`, err.Error()) + }) + + t.Run("too young, using generated type validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Age.Validate() + require.NoError(t, err) + }) + + t.Run("too old, using generated validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: field Age is more than 64]`, err.Error()) + }) + + t.Run("check min.", func(t *testing.T) { + r := validRequest + r.Some = 1 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: less than 3]`, err.Error()) + }) + + t.Run("check max.", func(t *testing.T) { + r := validRequest + r.Some = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: more than 64]`, err.Error()) + }) + + }) +} diff --git a/examples/overriding/ex4_by_method_and_func.go b/examples/overriding/ex4_by_method_and_func.go index 33d805f..5563630 100644 --- a/examples/overriding/ex4_by_method_and_func.go +++ b/examples/overriding/ex4_by_method_and_func.go @@ -15,14 +15,14 @@ type Age4 struct { func (r Age4) ValidateMin10() error { if r.Value < 10 { // 3 was override on 10 - return fmt.Errorf("field Age is less than 10 ") + return fmt.Errorf("field Age is less than 10") } return nil } func validateMax128(r Age4) error { if r.Value > 128 { // 64 override on 128 - return fmt.Errorf("field Age is more than 64 ") + return fmt.Errorf("field Age is more than 128") } return nil } diff --git a/examples/overriding/ex4_by_method_and_func_test.go b/examples/overriding/ex4_by_method_and_func_test.go new file mode 100644 index 0000000..e0a6483 --- /dev/null +++ b/examples/overriding/ex4_by_method_and_func_test.go @@ -0,0 +1,93 @@ +package overriding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Request4_Validate(t *testing.T) { + t.Parallel() + + validRequest := Request4{ + Age: Age4{Value: 10}, + Some: 10, + } + + t.Run("valid", func(t *testing.T) { + assert.NoError(t, validRequest.Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + t.Run("too young, using overridden rule", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: field Age is less than 10]`, err.Error()) + }) + + t.Run("too young, using func validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Age.ValidateMin10() + require.NotNil(t, err) + assert.Equal(t, `field Age is less than 10`, err.Error()) + }) + + t.Run("too young, using generated type validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 5 + + err := r.Age.Validate() + require.NoError(t, err) + }) + + t.Run("too old, using generated validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Validate() + require.NoError(t, err) + }) + + t.Run("the same value but, using overridden rule", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Validate() + require.NoError(t, err) + }) + + t.Run("too old, using overridden rule", func(t *testing.T) { + r := validRequest + r.Age.Value = 129 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: field Age is more than 128]`, err.Error()) + }) + + t.Run("check min.", func(t *testing.T) { + r := validRequest + r.Some = 1 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: less than 3]`, err.Error()) + }) + + t.Run("check max.", func(t *testing.T) { + r := validRequest + r.Some = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: more than 64]`, err.Error()) + }) + + }) +} diff --git a/examples/overriding/ex5_additional_test.go b/examples/overriding/ex5_additional_test.go new file mode 100644 index 0000000..9dd1224 --- /dev/null +++ b/examples/overriding/ex5_additional_test.go @@ -0,0 +1,88 @@ +package overriding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Request5_Validate(t *testing.T) { + t.Parallel() + + validRequest := Request5{ + Age: Age5{Value: 5}, + Some: 10, + } + + t.Run("valid", func(t *testing.T) { + assert.NoError(t, validRequest.Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + t.Run("definite value, using private validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 10 + r.Some = 10 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[unknown: fields Age and Some can't be 10 at the same time]`, err.Error()) + }) + + t.Run("too young, using generated type validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 2 + + err := r.Age.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Value: less than 3]`, err.Error()) + }) + + t.Run("too old, using generated type validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Age.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Value: more than 64]`, err.Error()) + }) + + t.Run("too young, using generated validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 2 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age.Value: less than 3]`, err.Error()) + }) + + t.Run("too old, using generated validator", func(t *testing.T) { + r := validRequest + r.Age.Value = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age.Value: more than 64]`, err.Error()) + }) + + t.Run("check min.", func(t *testing.T) { + r := validRequest + r.Some = 1 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: less than 3]`, err.Error()) + }) + + t.Run("check max.", func(t *testing.T) { + r := validRequest + r.Some = 65 + + err := r.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Some: more than 64]`, err.Error()) + }) + + }) +} diff --git a/examples/overriding/validators.go b/examples/overriding/validators.go index 6ca56e0..bdb259b 100644 --- a/examples/overriding/validators.go +++ b/examples/overriding/validators.go @@ -1,4 +1,4 @@ -//This file was automatically generated by the genval generator v1.3 +//This file was automatically generated by the genval generator v1.4 //Please don't modify it manually. Edit your entity tags and then //run go generate diff --git a/examples/simple/validators_test.go b/examples/simple/entities_test.go similarity index 68% rename from examples/simple/validators_test.go rename to examples/simple/entities_test.go index 81e3692..4cc9244 100644 --- a/examples/simple/validators_test.go +++ b/examples/simple/entities_test.go @@ -34,6 +34,15 @@ func Test_User_Validate(t *testing.T) { assert.Equal(t, `[Name: shorter than 3 chars]`, err.Error()) }) + t.Run("too long name", func(t *testing.T) { + user := validUser + user.Name = "123456789123456789123456789123456789123456789123456789123456789123456789" + + err := user.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Name: longer than 64 chars]`, err.Error()) + }) + t.Run("nil title", func(t *testing.T) { user := validUser user.Title = nil @@ -62,13 +71,13 @@ func Test_User_Validate(t *testing.T) { assert.Equal(t, "[Emails: less items than 1]", err.Error()) }) - t.Run("bad email", func(t *testing.T) { + t.Run("bad email key and value", func(t *testing.T) { user := validUser - user.Emails = map[int]string{1: "abc"} + user.Emails = map[int]string{1111: "abc"} err := user.Validate() require.NotNil(t, err) - assert.Equal(t, "[Emails.1: shorter than 5 chars]", err.Error()) + assert.Equal(t, "[Emails.1111: more than 3, Emails.1111: shorter than 5 chars]", err.Error()) }) t.Run("too young", func(t *testing.T) { @@ -80,6 +89,15 @@ func Test_User_Validate(t *testing.T) { assert.Equal(t, `[Age: less than 18]`, err.Error()) }) + t.Run("too old", func(t *testing.T) { + user := validUser + user.Age = 111 + + err := user.Validate() + require.NotNil(t, err) + assert.Equal(t, `[Age: more than 95]`, err.Error()) + }) + t.Run("bad dog", func(t *testing.T) { user := validUser user.Dog = Dog{} @@ -105,3 +123,16 @@ func Test_Dog_Validate(t *testing.T) { }) }) } + +func Test_Title_Validate(t *testing.T) { + t.Run("valid", func(t *testing.T) { + assert.NoError(t, Title(None).Validate()) + assert.NoError(t, Title(Doctor).Validate()) + assert.NoError(t, Title(Sir).Validate()) + assert.NoError(t, Title(Father).Validate()) + }) + + t.Run("invalid", func(t *testing.T) { + assert.Error(t, Title("unknown").Validate()) + }) +} diff --git a/examples/simple/validators.go b/examples/simple/validators.go index 76ea9a3..e17a5fa 100644 --- a/examples/simple/validators.go +++ b/examples/simple/validators.go @@ -1,4 +1,4 @@ -//This file was automatically generated by the genval generator v1.3 +//This file was automatically generated by the genval generator v1.4 //Please don't modify it manually. Edit your entity tags and then //run go generate @@ -26,10 +26,10 @@ func validate(i interface{}) error { // Validate validates Dog func (r Dog) Validate() error { var errs errlist.List - if utf8.RuneCountInString(r.Name) < 1 { + if utf8.RuneCountInString(string(r.Name)) < 1 { errs.AddFieldf("Name", "shorter than 1 chars") } - if utf8.RuneCountInString(r.Name) > 64 { + if utf8.RuneCountInString(string(r.Name)) > 64 { errs.AddFieldf("Name", "longer than 64 chars") } return errs.ErrorOrNil() @@ -51,10 +51,10 @@ func (r Title) Validate() error { // Validate validates User func (r User) Validate() error { var errs errlist.List - if utf8.RuneCountInString(r.Name) < 3 { + if utf8.RuneCountInString(string(r.Name)) < 3 { errs.AddFieldf("Name", "shorter than 3 chars") } - if utf8.RuneCountInString(r.Name) > 64 { + if utf8.RuneCountInString(string(r.Name)) > 64 { errs.AddFieldf("Name", "longer than 64 chars") } if r.Age < 18 { @@ -75,7 +75,7 @@ func (r User) Validate() error { if EmailsKey > 3 { errs.AddFieldf(fmt.Sprintf("Emails"+".%v", EmailsKey), "more than 3") } - if utf8.RuneCountInString(EmailsValue) < 5 { + if utf8.RuneCountInString(string(EmailsValue)) < 5 { errs.AddFieldf(fmt.Sprintf("Emails"+".%v", EmailsKey), "shorter than 5 chars") } } diff --git a/inspector.go b/inspector.go index 74847b0..4025d8b 100644 --- a/inspector.go +++ b/inspector.go @@ -185,9 +185,9 @@ func parseFieldType(t ast.Expr, logCtx string) types.TypeDef { if simple != nil { return simple } - return types.NewStruct(v.Name) + return types.NewCustom(v.Name, t) case *ast.SelectorExpr: - return types.NewExternalStruct(v.Sel.Name) + return types.NewExternalCustom(v.Sel.Name, t) case *ast.ArrayType: return types.NewArray(parseFieldType(v.Elt, logCtx)) case *ast.StarExpr: @@ -205,6 +205,25 @@ func parseFieldType(t ast.Expr, logCtx string) types.TypeDef { return nil } +func parsePrimitiveType(e ast.Expr, logCtx string) types.TypeDef { + v, ok := e.(*ast.Ident) + if !ok { + return nil + } + + if v.Obj != nil && v.Obj.Decl != nil { + if typeSpec, ok := v.Obj.Decl.(*ast.TypeSpec); ok { + switch customType := typeSpec.Type.(type) { + case *ast.StructType: + // *ast.StructType isn't supported yet + default: + return parseFieldType(customType, logCtx) + } + } + } + return nil +} + func parseFieldName(fieldNames []*ast.Ident, fieldType types.TypeDef) string { if len(fieldNames) != 0 { return fieldNames[0].Name diff --git a/main.go b/main.go index bcf69e4..06f0d7d 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,6 @@ package main +//go:generate ./genval -d examples/aliases -p aliases //go:generate ./genval -d examples/simple -p simple //go:generate ./genval -d examples/overriding -p overriding //go:generate ./genval -d examples/complicated -p complicated @@ -7,13 +8,14 @@ package main import ( "flag" + "fmt" "log" "os" "strings" ) const ( - version = "1.3" + version = "1.4" ) var ( @@ -21,6 +23,7 @@ var ( outputFile = flag.String("outputFile", "validators.go", "output file name") dir = flag.String("d", "", "directory with files to be validated") pkg = flag.String("p", "", "package with files to be validated") + printVersion = flag.Bool("version", false, "print current version") needValidatableCheck = flag.Bool("needValidatableCheck", true, "check struct on Validatable before calling Validate()") excludeRegexp = flag.String("excludeRegexp", `(client\.go|client_mock\.go)`, "regexp file names that generator should exclude, e.g. `(client\\.go|client_mock\\.go)`") @@ -29,6 +32,11 @@ var ( func main() { flag.Parse() + if *printVersion { + fmt.Fprintf(os.Stdout, "genval version: %s\n", version) + os.Exit(0) + } + // if directory & package aren`t set then first argument is used for both flags d, p := *dir, *pkg if d == "" && p == "" { diff --git a/main_test.go b/main_test.go index 0ac22bc..3d2af0f 100644 --- a/main_test.go +++ b/main_test.go @@ -8,4 +8,5 @@ func Test_Main(m *testing.T) { generate(config{[]string{""}, "examples/simple", "simple", "validators.go", exc}, true) generate(config{[]string{""}, "examples/complicated", "complicated", "validators.go", exc}, true) generate(config{[]string{""}, "examples/overriding", "overriding", "validators.go", exc}, true) + generate(config{[]string{""}, "examples/aliases", "aliases", "validators.go", exc}, true) } diff --git a/types/common.go b/types/common.go index 0f8eab3..6f91a4e 100644 --- a/types/common.go +++ b/types/common.go @@ -3,6 +3,7 @@ package types import ( "errors" "fmt" + "go/ast" "io" "strconv" "strings" @@ -13,6 +14,7 @@ type TypeDef interface { SetValidateTag(ValidatableTag) error Validate() error Generate(w io.Writer, cfg GenConfig, name Name) + Expr() ast.Expr } var ErrUnusedTag = errors.New("unused tag") @@ -65,14 +67,7 @@ func NewName(pointerPrefix, structVar, labelName, fieldName, tagName string) Nam tagName: tagName, } } -func NewSimpleName(labelName, fieldName string) Name { - return Name{ - pointerPrefix: "", - structVar: "", - fieldName: fieldName, - labelName: fmt.Sprintf("%q", labelName), - } -} + func NewIndexedName(labelName, indexVar, validateVar, tagName string, complexLabelName bool) Name { labelNamePrepared := fmt.Sprintf("fmt.Sprintf(\"%s.%%v\", %v)", labelName, indexVar) diff --git a/types/constants.go b/types/constants.go index 96cdf2a..83d8fdf 100644 --- a/types/constants.go +++ b/types/constants.go @@ -2,9 +2,7 @@ package types const ( ValidateTag = "validate" -) -const ( StringMinLenKey string = "min_len" StringMaxLenKey string = "max_len" diff --git a/types/tags.go b/types/tags.go index e343a78..425c46e 100644 --- a/types/tags.go +++ b/types/tags.go @@ -6,10 +6,30 @@ import ( "strings" ) +// ValidatableTag represents a tag that uses for validating type ValidatableTag interface { + // Key returns a tag title Key() string } +// ValidatableTags represents a set of ValidatableTag +type ValidatableTags []ValidatableTag + +// Empty check whether ValidatableTags is empty +func (ts ValidatableTags) Empty() bool { + return len(ts) == 0 +} + +// ContainsTag checks whether ValidatableTags contains a ValidatableTag +func (ts ValidatableTags) ContainsTag(t ValidatableTag) bool { + for _, v := range ts { + if v.Key() == t.Key() { + return true + } + } + return false +} + // contains {"json": "provider_id", "xml": "provider_id"} for // `json:"provider_id,omitempty" "xml":"provider_id" validate:"min_len=1" type FieldTagsNames map[string]string diff --git a/types/type_array.go b/types/type_array.go index 049ba65..5ee4a04 100644 --- a/types/type_array.go +++ b/types/type_array.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "go/ast" "io" ) @@ -87,3 +88,7 @@ func (t typeArray) Validate() error { } return t.innerType.Validate() } + +func (t typeArray) Expr() ast.Expr { + return nil +} diff --git a/types/type_bool.go b/types/type_bool.go index 04d0726..0d6f525 100644 --- a/types/type_bool.go +++ b/types/type_bool.go @@ -1,6 +1,9 @@ package types -import "io" +import ( + "go/ast" + "io" +) const Bool string = "bool" @@ -26,3 +29,7 @@ func (t typeBool) Generate(w io.Writer, cfg GenConfig, name Name) { func (t typeBool) Validate() error { return nil } + +func (t typeBool) Expr() ast.Expr { + return nil +} diff --git a/types/type_byte.go b/types/type_byte.go index 2b13e9e..174519a 100644 --- a/types/type_byte.go +++ b/types/type_byte.go @@ -1,6 +1,9 @@ package types -import "io" +import ( + "go/ast" + "io" +) const Byte string = "byte" @@ -26,3 +29,7 @@ func (t typeByte) Generate(w io.Writer, cfg GenConfig, name Name) { func (t typeByte) Validate() error { return nil } + +func (t typeByte) Expr() ast.Expr { + return nil +} diff --git a/types/type_chan.go b/types/type_chan.go index dcbf8ff..4fb754d 100644 --- a/types/type_chan.go +++ b/types/type_chan.go @@ -1,6 +1,9 @@ package types -import "io" +import ( + "go/ast" + "io" +) func NewChan() *typeChan { return &typeChan{} @@ -24,3 +27,7 @@ func (t typeChan) Generate(w io.Writer, cfg GenConfig, name Name) { func (t typeChan) Validate() error { return nil } + +func (t typeChan) Expr() ast.Expr { + return nil +} diff --git a/types/type_struct.go b/types/type_custom.go similarity index 68% rename from types/type_struct.go rename to types/type_custom.go index f7783b0..e74959c 100644 --- a/types/type_struct.go +++ b/types/type_custom.go @@ -2,28 +2,34 @@ package types import ( "fmt" + "go/ast" "io" "strings" ) -func NewStruct(typeName string) *typeStruct { - return &typeStruct{typeName: typeName, external: false} +func NewCustom(fieldName string, typeExpr ast.Expr) *typeCustom { + return &typeCustom{typeName: fieldName, typeExpr: typeExpr, external: false} } -func NewExternalStruct(typeName string) *typeStruct { - return &typeStruct{typeName: typeName, external: true} +func NewExternalCustom(typeName string, typeExpr ast.Expr) *typeCustom { + return &typeCustom{typeName: typeName, typeExpr: typeExpr, external: true} } -type typeStruct struct { +type typeCustom struct { typeName string + typeExpr ast.Expr external bool funcs []string } -func (t typeStruct) Type() string { +func (t typeCustom) Type() string { return t.typeName } -func (t *typeStruct) SetValidateTag(tag ValidatableTag) error { +func (t typeCustom) Expr() ast.Expr { + return t.typeExpr +} + +func (t *typeCustom) SetValidateTag(tag ValidatableTag) error { switch tag.Key() { case StructFuncKey: for _, v := range parseFuncsParam(tag.(SimpleTag).Param) { @@ -35,7 +41,7 @@ func (t *typeStruct) SetValidateTag(tag ValidatableTag) error { return nil } -func (t typeStruct) Generate(w io.Writer, cfg GenConfig, name Name) { +func (t typeCustom) Generate(w io.Writer, cfg GenConfig, name Name) { registerError := `errs.AddField(%s, err)` if !cfg.SeveralErrors { @@ -67,6 +73,6 @@ func (t typeStruct) Generate(w io.Writer, cfg GenConfig, name Name) { } } -func (t typeStruct) Validate() error { +func (t typeCustom) Validate() error { return nil } diff --git a/types/type_func.go b/types/type_func.go index f706d4f..33ad565 100644 --- a/types/type_func.go +++ b/types/type_func.go @@ -1,6 +1,9 @@ package types -import "io" +import ( + "go/ast" + "io" +) func NewFunc() *typeFunc { return &typeFunc{} @@ -24,3 +27,7 @@ func (t typeFunc) Generate(w io.Writer, cfg GenConfig, name Name) { func (t typeFunc) Validate() error { return nil } + +func (t typeFunc) Expr() ast.Expr { + return nil +} diff --git a/types/type_interface.go b/types/type_interface.go index e233d8b..ffd38c3 100644 --- a/types/type_interface.go +++ b/types/type_interface.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "go/ast" "io" ) @@ -40,3 +41,7 @@ func (t typeInterface) Generate(w io.Writer, cfg GenConfig, name Name) { func (t typeInterface) Validate() error { return nil } + +func (t typeInterface) Expr() ast.Expr { + return nil +} diff --git a/types/type_map.go b/types/type_map.go index eb2c064..851a1cd 100644 --- a/types/type_map.go +++ b/types/type_map.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "go/ast" "io" ) @@ -109,3 +110,7 @@ func genName(baseName string, key bool) string { } return fmt.Sprintf("%s%s", baseName, postfix) } + +func (t typeMap) Expr() ast.Expr { + return nil +} diff --git a/types/type_number.go b/types/type_number.go index fa00c7e..0b12104 100644 --- a/types/type_number.go +++ b/types/type_number.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "go/ast" "io" ) @@ -57,3 +58,7 @@ func (t typeNumber) Validate() error { }, ) } + +func (t typeNumber) Expr() ast.Expr { + return nil +} diff --git a/types/type_pointer.go b/types/type_pointer.go index 0d240dd..897fe2a 100644 --- a/types/type_pointer.go +++ b/types/type_pointer.go @@ -2,26 +2,32 @@ package types import ( "fmt" + "go/ast" "io" ) -func NewPointer(inner TypeDef) *typePointer { - return &typePointer{ +func NewPointer(inner TypeDef) *TypePointer { + return &TypePointer{ nullable: true, innerType: inner, } } -type typePointer struct { +type TypePointer struct { nullable bool innerType TypeDef } -func (t typePointer) Type() string { +func (t *TypePointer) Type() string { return t.innerType.Type() } -func (t *typePointer) SetValidateTag(tag ValidatableTag) error { +func (t *TypePointer) SetInnerType(newType TypeDef) *TypePointer { + t.innerType = newType + return t +} + +func (t *TypePointer) SetValidateTag(tag ValidatableTag) error { switch tag.Key() { case PointerNullableKey: t.nullable = true @@ -33,7 +39,7 @@ func (t *typePointer) SetValidateTag(tag ValidatableTag) error { return nil } -func (t typePointer) Generate(w io.Writer, cfg GenConfig, name Name) { +func (t *TypePointer) Generate(w io.Writer, cfg GenConfig, name Name) { if t.nullable { fmt.Fprintf(w, "if %s != nil {\n", name.Full()) t.innerType.Generate(w, cfg, name.WithPointer()) @@ -47,6 +53,10 @@ func (t typePointer) Generate(w io.Writer, cfg GenConfig, name Name) { } } -func (t typePointer) Validate() error { +func (t *TypePointer) Validate() error { return t.innerType.Validate() } + +func (t *TypePointer) Expr() ast.Expr { + return t.innerType.Expr() +} diff --git a/types/type_string.go b/types/type_string.go index d7f94ff..fcbf389 100644 --- a/types/type_string.go +++ b/types/type_string.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "go/ast" "io" ) @@ -38,14 +39,14 @@ func (t typeString) Generate(w io.Writer, cfg GenConfig, name Name) { if t.minLen != nil { if *t.minLen != "0" { cfg.AddImport("unicode/utf8") - fmt.Fprintf(w, "if utf8.RuneCountInString(%s) < %s {\n", name.Full(), *t.minLen) + fmt.Fprintf(w, "if utf8.RuneCountInString(%s(%s)) < %s {\n", String, name.Full(), *t.minLen) fmt.Fprintf(w, " errs.AddFieldf(%s, \"shorter than %s chars\")\n", name.LabelName(), *t.minLen) fmt.Fprintf(w, "}\n") } } if t.maxLen != nil { cfg.AddImport("unicode/utf8") - fmt.Fprintf(w, "if utf8.RuneCountInString(%s) > %s {\n", name.Full(), *t.maxLen) + fmt.Fprintf(w, "if utf8.RuneCountInString(%s(%s)) > %s {\n", String, name.Full(), *t.maxLen) fmt.Fprintf(w, " errs.AddFieldf(%s ,\"longer than %s chars\")\n", name.LabelName(), *t.maxLen) fmt.Fprintf(w, "}\n") } @@ -69,3 +70,7 @@ func (t typeString) Validate() error { }, ) } + +func (t typeString) Expr() ast.Expr { + return nil +}