diff --git a/go.mod b/go.mod index 471b67f..86423f4 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 - github.com/swaggo/swag v1.16.3 + github.com/swaggo/swag v1.16.4 go.mongodb.org/mongo-driver v1.17.1 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.28.0 diff --git a/go.sum b/go.sum index 3c530c5..0c4ae6a 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= -github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= -github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= diff --git a/vendor/github.com/swaggo/swag/Dockerfile b/vendor/github.com/swaggo/swag/Dockerfile index 410fe31..5ea9134 100644 --- a/vendor/github.com/swaggo/swag/Dockerfile +++ b/vendor/github.com/swaggo/swag/Dockerfile @@ -1,7 +1,7 @@ # Dockerfile References: https://docs.docker.com/engine/reference/builder/ # Start from the latest golang base image -FROM golang:1.20-alpine as builder +FROM --platform=$BUILDPLATFORM golang:1.21-alpine as builder # Set the Current Working Directory inside the container WORKDIR /app @@ -15,12 +15,18 @@ RUN go mod download # Copy the source from the current directory to the Working Directory inside the container COPY . . +# Configure go compiler target platform +ARG TARGETOS +ARG TARGETARCH +ENV GOARCH=$TARGETARCH \ + GOOS=$TARGETOS + # Build the Go app RUN CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o swag cmd/swag/main.go ######## Start a new stage from scratch ####### -FROM scratch +FROM --platform=$TARGETPLATFORM scratch WORKDIR /code/ diff --git a/vendor/github.com/swaggo/swag/Makefile b/vendor/github.com/swaggo/swag/Makefile index 0d8175d..85dc362 100644 --- a/vendor/github.com/swaggo/swag/Makefile +++ b/vendor/github.com/swaggo/swag/Makefile @@ -17,8 +17,6 @@ BINARY_NAME:=swag PACKAGES:=$(shell $(GOLIST) github.com/swaggo/swag github.com/swaggo/swag/cmd/swag github.com/swaggo/swag/gen github.com/swaggo/swag/format) GOFILES:=$(shell find . -name "*.go" -type f) -export GO111MODULE := on - all: test build .PHONY: build @@ -57,17 +55,8 @@ clean: deps: $(GOMODTIDY) -.PHONY: devel-deps -devel-deps: - GO111MODULE=off $(GOGET) -v -u \ - golang.org/x/lint/golint - -.PHONY: lint -lint: devel-deps - for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; - .PHONY: vet -vet: deps devel-deps +vet: deps $(GOVET) $(PACKAGES) .PHONY: fmt diff --git a/vendor/github.com/swaggo/swag/README.md b/vendor/github.com/swaggo/swag/README.md index 054e36e..7504f6e 100644 --- a/vendor/github.com/swaggo/swag/README.md +++ b/vendor/github.com/swaggo/swag/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) @@ -55,7 +56,7 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie ```sh go install github.com/swaggo/swag/cmd/swag@latest ``` -To build from source you need [Go](https://golang.org/dl/) (1.18 or newer). +To build from source you need [Go](https://golang.org/dl/) (1.19 or newer). Alternatively you can run the docker image: ```sh @@ -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) @@ -113,9 +115,10 @@ OPTIONS: --overridesFile value File to read global type overrides from. (default: ".swaggo") --parseGoList Parse dependency via 'go list' (default: true) --tags value, -t value A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded - --templateDelims value, --td value Provide custom delimeters for Go template generation. The format is leftDelim,rightDelim. For example: "[[,]]" + --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 @@ -946,6 +956,17 @@ swag init -g http/api.go -td "[[,]]" ``` The new delimiter is a string with the format "``,``". +### Parse Internal and Dependency Packages + +If the struct is defined in a dependency package, use `--parseDependency`. + +If the struct is defined in your main project, use `--parseInternal`. + +if you want to include both internal and from dependencies use both flags +``` +swag init --parseDependency --parseInternal +``` + ## About the Project This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en). ## Contributors diff --git a/vendor/github.com/swaggo/swag/README_pt.md b/vendor/github.com/swaggo/swag/README_pt.md index 7f95066..060b2c9 100644 --- a/vendor/github.com/swaggo/swag/README_pt.md +++ b/vendor/github.com/swaggo/swag/README_pt.md @@ -54,7 +54,7 @@ Swag converte anotações Go para Documentação Swagger 2.0. Criámos uma varie ```sh go install github.com/swaggo/swag/cmd/swag@latest ``` -Para construir a partir da fonte é necessário [Go](https://golang.org/dl/) (1.18 ou mais recente). +Para construir a partir da fonte é necessário [Go](https://golang.org/dl/) (1.19 ou mais recente). Ou descarregar um binário pré-compilado a partir da [página de lançamento](https://github.com/swaggo/swag/releases). diff --git a/vendor/github.com/swaggo/swag/README_zh-CN.md b/vendor/github.com/swaggo/swag/README_zh-CN.md index 87d600b..24a219c 100644 --- a/vendor/github.com/swaggo/swag/README_zh-CN.md +++ b/vendor/github.com/swaggo/swag/README_zh-CN.md @@ -50,7 +50,7 @@ Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framewo go install github.com/swaggo/swag/cmd/swag@latest ``` -从源码开始构建的话,需要有Go环境(1.18及以上版本)。 +从源码开始构建的话,需要有Go环境(1.19及以上版本)。 或者从github的release页面下载预编译好的二进制文件。 @@ -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/vendor/github.com/swaggo/swag/field_parser.go b/vendor/github.com/swaggo/swag/field_parser.go index 9b24e78..de8945d 100644 --- a/vendor/github.com/swaggo/swag/field_parser.go +++ b/vendor/github.com/swaggo/swag/field_parser.go @@ -65,35 +65,38 @@ func (ps *tagBaseFieldParser) ShouldSkip() bool { return false } -func (ps *tagBaseFieldParser) FieldName() (string, error) { - var name string +func (ps *tagBaseFieldParser) FieldNames() ([]string, error) { + if len(ps.field.Names) <= 1 { + // if embedded but with a json/form name ?? + if ps.field.Tag != nil { + // json:"tag,hoge" + name := strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0]) + if name != "" { + return []string{name}, nil + } - if ps.field.Tag != nil { - // json:"tag,hoge" - name = strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0]) - if name != "" { - return name, nil + // use "form" tag over json tag + name = ps.FormName() + if name != "" { + return []string{name}, nil + } } - - // use "form" tag over json tag - name = ps.FormName() - if name != "" { - return name, nil + if len(ps.field.Names) == 0 { + return nil, nil } } - - if ps.field.Names == nil { - return "", nil - } - - switch ps.p.PropNamingStrategy { - case SnakeCase: - return toSnakeCase(ps.field.Names[0].Name), nil - case PascalCase: - return ps.field.Names[0].Name, nil - default: - return toLowerCamelCase(ps.field.Names[0].Name), nil + var names = make([]string, 0, len(ps.field.Names)) + for _, name := range ps.field.Names { + switch ps.p.PropNamingStrategy { + case SnakeCase: + names = append(names, toSnakeCase(name.Name)) + case PascalCase: + names = append(names, name.Name) + default: + names = append(names, toLowerCamelCase(name.Name)) + } } + return names, nil } func (ps *tagBaseFieldParser) firstTagValue(tag string) string { @@ -169,6 +172,7 @@ func (ps *tagBaseFieldParser) CustomSchema() (*spec.Schema, error) { } type structField struct { + title string schemaType string arrayType string formatType string @@ -274,6 +278,7 @@ func (ps *tagBaseFieldParser) complementSchema(schema *spec.Schema, types []stri field := &structField{ schemaType: types[0], formatType: ps.tag.Get(formatTag), + title: ps.tag.Get(titleTag), } if len(types) > 1 && (types[0] == ARRAY || types[0] == OBJECT) { @@ -414,6 +419,7 @@ func (ps *tagBaseFieldParser) complementSchema(schema *spec.Schema, types []stri if field.schemaType != ARRAY { schema.Format = field.formatType } + schema.Title = field.title extensionsTagValue := ps.tag.Get(extensionsTag) if extensionsTagValue != "" { diff --git a/vendor/github.com/swaggo/swag/generics.go b/vendor/github.com/swaggo/swag/generics.go index 07344bb..80e93a9 100644 --- a/vendor/github.com/swaggo/swag/generics.go +++ b/vendor/github.com/swaggo/swag/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/vendor/github.com/swaggo/swag/operation.go b/vendor/github.com/swaggo/swag/operation.go index 169510f..ba3a05b 100644 --- a/vendor/github.com/swaggo/swag/operation.go +++ b/vendor/github.com/swaggo/swag/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) @@ -334,6 +334,9 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F if len(itemSchema.Type) == 0 { itemSchema = operation.parser.getUnderlyingSchema(prop.Items.Schema) } + if itemSchema == nil { + continue + } if len(itemSchema.Type) == 0 { continue } @@ -408,6 +411,7 @@ const ( exampleTag = "example" schemaExampleTag = "schemaExample" formatTag = "format" + titleTag = "title" validateTag = "validate" minimumTag = "minimum" maximumTag = "maximum" @@ -703,7 +707,7 @@ func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) e return nil } -var routerPattern = regexp.MustCompile(`^(/[\w./\-{}+:$]*)[[:blank:]]+\[(\w+)]`) +var routerPattern = regexp.MustCompile(`^(/[\w./\-{}\(\)+:$]*)[[:blank:]]+\[(\w+)]`) // ParseRouterComment parses comment for given `router` comment string. func (operation *Operation) ParseRouterComment(commentLine string, deprecated bool) error { @@ -837,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/vendor/github.com/swaggo/swag/packages.go b/vendor/github.com/swaggo/swag/packages.go index 69a1b05..2032414 100644 --- a/vendor/github.com/swaggo/swag/packages.go +++ b/vendor/github.com/swaggo/swag/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") || 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/vendor/github.com/swaggo/swag/parser.go b/vendor/github.com/swaggo/swag/parser.go index 604f827..cd2a46c 100644 --- a/vendor/github.com/swaggo/swag/parser.go +++ b/vendor/github.com/swaggo/swag/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. @@ -187,7 +190,7 @@ type FieldParserFactory func(ps *Parser, field *ast.Field) FieldParser // FieldParser parse struct field. type FieldParser interface { ShouldSkip() bool - FieldName() (string, error) + FieldNames() ([]string, error) FormName() string HeaderName() string PathName() string @@ -511,7 +514,7 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error { func parseGeneralAPIInfo(parser *Parser, comments []string) error { previousAttribute := "" - + var tag *spec.Tag // parsing classic meta data model for line := 0; line < len(comments); line++ { commentLine := comments[line] @@ -572,42 +575,43 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { case "@schemes": parser.swagger.Schemes = strings.Split(value, " ") case "@tag.name": - parser.swagger.Tags = append(parser.swagger.Tags, spec.Tag{ - TagProps: spec.TagProps{ - Name: value, - }, - }) + if parser.matchTag(value) { + parser.swagger.Tags = append(parser.swagger.Tags, spec.Tag{ + TagProps: spec.TagProps{ + Name: value, + }, + }) + tag = &parser.swagger.Tags[len(parser.swagger.Tags)-1] + } else { + tag = nil + } case "@tag.description": - tag := parser.swagger.Tags[len(parser.swagger.Tags)-1] - tag.TagProps.Description = value - replaceLastTag(parser.swagger.Tags, tag) + if tag != nil { + tag.TagProps.Description = value + } case "@tag.description.markdown": - tag := parser.swagger.Tags[len(parser.swagger.Tags)-1] + if tag != nil { + commentInfo, err := getMarkdownForTag(tag.TagProps.Name, parser.markdownFileDir) + if err != nil { + return err + } - commentInfo, err := getMarkdownForTag(tag.TagProps.Name, parser.markdownFileDir) - if err != nil { - return err + tag.TagProps.Description = string(commentInfo) } - - tag.TagProps.Description = string(commentInfo) - replaceLastTag(parser.swagger.Tags, tag) case "@tag.docs.url": - tag := parser.swagger.Tags[len(parser.swagger.Tags)-1] - tag.TagProps.ExternalDocs = &spec.ExternalDocumentation{ - URL: value, - Description: "", + if tag != nil { + tag.TagProps.ExternalDocs = &spec.ExternalDocumentation{ + URL: value, + } } - - replaceLastTag(parser.swagger.Tags, tag) case "@tag.docs.description": - tag := parser.swagger.Tags[len(parser.swagger.Tags)-1] - if tag.TagProps.ExternalDocs == nil { - return fmt.Errorf("%s needs to come after a @tags.docs.url", attribute) - } - - tag.TagProps.ExternalDocs.Description = value - replaceLastTag(parser.swagger.Tags, tag) + if tag != nil { + if tag.TagProps.ExternalDocs == nil { + return fmt.Errorf("%s needs to come after a @tags.docs.url", attribute) + } + tag.TagProps.ExternalDocs.Description = value + } case secBasicAttr, secAPIKeyAttr, secApplicationAttr, secImplicitAttr, secPasswordAttr, secAccessCodeAttr: scheme, err := parseSecAttributes(attribute, comments, &line) if err != nil { @@ -773,7 +777,10 @@ loopline: // Not mandatory field if securityAttr == descriptionAttr { - description = value + if description != "" { + description += "\n" + } + description += value } // next securityDefinitions @@ -881,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 @@ -893,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) @@ -943,6 +958,27 @@ func getTagsFromComment(comment string) (tags []string) { } +func (parser *Parser) matchTag(tag string) bool { + if len(parser.tags) == 0 { + return true + } + + if _, has := parser.tags["!"+tag]; has { + return false + } + if _, has := parser.tags[tag]; has { + return true + } + + // If all tags are negation then we should return true + for key := range parser.tags { + if key[0] != '!' { + return false + } + } + return true +} + func (parser *Parser) matchTags(comments []*ast.Comment) (match bool) { if len(parser.tags) == 0 { return true @@ -991,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 } } } @@ -1022,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: @@ -1238,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 { @@ -1260,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, } @@ -1282,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 } @@ -1297,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 { @@ -1321,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 } @@ -1336,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 @@ -1441,20 +1527,20 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st return nil, nil, nil } - fieldName, err := ps.FieldName() + fieldNames, err := ps.FieldNames() if err != nil { return nil, nil, err } - if fieldName == "" { + if len(fieldNames) == 0 { typeName, err := getFieldType(file, field.Type, nil) if err != nil { - return nil, nil, fmt.Errorf("%s: %w", fieldName, err) + return nil, nil, err } schema, err := parser.getTypeSchema(typeName, file, false) if err != nil { - return nil, nil, fmt.Errorf("%s: %w", fieldName, err) + return nil, nil, err } if len(schema.Type) > 0 && schema.Type[0] == OBJECT { @@ -1476,7 +1562,7 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st schema, err := ps.CustomSchema() if err != nil { - return nil, nil, fmt.Errorf("%s: %w", fieldName, err) + return nil, nil, fmt.Errorf("%v: %w", fieldNames, err) } if schema == nil { @@ -1490,24 +1576,24 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st } if err != nil { - return nil, nil, fmt.Errorf("%s: %w", fieldName, err) + return nil, nil, fmt.Errorf("%v: %w", fieldNames, err) } } err = ps.ComplementSchema(schema) if err != nil { - return nil, nil, fmt.Errorf("%s: %w", fieldName, err) + return nil, nil, fmt.Errorf("%v: %w", fieldNames, err) } var tagRequired []string required, err := ps.IsRequired() if err != nil { - return nil, nil, fmt.Errorf("%s: %w", fieldName, err) + return nil, nil, fmt.Errorf("%v: %w", fieldNames, err) } if required { - tagRequired = append(tagRequired, fieldName) + tagRequired = append(tagRequired, fieldNames...) } if schema.Extensions == nil { @@ -1522,8 +1608,11 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st if pathName := ps.PathName(); len(pathName) > 0 { schema.Extensions["path"] = pathName } - - return map[string]spec.Schema{fieldName: *schema}, tagRequired, nil + fields := make(map[string]spec.Schema) + for _, name := range fieldNames { + fields[name] = *schema + } + return fields, tagRequired, nil } func getFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) { @@ -1609,10 +1698,6 @@ func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string return []string{ANY} } -func replaceLastTag(slice []spec.Tag, element spec.Tag) { - slice = append(slice[:len(slice)-1], element) -} - // defineTypeOfExample example value define the type (object and array unsupported). func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{}, error) { switch schemaType { diff --git a/vendor/github.com/swaggo/swag/types.go b/vendor/github.com/swaggo/swag/types.go index 0076a6b..5f3031e 100644 --- a/vendor/github.com/swaggo/swag/types.go +++ b/vendor/github.com/swaggo/swag/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 diff --git a/vendor/github.com/swaggo/swag/version.go b/vendor/github.com/swaggo/swag/version.go index ff2810e..cf52320 100644 --- a/vendor/github.com/swaggo/swag/version.go +++ b/vendor/github.com/swaggo/swag/version.go @@ -1,4 +1,4 @@ package swag // Version of swag. -const Version = "v1.16.3" +const Version = "v1.16.4" diff --git a/vendor/modules.txt b/vendor/modules.txt index 9c1c1d1..485c862 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -233,7 +233,7 @@ github.com/swaggo/files # github.com/swaggo/gin-swagger v1.6.0 ## explicit; go 1.17 github.com/swaggo/gin-swagger -# github.com/swaggo/swag v1.16.3 +# github.com/swaggo/swag v1.16.4 ## explicit; go 1.18 github.com/swaggo/swag # github.com/twitchyliquid64/golang-asm v0.15.1