From 10030b02b48c59c73e6c4fd07e3256b61aff339f Mon Sep 17 00:00:00 2001 From: zdon0 <100082302+zdon0@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:26:35 +0300 Subject: [PATCH] fix parse nested structs and aliases (#1866) Co-authored-by: ma.mikhaylov --- generics.go | 20 +++++++++-- packages.go | 24 ++++++++++---- parser.go | 8 ++++- parser_test.go | 19 +++++++++++ testdata/alias_nested/cmd/main/main.go | 9 +++++ testdata/alias_nested/expected.json | 38 +++++++++++++++++++++ testdata/alias_nested/pkg/bad/data.go | 5 +++ testdata/alias_nested/pkg/good/data.go | 9 +++++ types.go | 46 ++++++++++++++++++-------- 9 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 testdata/alias_nested/cmd/main/main.go create mode 100644 testdata/alias_nested/expected.json create mode 100644 testdata/alias_nested/pkg/bad/data.go create mode 100644 testdata/alias_nested/pkg/good/data.go diff --git a/generics.go b/generics.go index 07344bbaf..80e93a90b 100644 --- a/generics.go +++ b/generics.go @@ -61,6 +61,7 @@ func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string, Enums: typeSpecDef.Enums, PkgPath: typeSpecDef.PkgPath, ParentSpec: typeSpecDef.ParentSpec, + SchemaName: "array_" + typeSpecDef.SchemaName, NotUnique: false, } } @@ -96,9 +97,9 @@ func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string, Enums: typeSpecDef.Enums, PkgPath: typeSpecDef.PkgPath, ParentSpec: typeSpecDef.ParentSpec, + SchemaName: "map_" + parts[0] + "_" + typeSpecDef.SchemaName, NotUnique: false, } - } if IsGolangPrimitiveType(genericParam) { return &TypeSpecDef{ @@ -106,6 +107,7 @@ func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string, Name: ast.NewIdent(genericParam), Type: ast.NewIdent(genericParam), }, + SchemaName: genericParam, } } return pkgDefs.FindTypeSpec(genericParam, file) @@ -155,14 +157,27 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi } name = fmt.Sprintf("%s%s-", string(IgnoreNameOverridePrefix), original.TypeName()) + schemaName := fmt.Sprintf("%s-", original.SchemaName) + var nameParts []string + var schemaNameParts []string + for _, def := range formals { if specDef, ok := genericParamTypeDefs[def.Name]; ok { - nameParts = append(nameParts, specDef.TypeName()) + nameParts = append(nameParts, specDef.Name) + + schemaNamePart := specDef.Name + + if specDef.TypeSpec != nil { + schemaNamePart = specDef.TypeSpec.SchemaName + } + + schemaNameParts = append(schemaNameParts, schemaNamePart) } } name += normalizeGenericTypeName(strings.Join(nameParts, "-")) + schemaName += normalizeGenericTypeName(strings.Join(schemaNameParts, "-")) if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok { return typeSpec @@ -180,6 +195,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi Doc: original.TypeSpec.Doc, Assign: original.TypeSpec.Assign, }, + SchemaName: schemaName, } pkgDefs.uniqueDefinitions[name] = parametrizedTypeSpec diff --git a/packages.go b/packages.go index d0640334e..479b26396 100644 --- a/packages.go +++ b/packages.go @@ -166,6 +166,8 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag pkgDefs.uniqueDefinitions[fullName] = nil anotherTypeDef.NotUnique = true pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef + anotherTypeDef.SetSchemaName() + typeSpecDef.NotUnique = true fullName = typeSpecDef.TypeName() pkgDefs.uniqueDefinitions[fullName] = typeSpecDef @@ -174,6 +176,8 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag pkgDefs.uniqueDefinitions[fullName] = typeSpecDef } + typeSpecDef.SetSchemaName() + if pkgDefs.packages[typeSpecDef.PkgPath] == nil { pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(typeSpecDef.Name(), typeSpecDef) } else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()]; !ok { @@ -579,17 +583,23 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File return typeDef } - //in case that comment //@name renamed the type with a name without a dot - typeDef, ok = pkgDefs.uniqueDefinitions[typeName] - if ok { - return typeDef - } - name := parts[0] typeDef, ok = pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, name)] if !ok { pkgPaths, externalPkgPaths := pkgDefs.findPackagePathFromImports("", file) typeDef = pkgDefs.findTypeSpecFromPackagePaths(pkgPaths, externalPkgPaths, name) } - return pkgDefs.parametrizeGenericType(file, typeDef, typeName) + + if typeDef != nil { + return pkgDefs.parametrizeGenericType(file, typeDef, typeName) + } + + //in case that comment //@name renamed the type with a name without a dot + for _, v := range pkgDefs.uniqueDefinitions { + if v.SchemaName == typeName { + return v + } + } + + return nil } diff --git a/parser.go b/parser.go index 2c694be30..b99405769 100644 --- a/parser.go +++ b/parser.go @@ -1316,8 +1316,14 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) } } + schemaName := typeName + + if typeSpecDef.SchemaName != "" { + schemaName = typeSpecDef.SchemaName + } + sch := Schema{ - Name: typeName, + Name: schemaName, PkgPath: typeSpecDef.PkgPath, Schema: definition, } diff --git a/parser_test.go b/parser_test.go index a8bcd46e4..6b64aec1c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -4367,3 +4367,22 @@ func Test(){ assert.True(t, ok) assert.NotNil(t, val2.Get) } + +func TestParser_EmbeddedStructAsOtherAliasGoListNested(t *testing.T) { + t.Parallel() + + p := New(SetParseDependency(1), ParseUsingGoList(true)) + + p.parseGoList = true + + searchDir := "testdata/alias_nested" + expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + + err = p.ParseAPI(searchDir, "cmd/main/main.go", 0) + assert.NoError(t, err) + + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, string(expected), string(b)) +} diff --git a/testdata/alias_nested/cmd/main/main.go b/testdata/alias_nested/cmd/main/main.go new file mode 100644 index 000000000..c80a0b9f1 --- /dev/null +++ b/testdata/alias_nested/cmd/main/main.go @@ -0,0 +1,9 @@ +package main + +import "github.com/swaggo/swag/testdata/alias_nested/pkg/good" + +// @Success 200 {object} good.Gen +// @Router /api [get]. +func main() { + var _ good.Gen +} diff --git a/testdata/alias_nested/expected.json b/testdata/alias_nested/expected.json new file mode 100644 index 000000000..f6542ca41 --- /dev/null +++ b/testdata/alias_nested/expected.json @@ -0,0 +1,38 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/api": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Gen" + } + } + } + } + } + }, + "definitions": { + "Gen": { + "type": "object", + "properties": { + "emb": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_alias_nested_pkg_good.Emb" + } + } + }, + "github_com_swaggo_swag_testdata_alias_nested_pkg_good.Emb": { + "type": "object", + "properties": { + "good": { + "type": "boolean" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/alias_nested/pkg/bad/data.go b/testdata/alias_nested/pkg/bad/data.go new file mode 100644 index 000000000..795993d15 --- /dev/null +++ b/testdata/alias_nested/pkg/bad/data.go @@ -0,0 +1,5 @@ +package bad + +type Emb struct { + Bad bool `json:"bad"` +} // @name Emb diff --git a/testdata/alias_nested/pkg/good/data.go b/testdata/alias_nested/pkg/good/data.go new file mode 100644 index 000000000..e340b7d38 --- /dev/null +++ b/testdata/alias_nested/pkg/good/data.go @@ -0,0 +1,9 @@ +package good + +type Gen struct { + Emb Emb `json:"emb"` +} // @name Gen + +type Emb struct { + Good bool `json:"good"` +} diff --git a/types.go b/types.go index 0076a6b40..5f3031e0b 100644 --- a/types.go +++ b/types.go @@ -30,6 +30,8 @@ type TypeSpecDef struct { PkgPath string ParentSpec ast.Decl + SchemaName string + NotUnique bool } @@ -46,20 +48,6 @@ func (t *TypeSpecDef) Name() string { func (t *TypeSpecDef) TypeName() string { if ignoreNameOverride(t.TypeSpec.Name.Name) { return t.TypeSpec.Name.Name[1:] - } else if t.TypeSpec.Comment != nil { - // get alias from comment '// @name ' - const regexCaseInsensitive = "(?i)" - reTypeName, err := regexp.Compile(regexCaseInsensitive + `^@name\s+(\S+)`) - if err != nil { - panic(err) - } - for _, comment := range t.TypeSpec.Comment.List { - trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/")) - texts := reTypeName.FindStringSubmatch(trimmedComment) - if len(texts) > 1 { - return texts[1] - } - } } var names []string @@ -86,6 +74,36 @@ func (t *TypeSpecDef) FullPath() string { return t.PkgPath + "." + t.Name() } +const regexCaseInsensitive = "(?i)" + +var reTypeName = regexp.MustCompile(regexCaseInsensitive + `^@name\s+(\S+)`) + +func (t *TypeSpecDef) Alias() string { + if t.TypeSpec.Comment == nil { + return "" + } + + // get alias from comment '// @name ' + for _, comment := range t.TypeSpec.Comment.List { + trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/")) + texts := reTypeName.FindStringSubmatch(trimmedComment) + if len(texts) > 1 { + return texts[1] + } + } + + return "" +} + +func (t *TypeSpecDef) SetSchemaName() { + if alias := t.Alias(); alias != "" { + t.SchemaName = alias + return + } + + t.SchemaName = t.TypeName() +} + // AstFileInfo information of an ast.File. type AstFileInfo struct { //FileSet the FileSet object which is used to parse this go source file