Repository: swaggo/swag
Pull request #1825 (2024-06-13 07:03:17)

Update docs for request and response headers
**Describe the PR**
the feature request header or custom request header in swaggo is already implemented by pr #30. However, it has not yet been documented.
And this is the reason for the pr

**Relation issue**
https://github.com/swaggo/swag/issues/29

**Additional context**
Just a simple documentation extension



Patch:
diff --git a/README.md b/README.md
index 066ba58..17e6db3 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,8 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie
 	- [User defined structure with an array type](#user-defined-structure-with-an-array-type)
 	- [Function scoped struct declaration](#function-scoped-struct-declaration)
 	- [Model composition in response](#model-composition-in-response)
-	- [Add a headers in response](#add-a-headers-in-response)
+        - [Add request headers](#add-request-headers)
+	- [Add response headers](#add-response-headers)
 	- [Use multiple path params](#use-multiple-path-params)
 	- [Example value of struct](#example-value-of-struct)
 	- [SchemaExample of body](#schemaexample-of-body)
@@ -103,6 +104,7 @@ OPTIONS:
    --outputTypes value, --ot value        Output types of generated files (docs.go, swagger.json, swagger.yaml) like go,json,yaml (default: "go,json,yaml")
    --parseVendor                          Parse go files in 'vendor' folder, disabled by default (default: false)
    --parseDependency, --pd                Parse go files inside dependency folder, disabled by default (default: false)
+   --parseDependencyLevel, --pdl          Enhancement of '--parseDependency', parse go files inside dependency folder, 0 disabled, 1 only parse models, 2 only parse operations, 3 parse all (default: 0)
    --markdownFiles value, --md value      Parse folder containing markdown files to use as description, disabled by default
    --codeExampleFiles value, --cef value  Parse folder containing code example files to use for the x-codeSamples extension, disabled by default
    --parseInternal                        Parse go files in internal packages, disabled by default (default: false)
@@ -116,6 +118,7 @@ OPTIONS:
    --templateDelims value, --td value     Provide custom delimiters for Go template generation. The format is leftDelim,rightDelim. For example: "[[,]]"
    --collectionFormat value, --cf value   Set default collection format (default: "csv")
    --state value                          Initial state for the state machine (default: ""), @HostState in root file, @State in other files
+   --parseFuncBody                        Parse API info within body of functions in go files, disabled by default (default: false)
    --help, -h                             show help (default: false)
 ```
 
@@ -645,7 +648,14 @@ type DeepObject struct { //in `proto` package
 }
 @success 200 {object} jsonresult.JSONResult{data1=proto.Order{data=proto.DeepObject},data2=[]proto.Order{data=[]proto.DeepObject}} "desc"
 ```
-### Add a headers in response
+### Add response request
+
+```go
+// @Param        X-MyHeader	  header    string    true   	"MyHeader must be set for valid response"
+// @Param        X-API-VERSION    header    string    true   	"API version eg.: 1.0"
+```
+
+### Add response headers
 
 ```go
 // @Success      200              {string}  string    "ok"
@@ -931,7 +941,7 @@ func GetPosts(w http.ResponseWriter, r *http.Request) {
 	_ = web.GenericNestedResponse[types.Post]{}
 }
 ```
-See [this file](https://github.com/swaggo/swag/blob/master/testdata/generics_nested/api/api.go) for more details 
+See [this file](https://github.com/swaggo/swag/blob/master/testdata/generics_nested/api/api.go) for more details
 and other examples.
 
 ### Change the default Go Template action delimiters
diff --git a/README_zh-CN.md b/README_zh-CN.md
index 2b3d9d2..24a219c 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -90,6 +90,7 @@ OPTIONS:
    --output value, -o value               文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs")
    --parseVendor                          是否解析vendor目录里的go源文件，默认不
    --parseDependency                      是否解析依赖目录中的go源文件，默认不
+   --parseDependencyLevel, --pdl          对'--parseDependency'参数进行增强, 是否解析依赖目录中的go源文件, 0 不解析, 1 只解析对象模型, 2 只解析API, 3 对象模型和API都解析 (default: 0)
    --markdownFiles value, --md value      指定API的描述信息所使用的markdown文件所在的目录
    --generatedTime                        是否输出时间到输出文件docs.go的顶部，默认是
    --codeExampleFiles value, --cef value  解析包含用于 x-codeSamples 扩展的代码示例文件的文件夹，默认禁用
diff --git a/cmd/swag/main.go b/cmd/swag/main.go
index 9848bfd..fd12c7f 100644
--- a/cmd/swag/main.go
+++ b/cmd/swag/main.go
@@ -18,6 +18,7 @@ const (
 	searchDirFlag            = "dir"
 	excludeFlag              = "exclude"
 	generalInfoFlag          = "generalInfo"
+	pipeFlag                 = "pipe"
 	propertyStrategyFlag     = "propertyStrategy"
 	outputFlag               = "output"
 	outputTypesFlag          = "outputTypes"
@@ -41,6 +42,7 @@ const (
 	collectionFormatFlag     = "collectionFormat"
 	packagePrefixFlag        = "packagePrefix"
 	stateFlag                = "state"
+	parseFuncBodyFlag        = "parseFuncBody"
 )
 
 var initFlags = []cli.Flag{
@@ -179,6 +181,11 @@ var initFlags = []cli.Flag{
 		Value: "",
 		Usage: "Set host state for swagger.json",
 	},
+	&cli.BoolFlag{
+		Name: parseFuncBodyFlag,
+		// Value: false,
+		Usage: "Parse API info within body of functions in go files, disabled by default (default: false)",
+	},
 }
 
 func initAction(ctx *cli.Context) error {
@@ -195,11 +202,17 @@ func initAction(ctx *cli.Context) error {
 	if ctx.IsSet(templateDelimsFlag) {
 		delims := strings.Split(ctx.String(templateDelimsFlag), ",")
 		if len(delims) != 2 {
-			return fmt.Errorf("exactly two template delimiters must be provided, comma separated")
+			return fmt.Errorf(
+				"exactly two template delimiters must be provided, comma separated",
+			)
 		} else if delims[0] == delims[1] {
 			return fmt.Errorf("template delimiters must be different")
 		}
-		leftDelim, rightDelim = strings.TrimSpace(delims[0]), strings.TrimSpace(delims[1])
+		leftDelim, rightDelim = strings.TrimSpace(
+			delims[0],
+		), strings.TrimSpace(
+			delims[1],
+		)
 	}
 
 	outputTypes := strings.Split(ctx.String(outputTypesFlag), ",")
@@ -211,9 +224,14 @@ func initAction(ctx *cli.Context) error {
 		logger = log.New(io.Discard, "", log.LstdFlags)
 	}
 
-	collectionFormat := swag.TransToValidCollectionFormat(ctx.String(collectionFormatFlag))
+	collectionFormat := swag.TransToValidCollectionFormat(
+		ctx.String(collectionFormatFlag),
+	)
 	if collectionFormat == "" {
-		return fmt.Errorf("not supported %s collectionFormat", ctx.String(collectionFormat))
+		return fmt.Errorf(
+			"not supported %s collectionFormat",
+			ctx.String(collectionFormat),
+		)
 	}
 
 	var pdv = ctx.Int(parseDependencyLevelFlag)
@@ -249,6 +267,7 @@ func initAction(ctx *cli.Context) error {
 		CollectionFormat:    collectionFormat,
 		PackagePrefix:       ctx.String(packagePrefixFlag),
 		State:               ctx.String(stateFlag),
+		ParseFuncBody:       ctx.Bool(parseFuncBodyFlag),
 	})
 }
 
@@ -269,6 +288,11 @@ func main() {
 			Aliases: []string{"f"},
 			Usage:   "format swag comments",
 			Action: func(c *cli.Context) error {
+
+				if c.Bool(pipeFlag) {
+					return format.New().Run(os.Stdin, os.Stdout)
+				}
+
 				searchDir := c.String(searchDirFlag)
 				excludeDir := c.String(excludeFlag)
 				mainFile := c.String(generalInfoFlag)
@@ -296,6 +320,12 @@ func main() {
 					Value:   "main.go",
 					Usage:   "Go file path in which 'swagger general API Info' is written",
 				},
+				&cli.BoolFlag{
+					Name:    "pipe",
+					Aliases: []string{"p"},
+					Value:   false,
+					Usage:   "Read from stdin, write to stdout.",
+				},
 			},
 		},
 	}
diff --git a/format/format.go b/format/format.go
index 0b9dbfc..01761ed 100644
--- a/format/format.go
+++ b/format/format.go
@@ -3,6 +3,7 @@ package format
 import (
 	"bytes"
 	"fmt"
+	"io"
 	"os"
 	"path/filepath"
 	"strings"
@@ -81,7 +82,8 @@ func (f *Format) visit(path string, fileInfo os.FileInfo, err error) error {
 
 func (f *Format) excludeDir(path string) bool {
 	return f.exclude[path] ||
-		filepath.Base(path)[0] == '.' && len(filepath.Base(path)) > 1 // exclude hidden folders
+		filepath.Base(path)[0] == '.' &&
+			len(filepath.Base(path)) > 1 // exclude hidden folders
 }
 
 func (f *Format) excludeFile(path string) bool {
@@ -127,3 +129,20 @@ func write(path string, contents []byte) error {
 	}
 	return os.Rename(f.Name(), path)
 }
+
+// Run the format on src and write the result to dst.
+func (f *Format) Run(src io.Reader, dst io.Writer) error {
+	contents, err := io.ReadAll(src)
+	if err != nil {
+		return err
+	}
+	result, err := f.formatter.Format("", contents)
+	if err != nil {
+		return err
+	}
+	r := bytes.NewReader(result)
+	if _, err := io.Copy(dst, r); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/gen/gen.go b/gen/gen.go
index 43cf73e..417d51c 100644
--- a/gen/gen.go
+++ b/gen/gen.go
@@ -146,6 +146,9 @@ type Config struct {
 
 	// State set host state
 	State string
+
+	// ParseFuncBody whether swag should parse api info inside of funcs
+	ParseFuncBody bool
 }
 
 // Build builds swagger json file  for given searchDir and mainAPIFile. Returns json.
@@ -213,6 +216,7 @@ func (g *Gen) Build(config *Config) error {
 	p.ParseInternal = config.ParseInternal
 	p.RequiredByDefault = config.RequiredByDefault
 	p.HostState = config.State
+	p.ParseFuncBody = config.ParseFuncBody
 
 	if err := p.ParseAPIMultiSearchDir(searchDirs, config.MainAPIFile, config.ParseDepth); err != nil {
 		return err
diff --git a/generics.go b/generics.go
index 07344bb..80e93a9 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/operation.go b/operation.go
index 24b0496..2ce9c9e 100644
--- a/operation.go
+++ b/operation.go
@@ -281,7 +281,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
 
 	requiredText := strings.ToLower(matches[4])
 	required := requiredText == "true" || requiredText == requiredLabel
-	description := matches[5]
+	description := strings.Join(strings.Split(matches[5], "\\n"), "\n")
 
 	param := createParameter(paramType, description, name, objectType, refType, required, enums, operation.parser.collectionFormatInQuery)
 
@@ -841,9 +841,9 @@ func parseObjectSchema(parser *Parser, refType string, astFile *ast.File) (*spec
 	case refType == NIL:
 		return nil, nil
 	case refType == INTERFACE:
-		return PrimitiveSchema(OBJECT), nil
+		return &spec.Schema{}, nil
 	case refType == ANY:
-		return PrimitiveSchema(OBJECT), nil
+		return &spec.Schema{}, nil
 	case IsGolangPrimitiveType(refType):
 		refType = TransToValidSchemeType(refType)
 
diff --git a/packages.go b/packages.go
index 466db86..2032414 100644
--- a/packages.go
+++ b/packages.go
@@ -93,7 +93,7 @@ func (pkgDefs *PackagesDefinitions) RangeFiles(handle func(info *AstFileInfo) er
 	for _, info := range pkgDefs.files {
 		// ignore package path prefix with 'vendor' or $GOROOT,
 		// because the router info of api will not be included these files.
-		if strings.HasPrefix(info.PackagePath, "vendor") || (runtime.GOROOT() != "" && strings.HasPrefix(info.Path, runtime.GOROOT())) {
+		if strings.HasPrefix(info.PackagePath, "vendor") || (runtime.GOROOT() != "" && strings.HasPrefix(info.Path, runtime.GOROOT()+string(filepath.Separator))) {
 			continue
 		}
 		sortedFiles = append(sortedFiles, info)
@@ -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 {
@@ -192,6 +196,7 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
 	for _, astDeclaration := range astFile.Decls {
 		funcDeclaration, ok := astDeclaration.(*ast.FuncDecl)
 		if ok && funcDeclaration.Body != nil {
+			functionScopedTypes := make(map[string]*TypeSpecDef)
 			for _, stmt := range funcDeclaration.Body.List {
 				if declStmt, ok := (stmt).(*ast.DeclStmt); ok {
 					if genDecl, ok := (declStmt.Decl).(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
@@ -212,12 +217,31 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
 									}
 								}
 
+								fullName := typeSpecDef.TypeName()
+								if structType, ok := typeSpecDef.TypeSpec.Type.(*ast.StructType); ok {
+									for _, field := range structType.Fields.List {
+										var idt *ast.Ident
+										var ok bool
+										switch field.Type.(type) {
+										case *ast.Ident:
+											idt, ok = field.Type.(*ast.Ident)
+										case *ast.StarExpr:
+											idt, ok = field.Type.(*ast.StarExpr).X.(*ast.Ident)
+										case *ast.ArrayType:
+											idt, ok = field.Type.(*ast.ArrayType).Elt.(*ast.Ident)
+										}
+										if ok && !IsGolangPrimitiveType(idt.Name) {
+											if functype, ok := functionScopedTypes[idt.Name]; ok {
+												idt.Name = functype.TypeName()
+											}
+										}
+									}
+								}
+
 								if pkgDefs.uniqueDefinitions == nil {
 									pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef)
 								}
 
-								fullName := typeSpecDef.TypeName()
-
 								anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName]
 								if ok {
 									if anotherTypeDef == nil {
@@ -228,14 +252,19 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
 										pkgDefs.uniqueDefinitions[fullName] = nil
 										anotherTypeDef.NotUnique = true
 										pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef
+										anotherTypeDef.SetSchemaName()
+
 										typeSpecDef.NotUnique = true
 										fullName = typeSpecDef.TypeName()
 										pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
 									}
 								} else {
 									pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
+									functionScopedTypes[typeSpec.Name.Name] = typeSpecDef
 								}
 
+								typeSpecDef.SetSchemaName()
+
 								if pkgDefs.packages[typeSpecDef.PkgPath] == nil {
 									pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(fullName, typeSpecDef)
 								} else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[fullName]; !ok {
@@ -353,7 +382,7 @@ func (pkgDefs *PackagesDefinitions) collectConstEnums(parsedSchemas map[*TypeSpe
 				continue
 			}
 
-			//delete it from parsed schemas, and will parse it again
+			// delete it from parsed schemas, and will parse it again
 			if _, ok = parsedSchemas[typeDef]; ok {
 				delete(parsedSchemas, typeDef)
 			}
@@ -469,7 +498,7 @@ func (pkgDefs *PackagesDefinitions) findPackagePathFromImports(pkg string, file
 				}
 				break
 			} else if imp.Name.Name == "_" && len(pkg) > 0 {
-				//for unused types
+				// for unused types
 				pd, ok := pkgDefs.packages[path]
 				if ok {
 					if pd.Name == pkg {
@@ -558,17 +587,27 @@ 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 k, v := range pkgDefs.uniqueDefinitions {
+		if v == nil {
+			pkgDefs.debug.Printf("%s TypeSpecDef is nil", k)
+			continue
+		}
+		if v.SchemaName == typeName {
+			return v
+		}
+	}
+
+	return nil
 }
diff --git a/parser.go b/parser.go
index 8a47d00..eb5d96c 100644
--- a/parser.go
+++ b/parser.go
@@ -179,6 +179,9 @@ type Parser struct {
 
 	// HostState is the state of the host
 	HostState string
+
+	// ParseFuncBody whether swag should parse api info inside of funcs
+	ParseFuncBody bool
 }
 
 // FieldParserFactory create FieldParser.
@@ -885,6 +888,13 @@ func isGeneralAPIComment(comments []string) bool {
 }
 
 func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) {
+	if tagName == "" {
+		// this happens when parsing the @description.markdown attribute
+		// it will be called properly another time with tagName="api"
+		// so we can safely return an empty byte slice here
+		return make([]byte, 0), nil
+	}
+
 	dirEntries, err := os.ReadDir(dirPath)
 	if err != nil {
 		return nil, err
@@ -897,11 +907,12 @@ func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) {
 
 		fileName := entry.Name()
 
-		if !strings.Contains(fileName, ".md") {
-			continue
+		expectedFileName := tagName
+		if !strings.HasSuffix(tagName, ".md") {
+			expectedFileName = tagName + ".md"
 		}
 
-		if strings.Contains(fileName, tagName) {
+		if fileName == expectedFileName {
 			fullPath := filepath.Join(dirPath, fileName)
 
 			commentInfo, err := os.ReadFile(fullPath)
@@ -1016,30 +1027,28 @@ func matchExtension(extensionToMatch string, comments []*ast.Comment) (match boo
 
 // ParseRouterAPIInfo parses router api info for given astFile.
 func (parser *Parser) ParseRouterAPIInfo(fileInfo *AstFileInfo) error {
-DeclsLoop:
-	for _, astDescription := range fileInfo.File.Decls {
-		if (fileInfo.ParseFlag & ParseOperations) == ParseNone {
-			continue
+	if (fileInfo.ParseFlag & ParseOperations) == ParseNone {
+		return nil
+	}
+
+	// parse File.Comments instead of File.Decls.Doc if ParseFuncBody flag set to "true"
+	if parser.ParseFuncBody {
+		for _, astComments := range fileInfo.File.Comments {
+			if astComments.List != nil {
+				if err := parser.parseRouterAPIInfoComment(astComments.List, fileInfo); err != nil {
+					return err
+				}
+			}
 		}
+
+		return nil
+	}
+
+	for _, astDescription := range fileInfo.File.Decls {
 		astDeclaration, ok := astDescription.(*ast.FuncDecl)
 		if ok && astDeclaration.Doc != nil && astDeclaration.Doc.List != nil {
-			if parser.matchTags(astDeclaration.Doc.List) &&
-				matchExtension(parser.parseExtension, astDeclaration.Doc.List) {
-				// for per 'function' comment, create a new 'Operation' object
-				operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir))
-				for _, comment := range astDeclaration.Doc.List {
-					err := operation.ParseComment(comment.Text, fileInfo.File)
-					if err != nil {
-						return fmt.Errorf("ParseComment error in file %s :%+v", fileInfo.Path, err)
-					}
-					if operation.State != "" && operation.State != parser.HostState {
-						continue DeclsLoop
-					}
-				}
-				err := processRouterOperation(parser, operation)
-				if err != nil {
-					return err
-				}
+			if err := parser.parseRouterAPIInfoComment(astDeclaration.Doc.List, fileInfo); err != nil {
+				return err
 			}
 		}
 	}
@@ -1047,6 +1056,28 @@ DeclsLoop:
 	return nil
 }
 
+func (parser *Parser) parseRouterAPIInfoComment(comments []*ast.Comment, fileInfo *AstFileInfo) error {
+	if parser.matchTags(comments) && matchExtension(parser.parseExtension, comments) {
+		// for per 'function' comment, create a new 'Operation' object
+		operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir))
+		for _, comment := range comments {
+			err := operation.ParseComment(comment.Text, fileInfo.File)
+			if err != nil {
+				return fmt.Errorf("ParseComment error in file %s :%+v", fileInfo.Path, err)
+			}
+			if operation.State != "" && operation.State != parser.HostState {
+				return nil
+			}
+		}
+		err := processRouterOperation(parser, operation)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 func refRouteMethodOp(item *spec.PathItem, method string) (op **spec.Operation) {
 	switch method {
 	case http.MethodGet:
@@ -1263,7 +1294,10 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error)
 	}
 
 	if definition.Description == "" {
-		fillDefinitionDescription(definition, typeSpecDef.File, typeSpecDef)
+		err = parser.fillDefinitionDescription(definition, typeSpecDef.File, typeSpecDef)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if len(typeSpecDef.Enums) > 0 {
@@ -1285,8 +1319,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,
 	}
@@ -1307,7 +1347,7 @@ func fullTypeName(parts ...string) string {
 
 // fillDefinitionDescription additionally fills fields in definition (spec.Schema)
 // TODO: If .go file contains many types, it may work for a long time
-func fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) {
+func (parser *Parser) fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) (err error) {
 	if file == nil {
 		return
 	}
@@ -1322,16 +1362,23 @@ func fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpec
 			if !ok || typeSpec != typeSpecDef.TypeSpec {
 				continue
 			}
-
-			definition.Description =
-				extractDeclarationDescription(typeSpec.Doc, typeSpec.Comment, generalDeclaration.Doc)
+			var typeName string
+			if typeSpec.Name != nil {
+				typeName = typeSpec.Name.Name
+			}
+			definition.Description, err =
+				parser.extractDeclarationDescription(typeName, typeSpec.Doc, typeSpec.Comment, generalDeclaration.Doc)
+			if err != nil {
+				return
+			}
 		}
 	}
+	return nil
 }
 
 // extractDeclarationDescription gets first description
 // from attribute descriptionAttr in commentGroups (ast.CommentGroup)
-func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string {
+func (parser *Parser) extractDeclarationDescription(typeName string, commentGroups ...*ast.CommentGroup) (string, error) {
 	var description string
 
 	for _, commentGroup := range commentGroups {
@@ -1346,9 +1393,23 @@ func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string {
 			if len(commentText) == 0 {
 				continue
 			}
-			attribute := FieldsByAnySpace(commentText, 2)[0]
+			fields := FieldsByAnySpace(commentText, 2)
+			attribute := fields[0]
 
-			if strings.ToLower(attribute) != descriptionAttr {
+			if attr := strings.ToLower(attribute); attr == descriptionMarkdownAttr {
+				if len(fields) > 1 {
+					typeName = fields[1]
+				}
+				if typeName == "" {
+					continue
+				}
+				desc, err := getMarkdownForTag(typeName, parser.markdownFileDir)
+				if err != nil {
+					return "", err
+				}
+				// if found markdown description, we will only use the markdown file content
+				return string(desc), nil
+			} else if attr != descriptionAttr {
 				if !isHandlingDescription {
 					continue
 				}
@@ -1361,7 +1422,7 @@ func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string {
 		}
 	}
 
-	return strings.TrimLeft(description, " ")
+	return strings.TrimLeft(description, " "), nil
 }
 
 // parseTypeExpr parses given type expression that corresponds to the type under
diff --git a/types.go b/types.go
index 0076a6b..5f3031e 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


Test patch:
diff --git a/generics_test.go b/generics_test.go
index 033bcf5..fecaa4d 100644
--- a/generics_test.go
+++ b/generics_test.go
@@ -134,6 +134,22 @@ func TestParseGenericsPackageAlias(t *testing.T) {
 	assert.Equal(t, string(expected), string(b))
 }
 
+func TestParseGenericsFunctionScoped(t *testing.T) {
+	t.Parallel()
+
+	searchDir := "testdata/generics_function_scoped"
+	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),
diff --git a/operation_test.go b/operation_test.go
index fb43a44..9ce4698 100644
--- a/operation_test.go
+++ b/operation_test.go
@@ -1322,6 +1322,27 @@ func TestParseParamCommentByID(t *testing.T) {
 	assert.Equal(t, expected, string(b))
 }
 
+func TestParseParamCommentWithMultilineDescriptions(t *testing.T) {
+	t.Parallel()
+
+	comment := `@Param some_id query int true "First line\nSecond line\nThird line"`
+	operation := NewOperation(nil)
+	err := operation.ParseComment(comment, nil)
+
+	assert.NoError(t, err)
+	b, _ := json.MarshalIndent(operation.Parameters, "", "    ")
+	expected := `[
+    {
+        "type": "integer",
+        "description": "First line\nSecond line\nThird line",
+        "name": "some_id",
+        "in": "query",
+        "required": true
+    }
+]`
+	assert.Equal(t, expected, string(b))
+}
+
 func TestParseParamCommentByQueryType(t *testing.T) {
 	t.Parallel()
 
@@ -2448,15 +2469,16 @@ func TestParseObjectSchema(t *testing.T) {
 
 	schema, err := operation.parseObjectSchema("interface{}", nil)
 	assert.NoError(t, err)
-	assert.Equal(t, schema, PrimitiveSchema(OBJECT))
+	assert.Equal(t, schema, &spec.Schema{})
 
 	schema, err = operation.parseObjectSchema("any", nil)
 	assert.NoError(t, err)
-	assert.Equal(t, schema, PrimitiveSchema(OBJECT))
+	assert.Equal(t, schema, &spec.Schema{})
 
 	schema, err = operation.parseObjectSchema("any{data=string}", nil)
 	assert.NoError(t, err)
-	assert.Equal(t, schema, PrimitiveSchema(OBJECT).SetProperty("data", *PrimitiveSchema("string")))
+	assert.Equal(t, schema,
+		(&spec.Schema{}).WithAllOf(spec.Schema{}, *PrimitiveSchema(OBJECT).SetProperty("data", *PrimitiveSchema("string"))))
 
 	schema, err = operation.parseObjectSchema("int", nil)
 	assert.NoError(t, err)
diff --git a/parser_test.go b/parser_test.go
index 0479a75..6b64aec 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -3363,14 +3363,14 @@ func TestParseFunctionScopedStructDefinition(t *testing.T) {
 	src := `
 package main
 
-// @Param request body main.Fun.request true "query params" 
+// @Param request body main.Fun.request true "query params"
 // @Success 200 {object} main.Fun.response
 // @Router /test [post]
 func Fun()  {
 	type request struct {
 		Name string
 	}
-	
+
 	type response struct {
 		Name string
 		Child string
@@ -3389,7 +3389,7 @@ func Fun()  {
 	assert.True(t, ok)
 }
 
-func TestParseFunctionScopedStructRequestResponseJSON(t *testing.T) {
+func TestParseFunctionScopedComplexStructDefinition(t *testing.T) {
 	t.Parallel()
 
 	src := `
@@ -3403,6 +3403,63 @@ func Fun()  {
 		Name string
 	}
 	
+	type grandChild struct {
+		Name string
+	}
+
+	type pointerChild struct {
+		Name string
+	}
+
+	type arrayChild struct {
+		Name string
+	}
+
+	type child struct {
+		GrandChild 		grandChild
+		PointerChild 	*pointerChild
+		ArrayChildren   []arrayChild
+	}
+
+	type response struct {
+		Children 	[]child
+	}
+}
+`
+	p := New()
+	_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
+	_, err := p.packages.ParseTypes()
+	assert.NoError(t, err)
+
+	err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
+	assert.NoError(t, err)
+
+	_, ok := p.swagger.Definitions["main.Fun.response"]
+	assert.True(t, ok)
+	_, ok = p.swagger.Definitions["main.Fun.child"]
+	assert.True(t, ok)
+	_, ok = p.swagger.Definitions["main.Fun.grandChild"]
+	assert.True(t, ok)
+	_, ok = p.swagger.Definitions["main.Fun.pointerChild"]
+	assert.True(t, ok)
+	_, ok = p.swagger.Definitions["main.Fun.arrayChild"]
+	assert.True(t, ok)
+}
+
+func TestParseFunctionScopedStructRequestResponseJSON(t *testing.T) {
+	t.Parallel()
+
+	src := `
+package main
+
+// @Param request body main.Fun.request true "query params"
+// @Success 200 {object} main.Fun.response
+// @Router /test [post]
+func Fun()  {
+	type request struct {
+		Name string
+	}
+
 	type response struct {
 		Name string
 		Child string
@@ -3474,6 +3531,130 @@ func Fun()  {
 	assert.Equal(t, expected, string(b))
 }
 
+func TestParseFunctionScopedComplexStructRequestResponseJSON(t *testing.T) {
+	t.Parallel()
+
+	src := `
+package main
+
+type PublicChild struct {
+	Name string
+}	
+
+// @Param request body main.Fun.request true "query params" 
+// @Success 200 {object} main.Fun.response
+// @Router /test [post]
+func Fun()  {
+	type request struct {
+		Name string
+	}
+	
+	type grandChild struct {
+		Name string
+	}
+
+	type child struct {
+		GrandChild grandChild
+	}
+
+	type response struct {
+		Children 	[]child
+	    PublicChild PublicChild
+	}
+}
+`
+	expected := `{
+    "info": {
+        "contact": {}
+    },
+    "paths": {
+        "/test": {
+            "post": {
+                "parameters": [
+                    {
+                        "description": "query params",
+                        "name": "request",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/main.Fun.request"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/main.Fun.response"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "main.Fun.child": {
+            "type": "object",
+            "properties": {
+                "grandChild": {
+                    "$ref": "#/definitions/main.Fun.grandChild"
+                }
+            }
+        },
+        "main.Fun.grandChild": {
+            "type": "object",
+            "properties": {
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "main.Fun.request": {
+            "type": "object",
+            "properties": {
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "main.Fun.response": {
+            "type": "object",
+            "properties": {
+                "children": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/main.Fun.child"
+                    }
+                },
+                "publicChild": {
+                    "$ref": "#/definitions/main.PublicChild"
+                }
+            }
+        },
+        "main.PublicChild": {
+            "type": "object",
+            "properties": {
+                "name": {
+                    "type": "string"
+                }
+            }
+        }
+    }
+}`
+
+	p := New()
+	_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
+
+	_, err := p.packages.ParseTypes()
+	assert.NoError(t, err)
+
+	err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
+	assert.NoError(t, err)
+
+	b, _ := json.MarshalIndent(p.swagger, "", "    ")
+	assert.Equal(t, expected, string(b))
+}
+
 func TestPackagesDefinitions_CollectAstFileInit(t *testing.T) {
 	t.Parallel()
 
@@ -4123,3 +4304,85 @@ func TestParser_skipPackageByPrefix(t *testing.T) {
 	assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/cmd"))
 	assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/gen"))
 }
+
+func TestParser_ParseRouterApiInFuncBody(t *testing.T) {
+	t.Parallel()
+
+	src := `
+package test
+
+func Test(){
+    // @Router /api/{id} [get]
+    _ = func() {
+	}
+}
+`
+	p := New()
+	p.ParseFuncBody = true
+	err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
+	assert.NoError(t, err)
+
+	err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
+	assert.NoError(t, err)
+
+	ps := p.swagger.Paths.Paths
+
+	val, ok := ps["/api/{id}"]
+
+	assert.True(t, ok)
+	assert.NotNil(t, val.Get)
+}
+
+func TestParser_ParseRouterApiInfoInAndOutFuncBody(t *testing.T) {
+	t.Parallel()
+
+	src := `
+package test
+
+// @Router /api/outside [get]
+func otherRoute(){
+}
+
+func Test(){
+    // @Router /api/inside [get]
+    _ = func() {
+	}
+}
+`
+	p := New()
+	p.ParseFuncBody = true
+	err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
+	assert.NoError(t, err)
+
+	err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
+	assert.NoError(t, err)
+
+	ps := p.swagger.Paths.Paths
+
+	val1, ok := ps["/api/outside"]
+	assert.True(t, ok)
+	assert.NotNil(t, val1.Get)
+
+	val2, ok := ps["/api/inside"]
+	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 0000000..c80a0b9
--- /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 0000000..f6542ca
--- /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 0000000..795993d
--- /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 0000000..e340b7d
--- /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/testdata/generics_function_scoped/api/api.go b/testdata/generics_function_scoped/api/api.go
new file mode 100644
index 0000000..904bce3
--- /dev/null
+++ b/testdata/generics_function_scoped/api/api.go
@@ -0,0 +1,75 @@
+package api
+
+import (
+	"net/http"
+
+	"github.com/swaggo/swag/testdata/generics_function_scoped/types"
+)
+
+// @Summary Generic Response
+// @Produce  json
+// @Success 200 {object} types.GenericResponse[api.GetGeneric.User]
+// @Success 201 {object} types.GenericResponse[api.GetGeneric.Post]
+// @Router / [get]
+func GetGeneric(w http.ResponseWriter, r *http.Request) {
+	type User struct {
+		Username int    `json:"username"`
+		Email    string `json:"email"`
+	}
+	type Post struct {
+		Slug  int    `json:"slug"`
+		Title string `json:"title"`
+	}
+
+	_ = types.GenericResponse[any]{}
+}
+
+// @Summary Generic Response With Custom Type Names
+// @Produce  json
+// @Success 200 {object} types.GenericResponse[api.GetGenericRenamed.User]
+// @Success 201 {object} types.GenericResponse[api.GetGenericRenamed.Post]
+// @Router /renamed [get]
+func GetGenericRenamed(w http.ResponseWriter, r *http.Request) {
+	type User struct {
+		Username int    `json:"username"`
+		Email    string `json:"email"`
+	} // @Name RenamedUserData
+	type Post struct {
+		Slug  int    `json:"slug"`
+		Title string `json:"title"`
+	} // @Name RenamedPostData
+
+	_ = types.GenericResponse[any]{}
+}
+
+// @Summary Multiple Generic Response
+// @Produce  json
+// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructA, api.GetGenericMulti.MyStructB]
+// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructB, api.GetGenericMulti.MyStructA]
+// @Router /multi [get]
+func GetGenericMulti(w http.ResponseWriter, r *http.Request) {
+	type MyStructA struct {
+		SomeFieldA string `json:"some_field_a"`
+	}
+	type MyStructB struct {
+		SomeFieldB string `json:"some_field_b"`
+	}
+
+	_ = types.GenericMultiResponse[any, any]{}
+}
+
+// @Summary Multiple Generic Response With Custom Type Names
+// @Produce  json
+// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructA, api.GetGenericMultiRenamed.MyStructB]
+// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructB, api.GetGenericMultiRenamed.MyStructA]
+// @Router /multi-renamed [get]
+func GetGenericMultiRenamed(w http.ResponseWriter, r *http.Request) {
+	type MyStructA struct {
+		SomeFieldA string `json:"some_field_a"`
+	} // @Name NameForMyStructA
+	type MyStructB struct {
+		SomeFieldB string `json:"some_field_b"`
+	} // @Name NameForMyStructB
+
+	_ = types.GenericMultiResponse[any, any]{}
+}
diff --git a/testdata/generics_function_scoped/expected.json b/testdata/generics_function_scoped/expected.json
new file mode 100644
index 0000000..3fb99fa
--- /dev/null
+++ b/testdata/generics_function_scoped/expected.json
@@ -0,0 +1,279 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "description": "This is a sample server.",
+        "title": "Swagger Example API",
+        "contact": {},
+        "version": "1.0"
+    },
+    "host": "localhost:8080",
+    "basePath": "/api",
+    "paths": {
+        "/": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Generic Response",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-api_GetGeneric_User"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-api_GetGeneric_Post"
+                        }
+                    }
+                }
+            }
+        },
+        "/multi": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Multiple Generic Response",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA"
+                        }
+                    }
+                }
+            }
+        },
+        "/multi-renamed": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Multiple Generic Response With Custom Type Names",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructA-NameForMyStructB"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructB-NameForMyStructA"
+                        }
+                    }
+                }
+            }
+        },
+        "/renamed": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Generic Response With Custom Type Names",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-RenamedUserData"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-RenamedPostData"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "NameForMyStructA": {
+            "type": "object",
+            "properties": {
+                "some_field_a": {
+                    "type": "string"
+                }
+            }
+        },
+        "NameForMyStructB": {
+            "type": "object",
+            "properties": {
+                "some_field_b": {
+                    "type": "string"
+                }
+            }
+        },
+        "RenamedPostData": {
+            "type": "object",
+            "properties": {
+                "slug": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                }
+            }
+        },
+        "RenamedUserData": {
+            "type": "object",
+            "properties": {
+                "email": {
+                    "type": "string"
+                },
+                "username": {
+                    "type": "integer"
+                }
+            }
+        },
+        "api.GetGeneric.Post": {
+            "type": "object",
+            "properties": {
+                "slug": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                }
+            }
+        },
+        "api.GetGeneric.User": {
+            "type": "object",
+            "properties": {
+                "email": {
+                    "type": "string"
+                },
+                "username": {
+                    "type": "integer"
+                }
+            }
+        },
+        "api.GetGenericMulti.MyStructA": {
+            "type": "object",
+            "properties": {
+                "some_field_a": {
+                    "type": "string"
+                }
+            }
+        },
+        "api.GetGenericMulti.MyStructB": {
+            "type": "object",
+            "properties": {
+                "some_field_b": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-NameForMyStructA-NameForMyStructB": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/NameForMyStructA"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/NameForMyStructB"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-NameForMyStructB-NameForMyStructA": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/NameForMyStructB"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/NameForMyStructA"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructA"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructB"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructB"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructA"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-RenamedPostData": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/RenamedPostData"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-RenamedUserData": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/RenamedUserData"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-api_GetGeneric_Post": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/api.GetGeneric.Post"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-api_GetGeneric_User": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/api.GetGeneric.User"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/testdata/generics_function_scoped/main.go b/testdata/generics_function_scoped/main.go
new file mode 100644
index 0000000..7dd44c3
--- /dev/null
+++ b/testdata/generics_function_scoped/main.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/swaggo/swag/testdata/generics_function_scoped/api"
+)
+
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server.
+// @host localhost:8080
+// @basePath /api
+func main() {
+	http.HandleFunc("/", api.GetGeneric)
+	http.HandleFunc("/renamed", api.GetGenericRenamed)
+	http.HandleFunc("/multi", api.GetGenericMulti)
+	http.HandleFunc("/multi-renamed", api.GetGenericMulti)
+	http.ListenAndServe(":8080", nil)
+}
diff --git a/testdata/generics_function_scoped/types/response.go b/testdata/generics_function_scoped/types/response.go
new file mode 100644
index 0000000..d12e532
--- /dev/null
+++ b/testdata/generics_function_scoped/types/response.go
@@ -0,0 +1,12 @@
+package types
+
+type GenericResponse[T any] struct {
+	Status string `json:"status"`
+	Data   T      `json:"data"`
+}
+
+type GenericMultiResponse[T any, X any] struct {
+	Status string `json:"status"`
+	DataT  T      `json:"data_t"`
+	DataX  X      `json:"data_x"`
+}
diff --git a/testdata/simple/api/api.go b/testdata/simple/api/api.go
index 85a7fa4..3896ef9 100644
--- a/testdata/simple/api/api.go
+++ b/testdata/simple/api/api.go
@@ -138,3 +138,20 @@ func GetPet6FunctionScopedResponse() {
 		Name string
 	}
 }
+
+// @Success 200 {object}  api.GetPet6FunctionScopedComplexResponse.response "ok"
+// @Router /GetPet6FunctionScopedComplexResponse [get]
+func GetPet6FunctionScopedComplexResponse() {
+	type pet struct {
+		Name string
+	}
+
+	type pointerPet struct {
+		Name string
+	}
+
+	type response struct {
+		Pets       []pet
+		PointerPet *pointerPet
+	}
+}
diff --git a/testdata/simple/expected.json b/testdata/simple/expected.json
index b3f6426..e1bcadb 100644
--- a/testdata/simple/expected.json
+++ b/testdata/simple/expected.json
@@ -101,6 +101,18 @@
         }
       }
     },
+    "/GetPet6FunctionScopedComplexResponse": {
+      "get": {
+        "responses": {
+          "200": {
+            "description": "ok",
+            "schema": {
+              "$ref": "#/definitions/api.GetPet6FunctionScopedComplexResponse.response"
+            }
+          }
+        }
+      }
+    },
     "/GetPet6FunctionScopedResponse": {
       "get": {
         "responses": {
@@ -401,6 +413,36 @@
     }
   },
   "definitions": {
+    "api.GetPet6FunctionScopedComplexResponse.pet": {
+      "type": "object",
+      "properties": {
+        "Name": {
+          "type": "string"
+        }
+      }
+    },
+    "api.GetPet6FunctionScopedComplexResponse.pointerPet": {
+      "type": "object",
+      "properties": {
+        "Name": {
+          "type": "string"
+        }
+      }
+    },
+    "api.GetPet6FunctionScopedComplexResponse.response": {
+      "type": "object",
+      "properties": {
+        "Pets": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/api.GetPet6FunctionScopedComplexResponse.pet"
+          }
+        },
+        "PointerPet": {
+          "$ref": "#/definitions/api.GetPet6FunctionScopedComplexResponse.pointerPet"
+        }
+      }
+    },
     "api.GetPet6FunctionScopedResponse.response": {
       "type": "object",
       "properties": {