diff --git a/generics.go b/generics.go index 66c84b85c..e04bc89ea 100644 --- a/generics.go +++ b/generics.go @@ -6,15 +6,12 @@ package swag import ( "errors" "fmt" - "github.com/go-openapi/spec" "go/ast" "strings" - "sync" "unicode" -) -var genericDefinitionsMutex = &sync.RWMutex{} -var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{} + "github.com/go-openapi/spec" +) type genericTypeSpec struct { ArrayDepth int @@ -22,53 +19,19 @@ type genericTypeSpec struct { Name string } -func (s *genericTypeSpec) Type() ast.Expr { - if s.TypeSpec != nil { - return &ast.SelectorExpr{ - X: &ast.Ident{Name: ""}, - Sel: &ast.Ident{Name: s.Name}, - } +func (t *genericTypeSpec) TypeName() string { + if t.TypeSpec != nil { + return t.TypeSpec.TypeName() } - - return &ast.Ident{Name: s.Name} -} - -func (s *genericTypeSpec) TypeDocName() string { - if s.TypeSpec != nil { - return strings.Replace(TypeDocName(s.TypeSpec.FullName(), s.TypeSpec.TypeSpec), "-", "_", -1) - } - - return s.Name -} - -func typeSpecFullName(typeSpecDef *TypeSpecDef) string { - fullName := typeSpecDef.FullName() - - if typeSpecDef.TypeSpec.TypeParams != nil { - fullName = fullName + "[" - for i, typeParam := range typeSpecDef.TypeSpec.TypeParams.List { - if i > 0 { - fullName = fullName + "-" - } - - fullName = fullName + typeParam.Names[0].Name - } - fullName = fullName + "]" - } - - return fullName + return t.Name } func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { - genericDefinitionsMutex.RLock() - tSpec, ok := genericsDefinitions[original][fullGenericForm] - genericDefinitionsMutex.RUnlock() - if ok { - return tSpec + if original == nil || original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 { + return original } - pkgName := strings.Split(fullGenericForm, ".")[0] - genericTypeName, genericParams := splitStructName(fullGenericForm) + name, genericParams := splitGenericsTypeName(fullGenericForm) if genericParams == nil { return nil } @@ -88,76 +51,62 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi arrayDepth++ } - tdef := pkgDefs.FindTypeSpec(genericParam, file, parseDependency) - if tdef != nil && !strings.Contains(genericParam, ".") { - genericParam = fullTypeName(file.Name.Name, genericParam) + typeDef := pkgDefs.FindTypeSpec(genericParam, file, parseDependency) + if typeDef != nil { + genericParam = typeDef.TypeName() + if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok { + pkgDefs.uniqueDefinitions[genericParam] = typeDef + } } genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ ArrayDepth: arrayDepth, - TypeSpec: tdef, + TypeSpec: typeDef, Name: genericParam, } } - parametrizedTypeSpec := &TypeSpecDef{ - File: original.File, - PkgPath: original.PkgPath, - TypeSpec: &ast.TypeSpec{ - Doc: original.TypeSpec.Doc, - Comment: original.TypeSpec.Comment, - Assign: original.TypeSpec.Assign, - }, - } - - ident := &ast.Ident{ - NamePos: original.TypeSpec.Name.NamePos, - Obj: original.TypeSpec.Name.Obj, - } - - if strings.Contains(genericTypeName, ".") { - genericTypeName = strings.Split(genericTypeName, ".")[1] - } - - var typeName = []string{TypeDocName(fullTypeName(pkgName, genericTypeName), parametrizedTypeSpec.TypeSpec)} - + name = fmt.Sprintf("%s%s-", string(IgnoreNameOverridePrefix), original.TypeName()) + var nameParts []string for _, def := range original.TypeSpec.TypeParams.List { if specDef, ok := genericParamTypeDefs[def.Names[0].Name]; ok { var prefix = "" - if specDef.ArrayDepth > 0 { + if specDef.ArrayDepth == 1 { prefix = "array_" - if specDef.ArrayDepth > 1 { - prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth) - } + } else if specDef.ArrayDepth > 1 { + prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth) } - typeName = append(typeName, prefix+specDef.TypeDocName()) + nameParts = append(nameParts, prefix+specDef.TypeName()) } } - ident.Name = strings.Join(typeName, "-") - ident.Name = strings.Replace(ident.Name, ".", "_", -1) - pkgNamePrefix := pkgName + "_" - if strings.HasPrefix(ident.Name, pkgNamePrefix) { - ident.Name = fullTypeName(pkgName, ident.Name[len(pkgNamePrefix):]) - } - ident.Name = string(IgnoreNameOverridePrefix) + ident.Name - - parametrizedTypeSpec.TypeSpec.Name = ident + name += strings.Replace(strings.Join(nameParts, "-"), ".", "_", -1) - newType := pkgDefs.resolveGenericType(original.File, original.TypeSpec.Type, genericParamTypeDefs, parseDependency) + if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok { + return typeSpec + } - genericDefinitionsMutex.Lock() - defer genericDefinitionsMutex.Unlock() - parametrizedTypeSpec.TypeSpec.Type = newType - if genericsDefinitions[original] == nil { - genericsDefinitions[original] = map[string]*TypeSpecDef{} + parametrizedTypeSpec := &TypeSpecDef{ + File: original.File, + PkgPath: original.PkgPath, + TypeSpec: &ast.TypeSpec{ + Name: &ast.Ident{ + Name: name, + NamePos: original.TypeSpec.Name.NamePos, + Obj: original.TypeSpec.Name.Obj, + }, + Type: pkgDefs.resolveGenericType(original.File, original.TypeSpec.Type, genericParamTypeDefs, parseDependency), + Doc: original.TypeSpec.Doc, + Assign: original.TypeSpec.Assign, + }, } - genericsDefinitions[original][fullGenericForm] = parametrizedTypeSpec + pkgDefs.uniqueDefinitions[name] = parametrizedTypeSpec + return parametrizedTypeSpec } -// splitStructName splits a generic struct name in his parts -func splitStructName(fullGenericForm string) (string, []string) { +// splitGenericsTypeName splits a generic struct name in his parts +func splitGenericsTypeName(fullGenericForm string) (string, []string) { //remove all spaces character fullGenericForm = strings.Map(func(r rune) rune { if unicode.IsSpace(r) { @@ -197,11 +146,24 @@ func splitStructName(fullGenericForm string) (string, []string) { return genericTypeName, genericParams } +func (pkgDefs *PackagesDefinitions) getParametrizedType(genTypeSpec *genericTypeSpec) ast.Expr { + if genTypeSpec.TypeSpec != nil && strings.Contains(genTypeSpec.Name, ".") { + parts := strings.SplitN(genTypeSpec.Name, ".", 2) + return &ast.SelectorExpr{ + X: &ast.Ident{Name: parts[0]}, + Sel: &ast.Ident{Name: parts[1]}, + } + } + + //a primitive type name or a type name in current package + return &ast.Ident{Name: genTypeSpec.Name} +} + func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec, parseDependency bool) ast.Expr { switch astExpr := expr.(type) { case *ast.Ident: if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok { - retType := genTypeSpec.Type() + retType := pkgDefs.getParametrizedType(genTypeSpec) for i := 0; i < genTypeSpec.ArrayDepth; i++ { retType = &ast.ArrayType{Elt: retType} } @@ -220,7 +182,7 @@ func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast. } case *ast.IndexExpr, *ast.IndexListExpr: fullGenericName, _ := getGenericFieldType(file, expr, genericParamTypeDefs) - typeDef := pkgDefs.findGenericTypeSpec(fullGenericName, file, parseDependency) + typeDef := pkgDefs.FindTypeSpec(fullGenericName, file, parseDependency) if typeDef != nil { return typeDef.TypeSpec.Type } @@ -274,7 +236,7 @@ func getExtendedGenericFieldType(file *ast.File, field ast.Expr, genericParamTyp TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } - return tSpec.FullName(), nil + return tSpec.TypeName(), nil default: return getFieldType(file, field) } @@ -343,14 +305,14 @@ func getGenericTypeName(file *ast.File, field ast.Expr) (string, error) { TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } - return tSpec.FullName(), nil + return tSpec.TypeName(), nil case *ast.ArrayType: tSpec := &TypeSpecDef{ File: file, TypeSpec: fieldType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } - return tSpec.FullName(), nil + return tSpec.TypeName(), nil case *ast.SelectorExpr: return fmt.Sprintf("%s.%s", fieldType.X.(*ast.Ident).Name, fieldType.Sel.Name), nil } diff --git a/generics_other.go b/generics_other.go index e82ef7329..deab83167 100644 --- a/generics_other.go +++ b/generics_other.go @@ -15,10 +15,6 @@ type genericTypeSpec struct { Name string } -func typeSpecFullName(typeSpecDef *TypeSpecDef) string { - return typeSpecDef.FullName() -} - func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { return original } diff --git a/generics_test.go b/generics_test.go index 44b7cd24c..deb5b75f5 100644 --- a/generics_test.go +++ b/generics_test.go @@ -103,20 +103,39 @@ func TestParseGenericsNames(t *testing.T) { assert.Equal(t, string(expected), string(b)) } +func TestParseGenericsPackageAlias(t *testing.T) { + t.Parallel() + + searchDir := "testdata/generics_package_alias" + expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + + p := New() + err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + assert.NoError(t, err) + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, string(expected), string(b)) +} + func TestParametrizeStruct(t *testing.T) { pd := PackagesDefinitions{ - packages: make(map[string]*PackageDefinitions), + packages: make(map[string]*PackageDefinitions), + uniqueDefinitions: make(map[string]*TypeSpecDef), } // valid typeSpec := pd.parametrizeGenericType( &ast.File{Name: &ast.Ident{Name: "test2"}}, &TypeSpecDef{ + File: &ast.File{Name: &ast.Ident{Name: "test"}}, TypeSpec: &ast.TypeSpec{ Name: &ast.Ident{Name: "Field"}, TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}}, Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}}, }}, "test.Field[string, []string]", false) + assert.NotNil(t, typeSpec) assert.Equal(t, "$test.Field-string-array_string", typeSpec.Name()) + assert.Equal(t, "test.Field-string-array_string", typeSpec.TypeName()) // definition contains one type params, but two type params are provided typeSpec = pd.parametrizeGenericType( @@ -172,30 +191,30 @@ func TestParametrizeStruct(t *testing.T) { assert.Nil(t, typeSpec) } -func TestSplitStructNames(t *testing.T) { +func TestSplitGenericsTypeNames(t *testing.T) { t.Parallel() - field, params := splitStructName("test.Field") + field, params := splitGenericsTypeName("test.Field") assert.Empty(t, field) assert.Nil(t, params) - field, params = splitStructName("test.Field]") + field, params = splitGenericsTypeName("test.Field]") assert.Empty(t, field) assert.Nil(t, params) - field, params = splitStructName("test.Field[string") + field, params = splitGenericsTypeName("test.Field[string") assert.Empty(t, field) assert.Nil(t, params) - field, params = splitStructName("test.Field[string] ") + field, params = splitGenericsTypeName("test.Field[string] ") assert.Equal(t, "test.Field", field) assert.Equal(t, []string{"string"}, params) - field, params = splitStructName("test.Field[string, []string]") + field, params = splitGenericsTypeName("test.Field[string, []string]") assert.Equal(t, "test.Field", field) assert.Equal(t, []string{"string", "[]string"}, params) - field, params = splitStructName("test.Field[test.Field[ string, []string] ]") + field, params = splitGenericsTypeName("test.Field[test.Field[ string, []string] ]") assert.Equal(t, "test.Field", field) assert.Equal(t, []string{"test.Field[string,[]string]"}, params) } diff --git a/packages.go b/packages.go index 39048ac62..1883e5666 100644 --- a/packages.go +++ b/packages.go @@ -109,6 +109,7 @@ func (pkgDefs *PackagesDefinitions) ParseTypes() (map[*TypeSpecDef]*Schema, erro pkgDefs.parseTypesFromFile(astFile, info.PackagePath, parsedSchemas) pkgDefs.parseFunctionScopedTypesFromFile(astFile, info.PackagePath, parsedSchemas) } + pkgDefs.removeAllNotUniqueTypes() return parsedSchemas, nil } @@ -135,14 +136,16 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef) } - fullName := typeSpecFullName(typeSpecDef) + fullName := typeSpecDef.TypeName() anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { - if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { - continue - } else { - delete(pkgDefs.uniqueDefinitions, fullName) + if typeSpecDef.PkgPath != anotherTypeDef.PkgPath { + anotherTypeDef.NotUnique = true + typeSpecDef.NotUnique = true + pkgDefs.uniqueDefinitions[fullName] = nil + pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef + pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef } } else { pkgDefs.uniqueDefinitions[fullName] = typeSpecDef @@ -190,14 +193,16 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef) } - fullName := typeSpecFullName(typeSpecDef) + fullName := typeSpecDef.TypeName() anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { - if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { - continue - } else { - delete(pkgDefs.uniqueDefinitions, fullName) + if typeSpecDef.PkgPath != anotherTypeDef.PkgPath { + anotherTypeDef.NotUnique = true + typeSpecDef.NotUnique = true + pkgDefs.uniqueDefinitions[fullName] = nil + pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef + pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef } } else { pkgDefs.uniqueDefinitions[fullName] = typeSpecDef @@ -221,6 +226,14 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as } } +func (pkgDefs *PackagesDefinitions) removeAllNotUniqueTypes() { + for key, ud := range pkgDefs.uniqueDefinitions { + if ud == nil { + delete(pkgDefs.uniqueDefinitions, key) + } + } +} + func (pkgDefs *PackagesDefinitions) findTypeSpec(pkgPath string, typeName string) *TypeSpecDef { if pkgDefs.packages == nil { return nil @@ -355,23 +368,9 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File parts := strings.Split(strings.Split(typeName, "[")[0], ".") if len(parts) > 1 { - isAliasPkgName := func(file *ast.File, pkgName string) bool { - if file != nil && file.Imports != nil { - for _, pkg := range file.Imports { - if pkg.Name != nil && pkg.Name.Name == pkgName { - return true - } - } - } - - return false - } - - if !isAliasPkgName(file, parts[0]) { - typeDef, ok := pkgDefs.uniqueDefinitions[typeName] - if ok { - return typeDef - } + typeDef, ok := pkgDefs.uniqueDefinitions[typeName] + if ok { + return typeDef } pkgPath := pkgDefs.findPackagePathFromImports(parts[0], file, false) @@ -389,15 +388,9 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File } } - if def := pkgDefs.findGenericTypeSpec(typeName, file, parseDependency); def != nil { - return def - } - - return pkgDefs.findTypeSpec(pkgPath, parts[1]) - } + typeDef = pkgDefs.findTypeSpec(pkgPath, parts[1]) - if def := pkgDefs.findGenericTypeSpec(fullTypeName(file.Name.Name, typeName), file, parseDependency); def != nil { - return def + return pkgDefs.parametrizeGenericType(file, typeDef, typeName, parseDependency) } typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] @@ -405,38 +398,31 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File return typeDef } - typeDef = pkgDefs.findTypeSpec(pkgDefs.files[file].PackagePath, typeName) - if typeDef != nil { + //in case that comment //@name renamed the type with a name without a dot + typeDef, ok = pkgDefs.uniqueDefinitions[typeName] + if ok { return typeDef } - for _, imp := range file.Imports { - if imp.Name != nil && imp.Name.Name == "." { - typeDef := pkgDefs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), typeName) - if typeDef != nil { - return typeDef - } + typeDef = func() *TypeSpecDef { + name := parts[0] + typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, name)] + if ok { + return typeDef } - } - - return nil -} - -func (pkgDefs *PackagesDefinitions) findGenericTypeSpec(typeName string, file *ast.File, parseDependency bool) *TypeSpecDef { - if strings.Contains(typeName, "[") { - // genericName differs from typeName in that it does not contain any type parameters - genericName := strings.SplitN(typeName, "[", 2)[0] - for tName, tSpec := range pkgDefs.uniqueDefinitions { - if !strings.Contains(tName, "[") { - continue - } - - if strings.Contains(tName, genericName) { - if parametrized := pkgDefs.parametrizeGenericType(file, tSpec, typeName, parseDependency); parametrized != nil { - return parametrized + typeDef = pkgDefs.findTypeSpec(pkgDefs.files[file].PackagePath, name) + if typeDef != nil { + return typeDef + } + for _, imp := range file.Imports { + if imp.Name != nil && imp.Name.Name == "." { + typeDef = pkgDefs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), name) + if typeDef != nil { + break } } } - } - return nil + return typeDef + }() + return pkgDefs.parametrizeGenericType(file, typeDef, typeName, parseDependency) } diff --git a/parser.go b/parser.go index c1bbb90d0..a36ba6d2d 100644 --- a/parser.go +++ b/parser.go @@ -11,7 +11,6 @@ import ( "go/token" "log" "net/http" - "net/url" "os" "os/exec" "path/filepath" @@ -105,15 +104,6 @@ type Parser struct { // outputSchemas store schemas which will be export to swagger outputSchemas map[*TypeSpecDef]*Schema - // existSchemaNames store names of models for conflict determination - existSchemaNames map[string]*Schema - - // toBeRenamedSchemas names of models to be renamed - toBeRenamedSchemas map[string]string - - // toBeRenamedSchemas URLs of ref models to be renamed - toBeRenamedRefURLs []*url.URL - // PropNamingStrategy naming strategy PropNamingStrategy string @@ -208,8 +198,6 @@ func New(options ...func(*Parser)) *Parser { debug: log.New(os.Stdout, "", log.LstdFlags), parsedSchemas: make(map[*TypeSpecDef]*Schema), outputSchemas: make(map[*TypeSpecDef]*Schema), - existSchemaNames: make(map[string]*Schema), - toBeRenamedSchemas: make(map[string]string), excludes: make(map[string]struct{}), fieldParserFactory: newTagBaseFieldParser, Overrides: make(map[string]string), @@ -368,8 +356,6 @@ func (parser *Parser) ParseAPIMultiSearchDir(searchDirs []string, mainAPIFile st return err } - parser.renameRefSchemas() - return parser.checkOperationIDUniqueness() } @@ -933,57 +919,9 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( return schema.Schema, nil } -func (parser *Parser) renameRefSchemas() { - if len(parser.toBeRenamedSchemas) == 0 { - return - } - - // rename schemas in swagger.Definitions - for name, pkgPath := range parser.toBeRenamedSchemas { - if schema, ok := parser.swagger.Definitions[name]; ok { - delete(parser.swagger.Definitions, name) - name = parser.renameSchema(name, pkgPath) - parser.swagger.Definitions[name] = schema - } - } - - // rename URLs if match - for _, refURL := range parser.toBeRenamedRefURLs { - parts := strings.Split(refURL.Fragment, "/") - name := parts[len(parts)-1] - - if pkgPath, ok := parser.toBeRenamedSchemas[name]; ok { - parts[len(parts)-1] = parser.renameSchema(name, pkgPath) - - refURL.Fragment = strings.Join(parts, "/") - } - } -} - -func (parser *Parser) renameSchema(name, pkgPath string) string { - parts := strings.Split(name, ".") - name = fullTypeName(pkgPath, parts[len(parts)-1]) - name = strings.ReplaceAll(name, "/", "_") - - return name -} - func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) *spec.Schema { _, ok := parser.outputSchemas[typeSpecDef] if !ok { - existSchema, ok := parser.existSchemaNames[schema.Name] - if ok { - // store the first one to be renamed after parsing over - _, ok = parser.toBeRenamedSchemas[existSchema.Name] - if !ok { - parser.toBeRenamedSchemas[existSchema.Name] = existSchema.PkgPath - } - // rename not the first one - schema.Name = parser.renameSchema(schema.Name, schema.PkgPath) - } else { - parser.existSchemaNames[schema.Name] = schema - } - parser.swagger.Definitions[schema.Name] = spec.Schema{} if schema.Schema != nil { @@ -994,8 +932,6 @@ func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) } refSchema := RefSchema(schema.Name) - // store every URL - parser.toBeRenamedRefURLs = append(parser.toBeRenamedRefURLs, refSchema.Ref.GetURL()) return refSchema } @@ -1014,14 +950,7 @@ func (parser *Parser) isInStructStack(typeSpecDef *TypeSpecDef) bool { // given name and package, and populates swagger schema definitions registry // with a schema for the given type func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) { - typeName := typeSpecDef.FullName() - var refTypeName string - if fn, ok := (typeSpecDef.ParentSpec).(*ast.FuncDecl); ok { - refTypeName = TypeDocNameFuncScoped(typeName, typeSpecDef.TypeSpec, fn.Name.Name) - } else { - refTypeName = TypeDocName(typeName, typeSpecDef.TypeSpec) - } - + typeName := typeSpecDef.TypeName() schema, found := parser.parsedSchemas[typeSpecDef] if found { parser.debug.Printf("Skipping '%s', already parsed.", typeName) @@ -1033,7 +962,7 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) parser.debug.Printf("Skipping '%s', recursion detected.", typeName) return &Schema{ - Name: refTypeName, + Name: typeName, PkgPath: typeSpecDef.PkgPath, Schema: PrimitiveSchema(OBJECT), }, @@ -1054,7 +983,7 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) } sch := Schema{ - Name: refTypeName, + Name: typeName, PkgPath: typeSpecDef.PkgPath, Schema: definition, } @@ -1069,20 +998,8 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) return &sch, nil } -func fullTypeName(pkgName, typeName string) string { - if pkgName != "" && !ignoreNameOverride(typeName) { - return pkgName + "." + typeName - } - - return typeName -} - -func fullTypeNameFunctionScoped(pkgName, fnName, typeName string) string { - if pkgName != "" { - return pkgName + "." + fnName + "." + typeName - } - - return typeName +func fullTypeName(parts ...string) string { + return strings.Join(parts, ".") } // fillDefinitionDescription additionally fills fields in definition (spec.Schema) diff --git a/parser_test.go b/parser_test.go index bd58d4d16..9afebfb30 100644 --- a/parser_test.go +++ b/parser_test.go @@ -168,7 +168,7 @@ func TestParser_ParseDefinition(t *testing.T) { } _, err = p.ParseDefinition(definition) assert.Error(t, err) - assert.Equal(t, "model.TestFuncDecl.Test", definition.FullName()) + assert.Equal(t, "model.TestFuncDecl.Test", definition.TypeName()) } func TestParser_ParseGeneralApiInfo(t *testing.T) { @@ -3384,7 +3384,6 @@ func Fun() { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") - t.Log(string(b)) assert.Equal(t, expected, string(b)) } diff --git a/schema.go b/schema.go index 0baee9328..5949113fb 100644 --- a/schema.go +++ b/schema.go @@ -3,8 +3,6 @@ package swag import ( "errors" "fmt" - "go/ast" - "strings" "github.com/go-openapi/spec" ) @@ -133,58 +131,10 @@ func TransToValidCollectionFormat(format string) string { return "" } -// TypeDocName get alias from comment '// @name ', otherwise the original type name to display in doc. -func TypeDocName(pkgName string, spec *ast.TypeSpec) string { - if spec != nil && !ignoreNameOverride(pkgName) { - if spec.Comment != nil { - for _, comment := range spec.Comment.List { - texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") - if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { - return texts[1] - } - } - } - - if spec.Name != nil { - return fullTypeName(strings.Split(pkgName, ".")[0], spec.Name.Name) - } - } - - if ignoreNameOverride(pkgName) { - return pkgName[1:] - } - - return pkgName -} - func ignoreNameOverride(name string) bool { return len(name) != 0 && name[0] == IgnoreNameOverridePrefix } -// TypeDocNameFuncScoped get alias from comment '// @name ', otherwise the original type name to display in doc. -func TypeDocNameFuncScoped(pkgName string, spec *ast.TypeSpec, fnName string) string { - if spec != nil && !ignoreNameOverride(pkgName) { - if spec.Comment != nil { - for _, comment := range spec.Comment.List { - texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") - if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { - return texts[1] - } - } - } - - if spec.Name != nil { - return fullTypeNameFunctionScoped(strings.Split(pkgName, ".")[0], fnName, spec.Name.Name) - } - } - - if ignoreNameOverride(pkgName) { - return pkgName[1:] - } - - return pkgName -} - // RefSchema build a reference schema. func RefSchema(refType string) *spec.Schema { return spec.RefSchema("#/definitions/" + refType) diff --git a/schema_test.go b/schema_test.go index d6fb3fdb2..6589e2e54 100644 --- a/schema_test.go +++ b/schema_test.go @@ -1,7 +1,6 @@ package swag import ( - "go/ast" "testing" "github.com/go-openapi/spec" @@ -150,57 +149,3 @@ func TestIsInterfaceLike(t *testing.T) { assert.Equal(t, IsInterfaceLike(STRING), false) } - -func TestTypeDocName(t *testing.T) { - t.Parallel() - - expected := "a/package" - assert.Equal(t, expected, TypeDocName(expected, nil)) - - expected = "package.Model" - assert.Equal(t, expected, TypeDocName("package", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}})) - - expected = "Model" - assert.Equal(t, expected, TypeDocName("package", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name Model"}}, - }, - })) - - expected = "package.ModelName" - assert.Equal(t, expected, TypeDocName("$package.ModelName", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}})) - - expected = "Model" - assert.Equal(t, expected, TypeDocName("$Model", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name ModelName"}}, - }, - })) -} - -func TestTypeDocNameFuncScoped(t *testing.T) { - t.Parallel() - - expected := "a/package" - assert.Equal(t, expected, TypeDocNameFuncScoped(expected, nil, "FnName")) - - expected = "package.FnName.Model" - assert.Equal(t, expected, TypeDocNameFuncScoped("package", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}}, "FnName")) - - expected = "Model" - assert.Equal(t, expected, TypeDocNameFuncScoped("package", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name Model"}}, - }, - }, "FnName")) - - expected = "package.FnName.ModelName" - assert.Equal(t, expected, TypeDocNameFuncScoped("$package.FnName.ModelName", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}}, "FnName")) - - expected = "Model" - assert.Equal(t, expected, TypeDocNameFuncScoped("$Model", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name ModelName"}}, - }, - }, "FnName")) -} diff --git a/testdata/conflict_name/expected.json b/testdata/conflict_name/expected.json index 74f046c46..0b3576dbc 100644 --- a/testdata/conflict_name/expected.json +++ b/testdata/conflict_name/expected.json @@ -24,7 +24,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse" } } } @@ -47,7 +47,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse" } } } @@ -55,7 +55,7 @@ } }, "definitions": { - "github.com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse": { + "github_com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse": { "type": "object", "properties": { "newTime": { @@ -63,7 +63,7 @@ } } }, - "github.com_swaggo_swag_testdata_conflict_name_model.MyStruct": { + "github_com_swaggo_swag_testdata_conflict_name_model.MyStruct": { "type": "object", "properties": { "name": { @@ -71,7 +71,7 @@ } } }, - "github.com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse": { + "github_com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse": { "type": "object", "properties": { "newTime": { @@ -79,7 +79,7 @@ } } }, - "github.com_swaggo_swag_testdata_conflict_name_model2.MyStruct": { + "github_com_swaggo_swag_testdata_conflict_name_model2.MyStruct": { "type": "object", "properties": { "name": { @@ -91,7 +91,7 @@ "type": "object", "properties": { "my": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model.MyStruct" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model.MyStruct" }, "name": { "type": "string" @@ -102,7 +102,7 @@ "type": "object", "properties": { "my": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model2.MyStruct" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model2.MyStruct" }, "name": { "type": "string" diff --git a/testdata/generics_basic/expected.json b/testdata/generics_basic/expected.json index d4933fe9c..b1d362497 100644 --- a/testdata/generics_basic/expected.json +++ b/testdata/generics_basic/expected.json @@ -46,7 +46,7 @@ "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericResponse-types_Field_string" + "$ref": "#/definitions/web.GenericResponse-types_Field-string" } }, "222": { @@ -138,25 +138,25 @@ "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericResponse-types_Field_string" + "$ref": "#/definitions/web.GenericResponse-types_Field-string" } }, "203": { "description": "Non-Authoritative Information", "schema": { - "$ref": "#/definitions/web.GenericResponse-types_Field_int" + "$ref": "#/definitions/web.GenericResponse-types_Field-int" } }, "204": { "description": "No Content", "schema": { - "$ref": "#/definitions/api.Response-string-types_Field_int" + "$ref": "#/definitions/api.Response-string-types_Field-int" } }, "205": { "description": "Reset Content", "schema": { - "$ref": "#/definitions/api.Response-api_StringStruct-types_Field_int" + "$ref": "#/definitions/api.Response-api_StringStruct-types_Field-int" } }, "222": { @@ -182,7 +182,7 @@ } }, "definitions": { - "api.Response-api_StringStruct-types_Field_int": { + "api.Response-api_StringStruct-types_Field-int": { "type": "object", "properties": { "data": { @@ -196,7 +196,7 @@ } } }, - "api.Response-string-types_Field_int": { + "api.Response-string-types_Field-int": { "type": "object", "properties": { "data": { @@ -226,6 +226,14 @@ } } }, + "types.Field-string": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, "types.Hello": { "type": "object", "properties": { @@ -372,7 +380,7 @@ } } }, - "web.GenericResponse-types_Field_int": { + "web.GenericResponse-types_Field-int": { "type": "object", "properties": { "data": { @@ -383,11 +391,11 @@ } } }, - "web.GenericResponse-types_Field_string": { + "web.GenericResponse-types_Field-string": { "type": "object", "properties": { "data": { - "type": "string" + "$ref": "#/definitions/types.Field-string" }, "status": { "type": "string" diff --git a/testdata/generics_names/api/api_alias_pkg.go b/testdata/generics_names/api/api_alias_pkg.go new file mode 100644 index 000000000..2ae39d69a --- /dev/null +++ b/testdata/generics_names/api/api_alias_pkg.go @@ -0,0 +1,19 @@ +package api + +import ( + "net/http" + + mytypes "github.com/swaggo/swag/testdata/generics_names/types" + myweb "github.com/swaggo/swag/testdata/generics_names/web" +) + +// @Summary Add a new pet to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Success 200 {object} myweb.AliasPkgGenericResponse[mytypes.Post] +// @Router /posts/aliaspkg [post] +func GetPostFromAliasPkg(w http.ResponseWriter, r *http.Request) { + //write your code + _ = myweb.AliasPkgGenericResponse[mytypes.Post]{} +} diff --git a/testdata/generics_names/expected.json b/testdata/generics_names/expected.json index 4891eaf4e..741b2455d 100644 --- a/testdata/generics_names/expected.json +++ b/testdata/generics_names/expected.json @@ -131,6 +131,26 @@ } } } + }, + "/posts/aliaspkg": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add a new pet to the store", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.AliasPkgGenericResponse-Post" + } + } + } + } } }, "definitions": { @@ -287,6 +307,17 @@ "type": "integer" } } + }, + "web.AliasPkgGenericResponse-Post": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/Post" + }, + "status": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/testdata/generics_names/web/handler.go b/testdata/generics_names/web/handler.go index c0d89d1cf..20c0a80d8 100644 --- a/testdata/generics_names/web/handler.go +++ b/testdata/generics_names/web/handler.go @@ -38,3 +38,9 @@ type APIError struct { ErrorCtx string // Error `context` tick comment CreatedAt time.Time // Error time } + +type AliasPkgGenericResponse[T any] struct { + Data T + + Status string +} diff --git a/testdata/generics_nested/expected.json b/testdata/generics_nested/expected.json index 78ea43f9c..a2d005505 100644 --- a/testdata/generics_nested/expected.json +++ b/testdata/generics_nested/expected.json @@ -26,7 +26,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType-types_Post" } } ], @@ -40,25 +40,25 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType-types_Post" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-types_Post" } }, "203": { "description": "Non-Authoritative Information", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post" } }, "222": { "description": "", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_types_Post-types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType-types_Post-types_Post" } } } @@ -81,7 +81,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_array_types_Post" + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType-array_types_Post" } } ], @@ -95,37 +95,37 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType-types_Post" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_array_types_Post" + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType-array_types_Post" } }, "203": { "description": "Non-Authoritative Information", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType_array_types_Post_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType-array_types_Post-types_Post" } }, "204": { "description": "No Content", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType_array_types_Post_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType-array_types_Post-types_Post" } }, "205": { "description": "Reset Content", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_array_web_GenericInnerType_array2_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post" } }, "222": { "description": "", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_array_types_Post-array_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType-array_types_Post-array_types_Post" } } } @@ -183,7 +183,7 @@ } } }, - "web.GenericInnerMultiType-types_Post-array_web_GenericInnerType_array2_types_Post": { + "web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": { "type": "object", "properties": { "itemOne": { @@ -218,7 +218,7 @@ } } }, - "web.GenericInnerMultiType-types_Post-web_GenericInnerType_types_Post": { + "web.GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post": { "type": "object", "properties": { "itemOne": { @@ -270,7 +270,7 @@ } } }, - "web.GenericNestedBody-web_GenericInnerType_array_types_Post": { + "web.GenericNestedBody-web_GenericInnerType-array_types_Post": { "type": "object", "properties": { "items": { @@ -283,7 +283,7 @@ } } }, - "web.GenericNestedBody-web_GenericInnerType_types_Post": { + "web.GenericNestedBody-web_GenericInnerType-types_Post": { "type": "object", "properties": { "items": { @@ -315,7 +315,7 @@ } } }, - "web.GenericNestedResponse-array_web_GenericInnerType_array_types_Post": { + "web.GenericNestedResponse-array_web_GenericInnerType-array_types_Post": { "type": "object", "properties": { "items": { @@ -334,7 +334,7 @@ } } }, - "web.GenericNestedResponse-array_web_GenericInnerType_types_Post": { + "web.GenericNestedResponse-array_web_GenericInnerType-types_Post": { "type": "object", "properties": { "items": { @@ -369,7 +369,7 @@ } } }, - "web.GenericNestedResponse-web_GenericInnerType_types_Post": { + "web.GenericNestedResponse-web_GenericInnerType-types_Post": { "type": "object", "properties": { "items": { @@ -385,7 +385,7 @@ } } }, - "web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType_array_types_Post_types_Post": { + "web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType-array_types_Post-types_Post": { "type": "object", "properties": { "itemOne": { @@ -411,7 +411,7 @@ } } }, - "web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType_array_types_Post_types_Post": { + "web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType-array_types_Post-types_Post": { "type": "object", "properties": { "itemOne": { @@ -434,7 +434,7 @@ } } }, - "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_array_web_GenericInnerType_array2_types_Post": { + "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": { "type": "object", "properties": { "itemOne": { @@ -445,7 +445,7 @@ "description": "ItemsTwo is the second thing", "type": "array", "items": { - "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType_array2_types_Post" + "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post" } }, "status": { @@ -454,7 +454,7 @@ } } }, - "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_types_Post": { + "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-types_Post": { "type": "object", "properties": { "itemOne": { @@ -474,7 +474,7 @@ } } }, - "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_web_GenericInnerType_types_Post": { + "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post": { "type": "object", "properties": { "itemOne": { @@ -485,7 +485,7 @@ "description": "ItemsTwo is the second thing", "type": "array", "items": { - "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post" } }, "status": { @@ -494,7 +494,7 @@ } } }, - "web.GenericNestedResponseMulti-web_GenericInnerType_array_types_Post-array_types_Post": { + "web.GenericNestedResponseMulti-web_GenericInnerType-array_types_Post-array_types_Post": { "type": "object", "properties": { "itemOne": { @@ -517,7 +517,7 @@ } } }, - "web.GenericNestedResponseMulti-web_GenericInnerType_types_Post-types_Post": { + "web.GenericNestedResponseMulti-web_GenericInnerType-types_Post-types_Post": { "type": "object", "properties": { "itemOne": { diff --git a/testdata/generics_package_alias/api/api1.go b/testdata/generics_package_alias/api/api1.go new file mode 100644 index 000000000..cf443df62 --- /dev/null +++ b/testdata/generics_package_alias/api/api1.go @@ -0,0 +1,25 @@ +package api + +import ( + myv1 "github.com/swaggo/swag/testdata/generics_package_alias/path1/v1" +) + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.ListResult[myv1.ProductDto] "" +// @Router /api1 [post] +func CreateMovie1() { + _ = myv1.ListResult[myv1.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.RenamedListResult[myv1.RenamedProductDto] "" +// @Router /api2 [post] +func CreateMovie2() { + _ = myv1.ListResult[myv1.ProductDto]{} +} diff --git a/testdata/generics_package_alias/api/api2.go b/testdata/generics_package_alias/api/api2.go new file mode 100644 index 000000000..9ffdd3911 --- /dev/null +++ b/testdata/generics_package_alias/api/api2.go @@ -0,0 +1,46 @@ +package api + +import ( + myv1 "github.com/swaggo/swag/testdata/generics_package_alias/path1/v1" + myv2 "github.com/swaggo/swag/testdata/generics_package_alias/path2/v1" +) + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv2.ListResult[myv2.ProductDto] "" +// @Router /api3 [post] +func CreateMovie3() { + _ = myv2.ListResult[myv2.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv2.RenamedListResult[myv2.RenamedProductDto] "" +// @Router /api4 [post] +func CreateMovie4() { + _ = myv2.ListResult[myv2.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.ListResult[myv2.ProductDto] "" +// @Router /api5 [post] +func CreateMovie5() { + _ = myv1.ListResult[myv2.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.RenamedListResult[myv2.RenamedProductDto] "" +// @Router /api6 [post] +func CreateMovie6() { + _ = myv1.ListResult[myv2.ProductDto]{} +} diff --git a/testdata/generics_package_alias/expected.json b/testdata/generics_package_alias/expected.json new file mode 100644 index 000000000..ec2c51aa1 --- /dev/null +++ b/testdata/generics_package_alias/expected.json @@ -0,0 +1,228 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/api1": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path1_v1_ProductDto" + } + } + } + } + }, + "/api2": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ListResultV1-ProductDtoV1" + } + } + } + } + }, + "/api3": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto" + } + } + } + } + }, + "/api4": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ListResultV2-ProductDtoV2" + } + } + } + } + }, + "/api5": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto" + } + } + } + } + }, + "/api6": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ListResultV1-ProductDtoV2" + } + } + } + } + } + }, + "definitions": { + "ListResultV1-ProductDtoV1": { + "type": "object", + "properties": { + "items11": { + "type": "array", + "items": { + "$ref": "#/definitions/ProductDtoV1" + } + } + } + }, + "ListResultV1-ProductDtoV2": { + "type": "object", + "properties": { + "items11": { + "type": "array", + "items": { + "$ref": "#/definitions/ProductDtoV2" + } + } + } + }, + "ListResultV2-ProductDtoV2": { + "type": "object", + "properties": { + "items22": { + "type": "array", + "items": { + "$ref": "#/definitions/ProductDtoV2" + } + } + } + }, + "ProductDtoV1": { + "type": "object", + "properties": { + "name11": { + "type": "string" + } + } + }, + "ProductDtoV2": { + "type": "object", + "properties": { + "name22": { + "type": "string" + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path1_v1_ProductDto": { + "type": "object", + "properties": { + "items1": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ProductDto" + } + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto": { + "type": "object", + "properties": { + "items1": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ProductDto" + } + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ProductDto": { + "type": "object", + "properties": { + "name1": { + "type": "string" + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto": { + "type": "object", + "properties": { + "items2": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ProductDto" + } + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ProductDto": { + "type": "object", + "properties": { + "name2": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/generics_package_alias/main.go b/testdata/generics_package_alias/main.go new file mode 100644 index 000000000..790580777 --- /dev/null +++ b/testdata/generics_package_alias/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/testdata/generics_package_alias/path1/v1/product.go b/testdata/generics_package_alias/path1/v1/product.go new file mode 100644 index 000000000..c0524a37b --- /dev/null +++ b/testdata/generics_package_alias/path1/v1/product.go @@ -0,0 +1,17 @@ +package v1 + +type ProductDto struct { + Name1 string `json:"name1"` +} + +type ListResult[T any] struct { + Items1 []T `json:"items1,omitempty"` +} + +type RenamedProductDto struct { + Name11 string `json:"name11"` +} // @name ProductDtoV1 + +type RenamedListResult[T any] struct { + Items11 []T `json:"items11,omitempty"` +} // @name ListResultV1 diff --git a/testdata/generics_package_alias/path2/v1/product.go b/testdata/generics_package_alias/path2/v1/product.go new file mode 100644 index 000000000..754ddb591 --- /dev/null +++ b/testdata/generics_package_alias/path2/v1/product.go @@ -0,0 +1,17 @@ +package v1 + +type ProductDto struct { + Name2 string `json:"name2"` +} + +type ListResult[T any] struct { + Items2 []T `json:"items2,omitempty"` +} + +type RenamedProductDto struct { + Name22 string `json:"name22"` +} // @name ProductDtoV2 + +type RenamedListResult[T any] struct { + Items22 []T `json:"items22,omitempty"` +} // @name ListResultV2 diff --git a/testdata/generics_property/expected.json b/testdata/generics_property/expected.json index 36acc18ca..e0880258f 100644 --- a/testdata/generics_property/expected.json +++ b/testdata/generics_property/expected.json @@ -121,10 +121,10 @@ "$ref": "#/definitions/types.Field-array_api_Person" }, "detail1": { - "$ref": "#/definitions/types.Field-types_Field_api_Person" + "$ref": "#/definitions/types.Field-types_Field-api_Person" }, "detail2": { - "$ref": "#/definitions/types.Field-types_Field_string" + "$ref": "#/definitions/types.Field-types_Field-string" }, "directors": { "$ref": "#/definitions/types.Field-array_api_Person" @@ -298,7 +298,7 @@ } } }, - "types.Field-types_Field_api_Person": { + "types.Field-types_Field-api_Person": { "type": "object", "properties": { "value": { @@ -326,7 +326,7 @@ } } }, - "types.Field-types_Field_string": { + "types.Field-types_Field-string": { "type": "object", "properties": { "value": { diff --git a/types.go b/types.go index 505984fdb..e149f8b28 100644 --- a/types.go +++ b/types.go @@ -2,6 +2,7 @@ package swag import ( "go/ast" + "strings" "github.com/go-openapi/spec" ) @@ -24,6 +25,8 @@ type TypeSpecDef struct { // path of package starting from under ${GOPATH}/src or from module path in go.mod PkgPath string ParentSpec ast.Decl + + NotUnique bool } // Name the name of the typeSpec. @@ -35,18 +38,40 @@ func (t *TypeSpecDef) Name() string { return "" } -// FullName full name of the typeSpec. -func (t *TypeSpecDef) FullName() string { - var fullName string - if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil { - fullName = fullTypeNameFunctionScoped(t.File.Name.Name, parentFun.Name.Name, t.TypeSpec.Name.Name) +// TypeName the type name of the typeSpec. +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 ' + for _, comment := range t.TypeSpec.Comment.List { + texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") + if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { + return texts[1] + } + } + } + + var names []string + if t.NotUnique { + pkgPath := strings.Map(func(r rune) rune { + if r == '\\' || r == '/' || r == '.' { + return '_' + } + return r + }, t.PkgPath) + names = append(names, pkgPath) } else { - fullName = fullTypeName(t.File.Name.Name, t.TypeSpec.Name.Name) + names = append(names, t.File.Name.Name) + } + if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil { + names = append(names, parentFun.Name.Name) } - return fullName + names = append(names, t.TypeSpec.Name.Name) + return fullTypeName(names...) } -// FullPath of the typeSpec. +// FullPath return the full path of the typeSpec. func (t *TypeSpecDef) FullPath() string { return t.PkgPath + "." + t.Name() }