Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ For example, if you had a Go backend that used the [Gin](https://www.github.com/
*The key features of Astra are:*
* Extract types from web services
* Read types from files
* Extracts [Go Validator V10](https://github.com/go-playground/validator) struct level validation
* Follow through different functions to extract types
* Extract types from any packages from the dependency tree
* Adapt to different coding styles
Expand Down
5 changes: 5 additions & 0 deletions astTraversal/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ type Result struct {
// StructFields is a map of struct fields (e.g. for a struct { Foo string })
StructFields map[string]Result

// StructFieldBindingTags is a map of struct field binding tags (e.g. for a struct { Foo string `binding:"foo"` })
StructFieldBindingTags BindingTagMap

// StructFieldValidationTags is a map of struct field validation tags (e.g. for a struct { Foo string `validate:"unique"` })
StructFieldValidationTags ValidationTagMap

// StructFieldValidationRequired is a map of struct field validation required tags (e.g. for a struct { Foo string `validate:"required"` })
StructFieldValidationRequired ValidationRequiredMap
Comment thread
TommoLeedsy marked this conversation as resolved.

// Doc is the documentation of the result
Doc string
}
31 changes: 23 additions & 8 deletions astTraversal/tags.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package astTraversal

import (
"go/types"
"reflect"
"regexp"
"strings"

"github.com/ls6-events/validjsonator"
)

type BindingTagType string
Expand Down Expand Up @@ -37,13 +41,11 @@ const (

var ValidationTags = []ValidationTagType{GinValidationTag, ValidatorValidationTag}

type ValidationTag struct {
IsRequired bool `json:"is_required,omitempty" yaml:"is_required,omitempty"`
}
type ValidationTagMap map[ValidationTagType]validjsonator.Schema

type ValidationTagMap map[ValidationTagType]ValidationTag
type ValidationRequiredMap map[ValidationTagType]bool

func ParseStructTag(field string, tag string) (BindingTagMap, ValidationTagMap) {
func ParseStructTag(field string, node types.Type, tag string) (BindingTagMap, ValidationTagMap, ValidationRequiredMap) {
bindingTags := make(BindingTagMap)
for _, bindingTag := range BindingTags {
tagValue, tagOk := reflect.StructTag(tag).Lookup(string(bindingTag))
Expand Down Expand Up @@ -75,16 +77,29 @@ func ParseStructTag(field string, tag string) (BindingTagMap, ValidationTagMap)
}

validationTags := make(ValidationTagMap)
validationRequired := make(ValidationRequiredMap)
for _, validationTag := range ValidationTags {
tagValue := reflect.StructTag(tag).Get(string(validationTag))
if tagValue == "" {
continue
}

validationTags[validationTag] = ValidationTag{
IsRequired: strings.Contains(tagValue, "required"),
splitValues := regexp.MustCompile(`\s*,?\s*dive\s*,?\s*`).Split(tagValue, 2)
baseSchema, required := validjsonator.ValidationTagsToSchema(splitValues[0])

if len(splitValues) == 2 {
diveSchema, _ := validjsonator.ValidationTagsToSchema(splitValues[1])

switch node.(type) {
case *types.Slice:
baseSchema.Items = &diveSchema
case *types.Map:
baseSchema.AdditionalProperties = &diveSchema
}
}

validationTags[validationTag], validationRequired[validationTag] = baseSchema, required
}

return bindingTags, validationTags
return bindingTags, validationTags, validationRequired
}
43 changes: 12 additions & 31 deletions astTraversal/tags_test.go
Original file line number Diff line number Diff line change
@@ -1,118 +1,99 @@
package astTraversal

import (
"go/types"
"reflect"
"testing"
)

func TestParseStructTag(t *testing.T) {
testCases := []struct {
field string
tag string
expectedBindingTags BindingTagMap
expectedValidationTags ValidationTagMap
field string
tag string
node types.Type
expectedBindingTags BindingTagMap
}{
{
field: "Field1",
tag: `json:"field1"`,
node: &types.Basic{},
expectedBindingTags: BindingTagMap{
JSONBindingTag: {
Name: "field1",
NotShown: false,
ReturnOptional: false,
},
},
expectedValidationTags: ValidationTagMap{},
},
{
field: "Field2",
tag: `json:"field2,omitempty"`,
node: &types.Basic{},
expectedBindingTags: BindingTagMap{
JSONBindingTag: {
Name: "field2",
NotShown: false,
ReturnOptional: true,
},
},
expectedValidationTags: ValidationTagMap{},
},
{
field: "Field3",
tag: `json:""`,
node: &types.Basic{},
expectedBindingTags: BindingTagMap{
JSONBindingTag: {
Name: "Field3",
NotShown: false,
ReturnOptional: false,
},
},
expectedValidationTags: ValidationTagMap{},
},
{
field: "Field4",
tag: `json:",omitempty"`,
node: &types.Basic{},
expectedBindingTags: BindingTagMap{
JSONBindingTag: {
Name: "Field4",
NotShown: false,
ReturnOptional: true,
},
},
expectedValidationTags: ValidationTagMap{},
},
{
field: "Field5",
tag: `json:"-"`,
node: &types.Basic{},
expectedBindingTags: BindingTagMap{
JSONBindingTag: {
Name: "",
NotShown: true,
ReturnOptional: false,
},
},
expectedValidationTags: ValidationTagMap{},
},
{
field: "Field6",
tag: `validate:"required"`,
expectedBindingTags: BindingTagMap{
NoBindingTag: {
Name: "Field6",
NotShown: false,
ReturnOptional: false,
},
},
expectedValidationTags: ValidationTagMap{
ValidatorValidationTag: {
IsRequired: true,
},
},
},
{
field: "Field7",
tag: ``,
node: &types.Basic{},
expectedBindingTags: BindingTagMap{
NoBindingTag: {
Name: "Field7",
NotShown: false,
ReturnOptional: false,
},
},
expectedValidationTags: ValidationTagMap{},
},
}

for _, testCase := range testCases {
t.Run("field="+testCase.field, func(t *testing.T) {
bindingTags, validationTags := ParseStructTag(testCase.field, testCase.tag)
bindingTags, _, _ := ParseStructTag(testCase.field, testCase.node, testCase.tag)

if !reflect.DeepEqual(bindingTags, testCase.expectedBindingTags) {
t.Errorf("Expected BindingTags: %v, but got: %v", testCase.expectedBindingTags, bindingTags)
}

if !reflect.DeepEqual(validationTags, testCase.expectedValidationTags) {
t.Errorf("Expected ValidationTags: %v, but got: %v", testCase.expectedValidationTags, validationTags)
}
})
}
}
26 changes: 15 additions & 11 deletions astTraversal/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,20 +204,23 @@ func (t *TypeTraverser) Result() (Result, error) {
case *types.Struct:
fields := make(map[string]Result)
for i := 0; i < n.NumFields(); i++ {
f := n.Field(i)
name := f.Id()
isExported := f.Exported()
isEmbedded := f.Embedded()
tag := n.Tag(i)
field := n.Field(i)
name := field.Id()
node := field.Type()
isExported := field.Exported()
isEmbedded := field.Embedded()

if !isExported {
continue
}

var bindingTag BindingTagMap
var validationTags ValidationTagMap
if isExported {
bindingTag, validationTags = ParseStructTag(name, n.Tag(i))
} else {
continue
}
var validationRequired ValidationRequiredMap
bindingTag, validationTags, validationRequired = ParseStructTag(name, node, tag)

structFieldResult, err := t.Traverser.Type(f.Type(), t.Package).Result()
structFieldResult, err := t.Traverser.Type(node, t.Package).Result()
if err != nil {
return Result{}, err
}
Expand All @@ -228,7 +231,7 @@ func (t *TypeTraverser) Result() (Result, error) {
return Result{}, err
}

pos := f.Pos()
pos := field.Pos()

node, err := structFieldResult.Package.ASTAtPos(pos)
if err == nil && node != nil {
Expand All @@ -242,6 +245,7 @@ func (t *TypeTraverser) Result() (Result, error) {
structFieldResult.IsEmbedded = isEmbedded
structFieldResult.StructFieldBindingTags = bindingTag
structFieldResult.StructFieldValidationTags = validationTags
structFieldResult.StructFieldValidationRequired = validationRequired

fields[name] = structFieldResult
}
Expand Down
2 changes: 1 addition & 1 deletion astTraversal/type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestTypeTraverser_Result(t *testing.T) {
ageField, ok := res.StructFields["Age"]
assert.True(t, ok)
assert.Equal(t, "int", ageField.Type)
assert.True(t, ageField.StructFieldValidationTags[GinValidationTag].IsRequired)
assert.True(t, ageField.StructFieldValidationRequired[GinValidationTag])
})
}

Expand Down
4 changes: 3 additions & 1 deletion cli/astra/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
)

require (
dario.cat/mergo v1.0.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
Expand All @@ -26,13 +27,14 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/ls6-events/validjsonator v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
Expand Down
8 changes: 6 additions & 2 deletions cli/astra/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
Expand Down Expand Up @@ -45,6 +47,8 @@ github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/ls6-events/validjsonator v1.1.0 h1:sKRPoULrVnI5AP0HeDDOEQxGmtyI+4yANrScrM/PpW0=
github.com/ls6-events/validjsonator v1.1.0/go.mod h1:GjTuaM3Y/bymNDAJLkziXHbApT/xtoxixQWgggdNxsY=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand All @@ -63,8 +67,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
Expand Down
4 changes: 3 additions & 1 deletion examples/basic/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
)

require (
dario.cat/mergo v1.0.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
Expand All @@ -24,13 +25,14 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/ls6-events/validjsonator v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
Expand Down
8 changes: 6 additions & 2 deletions examples/basic/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
Expand Down Expand Up @@ -42,6 +44,8 @@ github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/ls6-events/validjsonator v1.1.0 h1:sKRPoULrVnI5AP0HeDDOEQxGmtyI+4yANrScrM/PpW0=
github.com/ls6-events/validjsonator v1.1.0/go.mod h1:GjTuaM3Y/bymNDAJLkziXHbApT/xtoxixQWgggdNxsY=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand All @@ -60,8 +64,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down
Loading