Update dependencies

This commit is contained in:
Ingo Oppermann
2024-02-29 14:50:38 +01:00
parent 32a7916359
commit e8ca91d214
222 changed files with 14005 additions and 4625 deletions

View File

@@ -1,7 +1,7 @@
# Dockerfile References: https://docs.docker.com/engine/reference/builder/
# Start from the latest golang base image
FROM golang:1.18.3-alpine as builder
FROM golang:1.20-alpine as builder
# Set the Current Working Directory inside the container
WORKDIR /app
@@ -22,7 +22,9 @@ RUN CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o swag cmd/swag/
######## Start a new stage from scratch #######
FROM scratch
WORKDIR /root/
WORKDIR /code/
# Copy the Pre-built binary file from the previous stage
COPY --from=builder /app/swag .
COPY --from=builder /app/swag /bin/swag
ENTRYPOINT ["/bin/swag"]

View File

@@ -6,6 +6,7 @@ GOBUILD:=$(GOCMD) build
GOINSTALL:=$(GOCMD) install
GOCLEAN:=$(GOCMD) clean
GOTEST:=$(GOCMD) test
GOMODTIDY:=$(GOCMD) mod tidy
GOGET:=$(GOCMD) get
GOLIST:=$(GOCMD) list
GOVET:=$(GOCMD) vet
@@ -54,13 +55,7 @@ clean:
.PHONY: deps
deps:
$(GOGET) github.com/swaggo/cli
$(GOGET) sigs.k8s.io/yaml
$(GOGET) github.com/KyleBanks/depth
$(GOGET) github.com/go-openapi/jsonreference
$(GOGET) github.com/go-openapi/spec
$(GOGET) github.com/stretchr/testify/assert
$(GOGET) golang.org/x/tools/go/loader
$(GOMODTIDY)
.PHONY: devel-deps
devel-deps:

View File

@@ -44,17 +44,23 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie
- [How to use security annotations](#how-to-use-security-annotations)
- [Add a description for enum items](#add-a-description-for-enum-items)
- [Generate only specific docs file types](#generate-only-specific-docs-file-types)
- [How to use Go generic types](#how-to-use-generics)
- [About the Project](#about-the-project)
## Getting started
1. Add comments to your API source code, See [Declarative Comments Format](#declarative-comments-format).
2. Download swag by using:
2. Install swag by using:
```sh
go install github.com/swaggo/swag/cmd/swag@latest
```
To build from source you need [Go](https://golang.org/dl/) (1.17 or newer).
To build from source you need [Go](https://golang.org/dl/) (1.18 or newer).
Alternatively you can run the docker image:
```sh
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest
```
Or download a pre-compiled binary from the [release page](https://github.com/swaggo/swag/releases).
@@ -64,6 +70,9 @@ swag init
```
Make sure to import the generated `docs/docs.go` so that your specific configuration gets `init`'ed. If your General API annotations do not live in `main.go`, you can let swag know with `-g` flag.
```go
import _ "example-module-name/docs"
```
```sh
swag init -g http/api.go
```
@@ -106,6 +115,7 @@ OPTIONS:
--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: "[[,]]"
--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
--help, -h show help (default: false)
```
@@ -142,6 +152,7 @@ OPTIONS:
Find the example source code [here](https://github.com/swaggo/swag/tree/master/example/celler).
Finish the steps in [Getting started](#getting-started)
1. After using `swag init` to generate Swagger 2.0 docs, import the following packages:
```go
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
@@ -414,25 +425,26 @@ When a short string in your documentation is insufficient, or you need images, c
[celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller)
| annotation | description |
|-------------|----------------------------------------------------------------------------------------------------------------------------|
| description | A verbose explanation of the operation behavior. |
| description.markdown | A short description of the application. The description will be read from a file. E.g. `@description.markdown details` will load `details.md`| // @description.file endpoint.description.markdown |
| id | A unique string used to identify the operation. Must be unique among all API operations. |
| tags | A list of tags to each API operation that separated by commas. |
| summary | A short summary of what the operation does. |
| accept | A list of MIME types the APIs can consume. Note that Accept only affects operations with a request body, such as POST, PUT and PATCH. Value MUST be as described under [Mime Types](#mime-types). |
| produce | A list of MIME types the APIs can produce. Value MUST be as described under [Mime Types](#mime-types). |
| param | Parameters that separated by spaces. `param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
| security | [Security](#security) to each API operation. |
| success | Success response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
| failure | Failure response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
| response | As same as `success` and `failure` |
| header | Header in response that separated by spaces. `return code`,`{param type}`,`data type`,`comment` |
| router | Path definition that separated by spaces. `path`,`[httpMethod]` |
| x-name | The extension key, must be start by x- and take only json value. |
| x-codeSample | Optional Markdown usage. take `file` as parameter. This will then search for a file named like the summary in the given folder. |
| deprecated | Mark endpoint as deprecated. |
| annotation | description |
|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| description | A verbose explanation of the operation behavior. |
| description.markdown | A short description of the application. The description will be read from a file. E.g. `@description.markdown details` will load `details.md` | // @description.file endpoint.description.markdown |
| id | A unique string used to identify the operation. Must be unique among all API operations. |
| tags | A list of tags to each API operation that separated by commas. |
| summary | A short summary of what the operation does. |
| accept | A list of MIME types the APIs can consume. Note that Accept only affects operations with a request body, such as POST, PUT and PATCH. Value MUST be as described under [Mime Types](#mime-types). |
| produce | A list of MIME types the APIs can produce. Value MUST be as described under [Mime Types](#mime-types). |
| param | Parameters that separated by spaces. `param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
| security | [Security](#security) to each API operation. |
| success | Success response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
| failure | Failure response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
| response | As same as `success` and `failure` |
| header | Header in response that separated by spaces. `return code`,`{param type}`,`data type`,`comment` |
| router | Path definition that separated by spaces. `path`,`[httpMethod]` |
| deprecatedrouter | As same as router, but deprecated. |
| x-name | The extension key, must be start by x- and take only json value. |
| x-codeSample | Optional Markdown usage. take `file` as parameter. This will then search for a file named like the summary in the given folder. |
| deprecated | Mark endpoint as deprecated. |
@@ -909,6 +921,19 @@ By default `swag` command generates Swagger specification in three different fil
If you would like to limit a set of file types which should be generated you can use `--outputTypes` (short `-ot`) flag. Default value is `go,json,yaml` - output types separated with comma. To limit output only to `go` and `yaml` files, you would write `go,yaml`. With complete command that would be `swag init --outputTypes go,yaml`.
### How to use Generics
```go
// @Success 200 {object} web.GenericNestedResponse[types.Post]
// @Success 204 {object} web.GenericNestedResponse[types.Post, Types.AnotherOne]
// @Success 201 {object} web.GenericNestedResponse[web.GenericInnerType[types.Post]]
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
and other examples.
### Change the default Go Template action delimiters
[#980](https://github.com/swaggo/swag/issues/980)
[#1177](https://github.com/swaggo/swag/issues/1177)

View File

@@ -43,6 +43,7 @@ Swag converte anotações Go para Documentação Swagger 2.0. Criámos uma varie
- [Como utilizar as anotações de segurança](#como-utilizar-as-anotações-de-segurança)
- [Adicionar uma descrição para enumerar artigos](#add-a-description-for-enum-items)
- [Gerar apenas tipos de ficheiros de documentos específicos](#generate-only-specific-docs-file-file-types)
- [Como usar tipos genéricos](#como-usar-tipos-genéricos)
- [Sobre o projecto](#sobre-o-projecto)
## Começando
@@ -53,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.17 ou mais recente).
Para construir a partir da fonte é necessário [Go](https://golang.org/dl/) (1.18 ou mais recente).
Ou descarregar um binário pré-compilado a partir da [página de lançamento](https://github.com/swaggo/swag/releases).
@@ -905,6 +906,18 @@ Por defeito, o comando `swag` gera especificação Swagger em três tipos difere
Se desejar limitar um conjunto de tipos de ficheiros que devem ser gerados pode utilizar a bandeira `--outputTypes` (short `-ot`). O valor por defeito é `go,json,yaml` - tipos de saída separados por vírgula. Para limitar a saída apenas a ficheiros `go` e `yaml`, escrever-se-ia `go,yaml'. Com comando completo que seria `swag init --outputTypes go,yaml`.
### Como usar tipos genéricos
```go
// @Success 200 {object} web.GenericNestedResponse[types.Post]
// @Success 204 {object} web.GenericNestedResponse[types.Post, Types.AnotherOne]
// @Success 201 {object} web.GenericNestedResponse[web.GenericInnerType[types.Post]]
func GetPosts(w http.ResponseWriter, r *http.Request) {
_ = web.GenericNestedResponse[types.Post]{}
}
```
Para mais detalhes e outros exemplos, veja [esse arquivo](https://github.com/swaggo/swag/blob/master/testdata/generics_nested/api/api.go)
### Alterar os delimitadores de acção padrão Go Template
[#980](https://github.com/swaggo/swag/issues/980)
[#1177](https://github.com/swaggo/swag/issues/1177)

View File

@@ -50,7 +50,7 @@ Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framewo
go install github.com/swaggo/swag/cmd/swag@latest
```
从源码开始构建的话需要有Go环境1.17及以上版本)。
从源码开始构建的话需要有Go环境1.18及以上版本)。
或者从github的release页面下载预编译好的二进制文件。
@@ -378,23 +378,25 @@ swag fmt -d ./ --exclude ./internal
Example [celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller)
| 注释 | 描述 |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| description | 操作行为的详细说明。 |
| description.markdown | 应用程序的简短描述。该描述将从名为`endpointname.md`的文件中读取。 |
| id | 用于标识操作的唯一字符串。在所有API操作中必须唯一。 |
| tags | 每个API操作的标签列表以逗号分隔。 |
| 注释 | 描述 |
|----------------------|------------------------------------------------------------------------------------------------|
| description | 操作行为的详细说明。 |
| description.markdown | 应用程序的简短描述。该描述将从名为`endpointname.md`的文件中读取。 |
| id | 用于标识操作的唯一字符串。在所有API操作中必须唯一。 |
| tags | 每个API操作的标签列表以逗号分隔。 |
| summary | 该操作的简短摘要。 |
| accept | API 可以使用的 MIME 类型列表。 请注意Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime类型)”中所述。 |
| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime类型)”中所述。 |
| accept | API 可以使用的 MIME 类型列表。 请注意Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime类型)”中所述。 |
| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime类型)”中所述。 |
| param | 用空格分隔的参数。`param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
| security | 每个API操作的[安全性](#安全性)。 |
| success | 以空格分隔的成功响应。`return code`,`{param type}`,`data type`,`comment` |
| failure | 以空格分隔的故障响应。`return code`,`{param type}`,`data type`,`comment` |
| response | 与success、failure作用相同 |
| header | 以空格分隔的头字段。 `return code`,`{param type}`,`data type`,`comment` |
| router | 以空格分隔的路径定义。 `path`,`[httpMethod]` |
| x-name | 扩展字段必须以`x-`开头并且只能使用json值。 |
| security | 每个API操作的[安全性](#安全性)。 |
| success | 以空格分隔的成功响应。`return code`,`{param type}`,`data type`,`comment` |
| failure | 以空格分隔的故障响应。`return code`,`{param type}`,`data type`,`comment` |
| response | 与success、failure作用相同 |
| header | 以空格分隔的头字段。 `return code`,`{param type}`,`data type`,`comment` |
| router | 以空格分隔的路径定义。 `path`,`[httpMethod]` |
| deprecatedrouter | 与router相同但是是deprecated的。 |
| x-name | 扩展字段必须以`x-`开头并且只能使用json值。 |
| deprecated | 将当前API操作的所有路径设置为deprecated |
## Mime类型

View File

@@ -6,6 +6,7 @@ import (
"reflect"
"strconv"
"strings"
"unicode/utf8"
)
// ConstVariable a model to record a const variable
@@ -60,7 +61,7 @@ func EvaluateEscapedString(text string) string {
i++
char, err := strconv.ParseInt(text[i:i+4], 16, 32)
if err == nil {
result = AppendUtf8Rune(result, rune(char))
result = utf8.AppendRune(result, rune(char))
}
i += 3
} else if c, ok := escapedChars[text[i]]; ok {
@@ -404,7 +405,7 @@ func EvaluateUnary(x interface{}, operator token.Token, xtype ast.Expr) (interfa
func EvaluateBinary(x, y interface{}, operator token.Token, xtype, ytype ast.Expr) (interface{}, ast.Expr) {
if operator == token.SHR || operator == token.SHL {
var rightOperand uint64
yValue := CanIntegerValue{reflect.ValueOf(y)}
yValue := reflect.ValueOf(y)
if yValue.CanUint() {
rightOperand = yValue.Uint()
} else if yValue.CanInt() {
@@ -467,8 +468,8 @@ func EvaluateBinary(x, y interface{}, operator token.Token, xtype, ytype ast.Exp
evalType = ytype
}
xValue := CanIntegerValue{reflect.ValueOf(x)}
yValue := CanIntegerValue{reflect.ValueOf(y)}
xValue := reflect.ValueOf(x)
yValue := reflect.ValueOf(y)
if xValue.Kind() == reflect.String && yValue.Kind() == reflect.String {
return xValue.String() + yValue.String(), evalType
}

View File

@@ -96,13 +96,25 @@ func (ps *tagBaseFieldParser) FieldName() (string, error) {
}
}
func (ps *tagBaseFieldParser) FormName() string {
func (ps *tagBaseFieldParser) firstTagValue(tag string) string {
if ps.field.Tag != nil {
return strings.TrimSpace(strings.Split(ps.tag.Get(formTag), ",")[0])
return strings.TrimRight(strings.TrimSpace(strings.Split(ps.tag.Get(tag), ",")[0]), "[]")
}
return ""
}
func (ps *tagBaseFieldParser) FormName() string {
return ps.firstTagValue(formTag)
}
func (ps *tagBaseFieldParser) HeaderName() string {
return ps.firstTagValue(headerTag)
}
func (ps *tagBaseFieldParser) PathName() string {
return ps.firstTagValue(uriTag)
}
func toSnakeCase(in string) string {
var (
runes = []rune(in)

View File

@@ -14,9 +14,8 @@ import (
)
type genericTypeSpec struct {
ArrayDepth int
TypeSpec *TypeSpecDef
Name string
TypeSpec *TypeSpecDef
Name string
}
type formalParamType struct {
@@ -31,6 +30,87 @@ func (t *genericTypeSpec) TypeName() string {
return t.Name
}
func normalizeGenericTypeName(name string) string {
return strings.Replace(name, ".", "_", -1)
}
func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string, file *ast.File) (typeSpecDef *TypeSpecDef) {
if strings.HasPrefix(genericParam, "[]") {
typeSpecDef = pkgDefs.getTypeFromGenericParam(genericParam[2:], file)
if typeSpecDef == nil {
return nil
}
var expr ast.Expr
switch typeSpecDef.TypeSpec.Type.(type) {
case *ast.ArrayType, *ast.MapType:
expr = typeSpecDef.TypeSpec.Type
default:
name := typeSpecDef.TypeName()
expr = ast.NewIdent(name)
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
pkgDefs.uniqueDefinitions[name] = typeSpecDef
}
}
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "array_" + typeSpecDef.TypeName()),
Type: &ast.ArrayType{
Elt: expr,
},
},
Enums: typeSpecDef.Enums,
PkgPath: typeSpecDef.PkgPath,
ParentSpec: typeSpecDef.ParentSpec,
NotUnique: false,
}
}
if strings.HasPrefix(genericParam, "map[") {
parts := strings.SplitN(genericParam[4:], "]", 2)
if len(parts) != 2 {
return nil
}
typeSpecDef = pkgDefs.getTypeFromGenericParam(parts[1], file)
if typeSpecDef == nil {
return nil
}
var expr ast.Expr
switch typeSpecDef.TypeSpec.Type.(type) {
case *ast.ArrayType, *ast.MapType:
expr = typeSpecDef.TypeSpec.Type
default:
name := typeSpecDef.TypeName()
expr = ast.NewIdent(name)
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
pkgDefs.uniqueDefinitions[name] = typeSpecDef
}
}
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "map_" + parts[0] + "_" + typeSpecDef.TypeName()),
Type: &ast.MapType{
Key: ast.NewIdent(parts[0]), //assume key is string or integer
Value: expr,
},
},
Enums: typeSpecDef.Enums,
PkgPath: typeSpecDef.PkgPath,
ParentSpec: typeSpecDef.ParentSpec,
NotUnique: false,
}
}
if IsGolangPrimitiveType(genericParam) {
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(genericParam),
Type: ast.NewIdent(genericParam),
},
}
}
return pkgDefs.FindTypeSpec(genericParam, file)
}
func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string) *TypeSpecDef {
if original == nil || original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 {
return original
@@ -58,27 +138,19 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi
genericParamTypeDefs := map[string]*genericTypeSpec{}
for i, genericParam := range genericParams {
arrayDepth := 0
for {
if len(genericParam) <= 2 || genericParam[:2] != "[]" {
break
}
genericParam = genericParam[2:]
arrayDepth++
}
typeDef := pkgDefs.FindTypeSpec(genericParam, file)
if typeDef != nil {
genericParam = typeDef.TypeName()
if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok {
pkgDefs.uniqueDefinitions[genericParam] = typeDef
var typeDef *TypeSpecDef
if !IsGolangPrimitiveType(genericParam) {
typeDef = pkgDefs.getTypeFromGenericParam(genericParam, file)
if typeDef != nil {
genericParam = typeDef.TypeName()
if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok {
pkgDefs.uniqueDefinitions[genericParam] = typeDef
}
}
}
genericParamTypeDefs[formals[i].Name] = &genericTypeSpec{
ArrayDepth: arrayDepth,
TypeSpec: typeDef,
Name: genericParam,
TypeSpec: typeDef,
Name: genericParam,
}
}
@@ -86,17 +158,11 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi
var nameParts []string
for _, def := range formals {
if specDef, ok := genericParamTypeDefs[def.Name]; ok {
var prefix = ""
if specDef.ArrayDepth == 1 {
prefix = "array_"
} else if specDef.ArrayDepth > 1 {
prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth)
}
nameParts = append(nameParts, prefix+specDef.TypeName())
nameParts = append(nameParts, specDef.TypeName())
}
}
name += strings.Replace(strings.Join(nameParts, "-"), ".", "_", -1)
name += normalizeGenericTypeName(strings.Join(nameParts, "-"))
if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok {
return typeSpec
@@ -180,11 +246,7 @@ func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.
switch astExpr := expr.(type) {
case *ast.Ident:
if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok {
retType := pkgDefs.getParametrizedType(genTypeSpec)
for i := 0; i < genTypeSpec.ArrayDepth; i++ {
retType = &ast.ArrayType{Elt: retType}
}
return retType
return pkgDefs.getParametrizedType(genTypeSpec)
}
case *ast.ArrayType:
return &ast.ArrayType{

View File

@@ -1,42 +0,0 @@
//go:build !go1.18
// +build !go1.18
package swag
import (
"fmt"
"github.com/go-openapi/spec"
"go/ast"
)
type genericTypeSpec struct {
ArrayDepth int
TypeSpec *TypeSpecDef
Name string
}
func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string) *TypeSpecDef {
return original
}
func getGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
return "", fmt.Errorf("unknown field type %#v", field)
}
func (parser *Parser) parseGenericTypeExpr(file *ast.File, typeExpr ast.Expr) (*spec.Schema, error) {
switch typeExpr.(type) {
// suppress debug messages for these types
case *ast.InterfaceType:
case *ast.StructType:
case *ast.Ident:
case *ast.StarExpr:
case *ast.SelectorExpr:
case *ast.ArrayType:
case *ast.MapType:
case *ast.FuncType:
default:
parser.debug.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
}
return PrimitiveSchema(OBJECT), nil
}

View File

@@ -21,6 +21,7 @@ import (
type RouteProperties struct {
HTTPMethod string
Path string
Deprecated bool
}
// Operation describes a single API operation on a path.
@@ -30,6 +31,7 @@ type Operation struct {
codeExampleFilesDir string
spec.Operation
RouterProperties []RouteProperties
State string
}
var mimeTypeAliases = map[string]string{
@@ -118,6 +120,8 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
lineRemainder = fields[1]
}
switch lowerAttribute {
case stateAttr:
operation.ParseStateComment(lineRemainder)
case descriptionAttr:
operation.ParseDescriptionComment(lineRemainder)
case descriptionMarkdownAttr:
@@ -144,7 +148,9 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
case headerAttr:
return operation.ParseResponseHeaderComment(lineRemainder, astFile)
case routerAttr:
return operation.ParseRouterComment(lineRemainder)
return operation.ParseRouterComment(lineRemainder, false)
case deprecatedRouterAttr:
return operation.ParseRouterComment(lineRemainder, true)
case securityAttr:
return operation.ParseSecurityComment(lineRemainder)
case deprecatedAttr:
@@ -158,7 +164,7 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
return nil
}
// ParseCodeSample godoc.
// ParseCodeSample parse code sample.
func (operation *Operation) ParseCodeSample(attribute, _, lineRemainder string) error {
if lineRemainder == "file" {
data, err := getCodeExampleForSummary(operation.Summary, operation.codeExampleFilesDir)
@@ -183,7 +189,12 @@ func (operation *Operation) ParseCodeSample(attribute, _, lineRemainder string)
return operation.ParseMetadata(attribute, strings.ToLower(attribute), lineRemainder)
}
// ParseDescriptionComment godoc.
// ParseStateComment parse state comment.
func (operation *Operation) ParseStateComment(lineRemainder string) {
operation.State = lineRemainder
}
// ParseDescriptionComment parse description comment.
func (operation *Operation) ParseDescriptionComment(lineRemainder string) {
if operation.Description == "" {
operation.Description = lineRemainder
@@ -194,7 +205,7 @@ func (operation *Operation) ParseDescriptionComment(lineRemainder string) {
operation.Description += "\n" + lineRemainder
}
// ParseMetadata godoc.
// ParseMetadata parse metadata.
func (operation *Operation) ParseMetadata(attribute, lowerAttribute, lineRemainder string) error {
// parsing specific meta data extensions
if strings.HasPrefix(lowerAttribute, "@x-") {
@@ -275,16 +286,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
param := createParameter(paramType, description, name, objectType, refType, required, enums, operation.parser.collectionFormatInQuery)
switch paramType {
case "path", "header":
switch objectType {
case ARRAY:
if !IsPrimitiveType(refType) {
return fmt.Errorf("%s is not supported array type for %s", refType, paramType)
}
case OBJECT:
return fmt.Errorf("%s is not supported type for %s", refType, paramType)
}
case "query", "formData":
case "path", "header", "query", "formData":
switch objectType {
case ARRAY:
if !IsPrimitiveType(refType) && !(refType == "file" && paramType == "formData") {
@@ -313,11 +315,14 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
}
}
var formName = name
if item.Schema.Extensions != nil {
if nameVal, ok := item.Schema.Extensions[formTag]; ok {
formName = nameVal.(string)
}
nameOverrideType := paramType
// query also uses formData tags
if paramType == "query" {
nameOverrideType = "formData"
}
// load overridden type specific name from extensions if exists
if nameVal, ok := item.Schema.Extensions[nameOverrideType]; ok {
name = nameVal.(string)
}
switch {
@@ -335,10 +340,10 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
if !IsSimplePrimitiveType(itemSchema.Type[0]) {
continue
}
param = createParameter(paramType, prop.Description, formName, prop.Type[0], itemSchema.Type[0], findInSlice(schema.Required, name), itemSchema.Enum, operation.parser.collectionFormatInQuery)
param = createParameter(paramType, prop.Description, name, prop.Type[0], itemSchema.Type[0], findInSlice(schema.Required, item.Name), itemSchema.Enum, operation.parser.collectionFormatInQuery)
case IsSimplePrimitiveType(prop.Type[0]):
param = createParameter(paramType, prop.Description, formName, PRIMITIVE, prop.Type[0], findInSlice(schema.Required, name), nil, operation.parser.collectionFormatInQuery)
param = createParameter(paramType, prop.Description, name, PRIMITIVE, prop.Type[0], findInSlice(schema.Required, item.Name), nil, operation.parser.collectionFormatInQuery)
default:
operation.parser.debug.Printf("skip field [%s] in %s is not supported type for %s", name, refType, paramType)
continue
@@ -395,6 +400,8 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
const (
formTag = "form"
jsonTag = "json"
uriTag = "uri"
headerTag = "header"
bindingTag = "binding"
defaultTag = "default"
enumsTag = "enums"
@@ -699,7 +706,7 @@ func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) e
var routerPattern = regexp.MustCompile(`^(/[\w./\-{}+:$]*)[[:blank:]]+\[(\w+)]`)
// ParseRouterComment parses comment for given `router` comment string.
func (operation *Operation) ParseRouterComment(commentLine string) error {
func (operation *Operation) ParseRouterComment(commentLine string, deprecated bool) error {
matches := routerPattern.FindStringSubmatch(commentLine)
if len(matches) != 3 {
return fmt.Errorf("can not parse router comment \"%s\"", commentLine)
@@ -708,6 +715,7 @@ func (operation *Operation) ParseRouterComment(commentLine string) error {
signature := RouteProperties{
Path: matches[1],
HTTPMethod: strings.ToUpper(matches[2]),
Deprecated: deprecated,
}
if _, ok := allMethod[signature.HTTPMethod]; !ok {
@@ -918,6 +926,10 @@ func parseCombinedObjectSchema(parser *Parser, refType string, astFile *ast.File
return nil, err
}
if schema == nil {
schema = PrimitiveSchema(OBJECT)
}
props[keyVal[0]] = *schema
}
}

View File

@@ -354,7 +354,7 @@ func (pkgDefs *PackagesDefinitions) collectConstEnums(parsedSchemas map[*TypeSpe
}
//delete it from parsed schemas, and will parse it again
if _, ok := parsedSchemas[typeDef]; ok {
if _, ok = parsedSchemas[typeDef]; ok {
delete(parsedSchemas, typeDef)
}
@@ -363,7 +363,7 @@ func (pkgDefs *PackagesDefinitions) collectConstEnums(parsedSchemas map[*TypeSpe
}
name := constVar.Name.Name
if _, ok := constVar.Value.(ast.Expr); ok {
if _, ok = constVar.Value.(ast.Expr); ok {
continue
}
@@ -506,13 +506,6 @@ func (pkgDefs *PackagesDefinitions) findPackagePathFromImports(pkg string, file
}
func (pkgDefs *PackagesDefinitions) findTypeSpecFromPackagePaths(matchedPkgPaths, externalPkgPaths []string, name string) (typeDef *TypeSpecDef) {
for _, pkgPath := range matchedPkgPaths {
typeDef = pkgDefs.findTypeSpec(pkgPath, name)
if typeDef != nil {
return typeDef
}
}
if pkgDefs.parseDependency > 0 {
for _, pkgPath := range externalPkgPaths {
if err := pkgDefs.loadExternalPackage(pkgPath); err == nil {
@@ -524,6 +517,13 @@ func (pkgDefs *PackagesDefinitions) findTypeSpecFromPackagePaths(matchedPkgPaths
}
}
for _, pkgPath := range matchedPkgPaths {
typeDef = pkgDefs.findTypeSpec(pkgPath, name)
if typeDef != nil {
return typeDef
}
}
return typeDef
}
@@ -542,13 +542,14 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File
parts := strings.Split(strings.Split(typeName, "[")[0], ".")
if len(parts) > 1 {
typeDef, ok := pkgDefs.uniqueDefinitions[typeName]
if ok {
return typeDef
}
pkgPaths, externalPkgPaths := pkgDefs.findPackagePathFromImports(parts[0], file)
typeDef = pkgDefs.findTypeSpecFromPackagePaths(pkgPaths, externalPkgPaths, parts[1])
if len(externalPkgPaths) == 0 || pkgDefs.parseDependency == ParseNone {
typeDef, ok := pkgDefs.uniqueDefinitions[typeName]
if ok {
return typeDef
}
}
typeDef := pkgDefs.findTypeSpecFromPackagePaths(pkgPaths, externalPkgPaths, parts[1])
return pkgDefs.parametrizeGenericType(file, typeDef, typeName)
}

View File

@@ -43,6 +43,7 @@ const (
headerAttr = "@header"
tagsAttr = "@tags"
routerAttr = "@router"
deprecatedRouterAttr = "@deprecatedrouter"
summaryAttr = "@summary"
deprecatedAttr = "@deprecated"
securityAttr = "@security"
@@ -66,6 +67,7 @@ const (
extDocsURLAttr = "@externaldocs.url"
xCodeSamplesAttr = "@x-codesamples"
scopeAttrPrefix = "@scope."
stateAttr = "@state"
)
// ParseFlag determine what to parse
@@ -174,6 +176,9 @@ type Parser struct {
// tags to filter the APIs after
tags map[string]struct{}
// HostState is the state of the host
HostState string
}
// FieldParserFactory create FieldParser.
@@ -184,6 +189,8 @@ type FieldParser interface {
ShouldSkip() bool
FieldName() (string, error)
FormName() string
HeaderName() string
PathName() string
CustomSchema() (*spec.Schema, error)
ComplementSchema(schema *spec.Schema) error
IsRequired() (bool, error)
@@ -541,6 +548,14 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error {
case "@host":
parser.swagger.Host = value
case "@hoststate":
fields = FieldsByAnySpace(commentLine, 3)
if len(fields) != 3 {
return fmt.Errorf("%s needs 3 arguments", attribute)
}
if parser.HostState == fields[1] {
parser.swagger.Host = fields[2]
}
case "@basepath":
parser.swagger.BasePath = value
@@ -722,6 +737,7 @@ func parseSecAttributes(context string, lines []string, index *int) (*spec.Secur
attrMap, scopes := make(map[string]string), make(map[string]string)
extensions, description := make(map[string]interface{}), ""
loopline:
for ; *index < len(lines); *index++ {
v := strings.TrimSpace(lines[*index])
if len(v) == 0 {
@@ -738,23 +754,21 @@ func parseSecAttributes(context string, lines []string, index *int) (*spec.Secur
for _, findterm := range search {
if securityAttr == findterm {
attrMap[securityAttr] = value
break
continue loopline
}
}
isExists, err := isExistsScope(securityAttr)
if err != nil {
if isExists, err := isExistsScope(securityAttr); err != nil {
return nil, err
}
if isExists {
scopes[securityAttr[len(scopeAttrPrefix):]] = v[len(securityAttr):]
} else if isExists {
scopes[securityAttr[len(scopeAttrPrefix):]] = value
continue
}
if strings.HasPrefix(securityAttr, "@x-") {
// Add the custom attribute without the @
extensions[securityAttr[1:]] = value
continue
}
// Not mandatory field
@@ -901,14 +915,14 @@ func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) {
func isExistsScope(scope string) (bool, error) {
s := strings.Fields(scope)
for _, v := range s {
if strings.Contains(v, scopeAttrPrefix) {
if strings.HasPrefix(v, scopeAttrPrefix) {
if strings.Contains(v, ",") {
return false, fmt.Errorf("@scope can't use comma(,) get=" + v)
}
}
}
return strings.Contains(scope, scopeAttrPrefix), nil
return strings.HasPrefix(scope, scopeAttrPrefix), nil
}
func getTagsFromComment(comment string) (tags []string) {
@@ -977,6 +991,7 @@ 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
@@ -992,6 +1007,9 @@ func (parser *Parser) ParseRouterAPIInfo(fileInfo *AstFileInfo) error {
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 {
@@ -1065,6 +1083,10 @@ func processRouterOperation(parser *Parser, operation *Operation) error {
*op = &operation.Operation
}
if routeProperties.Deprecated {
(*op).Deprecated = routeProperties.Deprecated
}
parser.swagger.Paths.Paths[routeProperties.Path] = pathItem
}
@@ -1261,6 +1283,9 @@ 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) {
if file == nil {
return
}
for _, astDeclaration := range file.Decls {
generalDeclaration, ok := astDeclaration.(*ast.GenDecl)
if !ok || generalDeclaration.Tok != token.TYPE {
@@ -1485,11 +1510,17 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st
tagRequired = append(tagRequired, fieldName)
}
if schema.Extensions == nil {
schema.Extensions = make(spec.Extensions)
}
if formName := ps.FormName(); len(formName) > 0 {
if schema.Extensions == nil {
schema.Extensions = make(spec.Extensions)
}
schema.Extensions[formTag] = formName
schema.Extensions["formData"] = formName
}
if headerName := ps.HeaderName(); len(headerName) > 0 {
schema.Extensions["header"] = headerName
}
if pathName := ps.PathName(); len(pathName) > 0 {
schema.Extensions["path"] = pathName
}
return map[string]spec.Schema{fieldName: *schema}, tagRequired, nil

View File

@@ -35,7 +35,7 @@ type TypeSpecDef struct {
// Name the name of the typeSpec.
func (t *TypeSpecDef) Name() string {
if t.TypeSpec != nil {
if t.TypeSpec != nil && t.TypeSpec.Name != nil {
return t.TypeSpec.Name.Name
}
@@ -71,7 +71,7 @@ func (t *TypeSpecDef) TypeName() string {
return r
}, t.PkgPath)
names = append(names, pkgPath)
} else {
} else if t.File != nil {
names = append(names, t.File.Name.Name)
}
if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil {

View File

@@ -1,31 +0,0 @@
//go:build go1.18
// +build go1.18
package swag
import (
"reflect"
"unicode/utf8"
)
// AppendUtf8Rune appends the UTF-8 encoding of r to the end of p and
// returns the extended buffer. If the rune is out of range,
// it appends the encoding of RuneError.
func AppendUtf8Rune(p []byte, r rune) []byte {
return utf8.AppendRune(p, r)
}
// CanIntegerValue a wrapper of reflect.Value
type CanIntegerValue struct {
reflect.Value
}
// CanInt reports whether Uint can be used without panicking.
func (v CanIntegerValue) CanInt() bool {
return v.Value.CanInt()
}
// CanUint reports whether Uint can be used without panicking.
func (v CanIntegerValue) CanUint() bool {
return v.Value.CanUint()
}

View File

@@ -1,47 +0,0 @@
//go:build !go1.18
// +build !go1.18
package swag
import (
"reflect"
"unicode/utf8"
)
// AppendUtf8Rune appends the UTF-8 encoding of r to the end of p and
// returns the extended buffer. If the rune is out of range,
// it appends the encoding of RuneError.
func AppendUtf8Rune(p []byte, r rune) []byte {
length := utf8.RuneLen(rune(r))
if length > 0 {
utf8Slice := make([]byte, length)
utf8.EncodeRune(utf8Slice, rune(r))
p = append(p, utf8Slice...)
}
return p
}
// CanIntegerValue a wrapper of reflect.Value
type CanIntegerValue struct {
reflect.Value
}
// CanInt reports whether Uint can be used without panicking.
func (v CanIntegerValue) CanInt() bool {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
default:
return false
}
}
// CanUint reports whether Uint can be used without panicking.
func (v CanIntegerValue) CanUint() bool {
switch v.Kind() {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return true
default:
return false
}
}

View File

@@ -1,4 +1,4 @@
package swag
// Version of swag.
const Version = "v1.16.2"
const Version = "v1.16.3"