From d209f71e81955068903e04daca7716b7535576b9 Mon Sep 17 00:00:00 2001 From: Pablo Morelli Date: Tue, 19 Apr 2022 15:08:22 +0200 Subject: [PATCH 01/12] added security definition description (#1174) --- README.md | 50 ++++++++++++------------- example/celler/main.go | 1 + parser.go | 48 +++++++++++++++++------- parser_test.go | 84 +++++++++++++++++++++++++++++++++++++++++- swagger_test.go | 8 ++++ testdata/main.go | 1 + 6 files changed, 152 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 8774db7a4..12138b91e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/swaggo/swag)](https://goreportcard.com/report/github.com/swaggo/swag) [![codebeat badge](https://codebeat.co/badges/71e2f5e5-9e6b-405d-baf9-7cc8b5037330)](https://codebeat.co/projects/github-com-swaggo-swag-master) [![Go Doc](https://godoc.org/github.com/swaggo/swagg?status.svg)](https://godoc.org/github.com/swaggo/swag) -[![Backers on Open Collective](https://opencollective.com/swag/backers/badge.svg)](#backers) +[![Backers on Open Collective](https://opencollective.com/swag/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/swag/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield) [![Release](https://img.shields.io/github/release/swaggo/swag.svg?style=flat-square)](https://github.com/swaggo/swag/releases) @@ -30,7 +30,7 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie - [Descriptions over multiple lines](#descriptions-over-multiple-lines) - [User defined structure with an array type](#user-defined-structure-with-an-array-type) - [Model composition in response](#model-composition-in-response) - - [Add a headers in response](#add-a-headers-in-response) + - [Add a headers in response](#add-a-headers-in-response) - [Use multiple path params](#use-multiple-path-params) - [Example value of struct](#example-value-of-struct) - [SchemaExample of body](#schemaexample-of-body) @@ -193,7 +193,7 @@ import ( "github.com/gin-gonic/gin" "github.com/swaggo/files" "github.com/swaggo/gin-swagger" - + "./docs" // docs is generated by Swag CLI, you have to import it. ) @@ -212,7 +212,7 @@ func main() { docs.SwaggerInfo.Host = "petstore.swagger.io" docs.SwaggerInfo.BasePath = "/v2" docs.SwaggerInfo.Schemes = []string{"http", "https"} - + r := gin.New() // use ginSwagger middleware to serve the API docs @@ -298,10 +298,10 @@ $ swag init ## The swag formatter -The Swag Comments can be automatically formatted, just like 'go fmt'. +The Swag Comments can be automatically formatted, just like 'go fmt'. Find the result of formatting [here](https://github.com/swaggo/swag/tree/master/example/celler). -Usage: +Usage: ```shell swag fmt ``` @@ -444,21 +444,21 @@ Besides that, `swag` also accepts aliases for some MIME Types as follows: | annotation | description | parameters | example | |------------|-------------|------------|---------| | securitydefinitions.basic | [Basic](https://swagger.io/docs/specification/2-0/authentication/basic-authentication/) auth. | | // @securityDefinitions.basic BasicAuth | -| securitydefinitions.apikey | [API key](https://swagger.io/docs/specification/2-0/authentication/api-keys/) auth. | in, name | // @securityDefinitions.apikey ApiKeyAuth | -| securitydefinitions.oauth2.application | [OAuth2 application](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope | // @securitydefinitions.oauth2.application OAuth2Application | -| securitydefinitions.oauth2.implicit | [OAuth2 implicit](https://swagger.io/docs/specification/authentication/oauth2/) auth. | authorizationUrl, scope | // @securitydefinitions.oauth2.implicit OAuth2Implicit | -| securitydefinitions.oauth2.password | [OAuth2 password](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope | // @securitydefinitions.oauth2.password OAuth2Password | -| securitydefinitions.oauth2.accessCode | [OAuth2 access code](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, authorizationUrl, scope | // @securitydefinitions.oauth2.accessCode OAuth2AccessCode | - - -| parameters annotation | example | -|-----------------------|----------------------------------------------------------| -| in | // @in header | -| name | // @name Authorization | -| tokenUrl | // @tokenUrl https://example.com/oauth/token | -| authorizationurl | // @authorizationurl https://example.com/oauth/authorize | -| scope.hoge | // @scope.write Grants write access | - +| securitydefinitions.apikey | [API key](https://swagger.io/docs/specification/2-0/authentication/api-keys/) auth. | in, name, description | // @securityDefinitions.apikey ApiKeyAuth | +| securitydefinitions.oauth2.application | [OAuth2 application](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope, description | // @securitydefinitions.oauth2.application OAuth2Application | +| securitydefinitions.oauth2.implicit | [OAuth2 implicit](https://swagger.io/docs/specification/authentication/oauth2/) auth. | authorizationUrl, scope, description | // @securitydefinitions.oauth2.implicit OAuth2Implicit | +| securitydefinitions.oauth2.password | [OAuth2 password](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope, description | // @securitydefinitions.oauth2.password OAuth2Password | +| securitydefinitions.oauth2.accessCode | [OAuth2 access code](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, authorizationUrl, scope, description | // @securitydefinitions.oauth2.accessCode OAuth2AccessCode | + + +| parameters annotation | example | +|---------------------------------|-------------------------------------------------------------------------| +| in | // @in header | +| name | // @name Authorization | +| tokenUrl | // @tokenUrl https://example.com/oauth/token | +| authorizationurl | // @authorizationurl https://example.com/oauth/authorize | +| scope.hoge | // @scope.write Grants write access | +| description | // @description OAuth protects our entity endpoints | ## Attribute @@ -487,7 +487,7 @@ type Foo struct { Field Name | Type | Description ---|:---:|--- -validate | `string` | Determines the validation for the parameter. Possible values are: `required`. +validate | `string` | Determines the validation for the parameter. Possible values are: `required`. default | * | Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request. (Note: "default" has no meaning for required parameters.) See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Unlike JSON Schema this value MUST conform to the defined [`type`](#parameterType) for this parameter. maximum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2. minimum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3. @@ -512,7 +512,7 @@ Field Name | Type | Description ### Descriptions over multiple lines -You can add descriptions spanning multiple lines in either the general api description or routes definitions like so: +You can add descriptions spanning multiple lines in either the general api description or routes definitions like so: ```go // @description This is the first line @@ -561,7 +561,7 @@ type Order struct { //in `proto` package @success 200 {object} jsonresult.JSONResult{data=[]string} "desc" ``` -- overriding multiple fields. field will be added if not exists +- overriding multiple fields. field will be added if not exists ```go @success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc" ``` @@ -751,7 +751,7 @@ Rendered: "id": "integer" } ``` - + ### Use swaggerignore tag to exclude a field diff --git a/example/celler/main.go b/example/celler/main.go index c94ebc475..87d4742a4 100644 --- a/example/celler/main.go +++ b/example/celler/main.go @@ -33,6 +33,7 @@ import ( // @securityDefinitions.apikey ApiKeyAuth // @in header // @name Authorization +// @description Description for what is this security definition being used // @securitydefinitions.oauth2.application OAuth2Application // @tokenUrl https://example.com/oauth/token diff --git a/parser.go b/parser.go index 387151aba..980c1d7b2 100644 --- a/parser.go +++ b/parser.go @@ -383,7 +383,8 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { previousAttribute := "" // parsing classic meta data model - for i, commentLine := range comments { + for i := 0; i < len(comments); i++ { + commentLine := comments[i] attribute := strings.Split(commentLine, " ")[0] value := strings.TrimSpace(commentLine[len(attribute):]) multilineBlock := false @@ -472,35 +473,35 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { case "@securitydefinitions.basic": parser.swagger.SecurityDefinitions[value] = spec.BasicAuth() case "@securitydefinitions.apikey": - attrMap, _, _, err := parseSecAttr(attribute, []string{"@in", "@name"}, comments[i+1:]) + attrMap, _, extensions, err := parseSecAttr(attribute, []string{"@in", "@name"}, comments, &i) if err != nil { return err } - parser.swagger.SecurityDefinitions[value] = spec.APIKeyAuth(attrMap["@name"], attrMap["@in"]) + parser.swagger.SecurityDefinitions[value] = tryAddDescription(spec.APIKeyAuth(attrMap["@name"], attrMap["@in"]), extensions) case "@securitydefinitions.oauth2.application": - attrMap, scopes, extensions, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments[i+1:]) + attrMap, scopes, extensions, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &i) if err != nil { return err } - parser.swagger.SecurityDefinitions[value] = secOAuth2Application(attrMap["@tokenurl"], scopes, extensions) + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Application(attrMap["@tokenurl"], scopes, extensions), extensions) case "@securitydefinitions.oauth2.implicit": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@authorizationurl"}, comments[i+1:]) + attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@authorizationurl"}, comments, &i) if err != nil { return err } - parser.swagger.SecurityDefinitions[value] = secOAuth2Implicit(attrs["@authorizationurl"], scopes, ext) + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Implicit(attrs["@authorizationurl"], scopes, ext), ext) case "@securitydefinitions.oauth2.password": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments[i+1:]) + attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &i) if err != nil { return err } - parser.swagger.SecurityDefinitions[value] = secOAuth2Password(attrs["@tokenurl"], scopes, ext) + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Password(attrs["@tokenurl"], scopes, ext), ext) case "@securitydefinitions.oauth2.accesscode": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl", "@authorizationurl"}, comments[i+1:]) + attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl", "@authorizationurl"}, comments, &i) if err != nil { return err } - parser.swagger.SecurityDefinitions[value] = secOAuth2AccessToken(attrs["@authorizationurl"], attrs["@tokenurl"], scopes, ext) + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2AccessToken(attrs["@authorizationurl"], attrs["@tokenurl"], scopes, ext), ext) case "@query.collection.format": parser.collectionFormatInQuery = value default: @@ -549,6 +550,15 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { return nil } +func tryAddDescription(spec *spec.SecurityScheme, extensions map[string]interface{}) *spec.SecurityScheme { + if val, ok := extensions["@description"]; ok { + if str, ok := val.(string); ok { + spec.Description = str + } + } + return spec +} + // ParseAcceptComment parses comment for given `accept` comment string. func (parser *Parser) ParseAcceptComment(commentLine string) error { return parseMimeTypeList(commentLine, &parser.swagger.Consumes, "%v accept type can't be accepted") @@ -572,16 +582,20 @@ func isGeneralAPIComment(comments []string) bool { return true } -func parseSecAttr(context string, search []string, lines []string) (map[string]string, map[string]string, map[string]interface{}, error) { +func parseSecAttr(context string, search []string, lines []string, index *int) (map[string]string, map[string]string, map[string]interface{}, error) { attrMap := map[string]string{} scopes := map[string]string{} extensions := map[string]interface{}{} - for _, v := range lines { + + // For the first line we get the attributes in the context parameter, so we skip to the next one + *index++ + + for ; *index < len(lines); *index++ { + v := lines[*index] securityAttr := strings.ToLower(strings.Split(v, " ")[0]) for _, findterm := range search { if securityAttr == findterm { attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):]) - continue } } @@ -596,8 +610,14 @@ func parseSecAttr(context string, search []string, lines []string) (map[string]s // Add the custom attribute without the @ extensions[securityAttr[1:]] = strings.TrimSpace(v[len(securityAttr):]) } + // Not mandatory field + if securityAttr == "@description" { + extensions[securityAttr] = strings.TrimSpace(v[len(securityAttr):]) + } // next securityDefinitions if strings.Index(securityAttr, "@securitydefinitions.") == 0 { + // Go back to the previous line and break + *index-- break } } diff --git a/parser_test.go b/parser_test.go index 0799ea242..908d54a7f 100644 --- a/parser_test.go +++ b/parser_test.go @@ -10,8 +10,10 @@ import ( "log" "os" "path/filepath" + "reflect" "testing" + "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" ) @@ -152,6 +154,7 @@ func TestParser_ParseGeneralApiInfo(t *testing.T) { "paths": {}, "securityDefinitions": { "ApiKeyAuth": { + "description": "some description", "type": "apiKey", "name": "Authorization", "in": "header" @@ -538,15 +541,32 @@ func TestParser_ParseGeneralAPISecurity(t *testing.T) { err := parseGeneralAPIInfo(parser, []string{ "@securitydefinitions.apikey ApiKey", "@in header", - "@name X-API-KEY"}) + "@name X-API-KEY", + "@description some", + "", + "@securitydefinitions.oauth2.accessCode OAuth2AccessCode", + "@tokenUrl https://example.com/oauth/token", + "@authorizationUrl https://example.com/oauth/authorize", + "@scope.admin foo", + }) assert.NoError(t, err) b, _ := json.MarshalIndent(parser.GetSwagger().SecurityDefinitions, "", " ") expected := `{ "ApiKey": { + "description": "some", "type": "apiKey", "name": "X-API-KEY", "in": "header" + }, + "OAuth2AccessCode": { + "type": "oauth2", + "flow": "accessCode", + "authorizationUrl": "https://example.com/oauth/authorize", + "tokenUrl": "https://example.com/oauth/token", + "scopes": { + "admin": " foo" + } } }` assert.Equal(t, expected, string(b)) @@ -3422,3 +3442,65 @@ func TestGetFieldType(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "models.User", field) } + +func TestTryAddDescription(t *testing.T) { + type args struct { + spec *spec.SecurityScheme + extensions map[string]interface{} + } + tests := []struct { + name string + args args + want *spec.SecurityScheme + }{ + { + name: "added description", + args: args{ + spec: &spec.SecurityScheme{}, + extensions: map[string]interface{}{ + "@description": "some description", + }, + }, + want: &spec.SecurityScheme{ + SecuritySchemeProps: spec.SecuritySchemeProps{ + Description: "some description", + }, + }, + }, + { + name: "no description", + args: args{ + spec: &spec.SecurityScheme{}, + extensions: map[string]interface{}{ + "@not-description": "some description", + }, + }, + want: &spec.SecurityScheme{ + SecuritySchemeProps: spec.SecuritySchemeProps{ + Description: "", + }, + }, + }, + { + name: "description has invalid format", + args: args{ + spec: &spec.SecurityScheme{}, + extensions: map[string]interface{}{ + "@description": 12345, + }, + }, + want: &spec.SecurityScheme{ + SecuritySchemeProps: spec.SecuritySchemeProps{ + Description: "", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tryAddDescription(tt.args.spec, tt.args.extensions); !reflect.DeepEqual(got, tt.want) { + t.Errorf("tryAddDescription() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/swagger_test.go b/swagger_test.go index 6483ef8ad..3d15d0b59 100644 --- a/swagger_test.go +++ b/swagger_test.go @@ -146,6 +146,14 @@ var doc = `{ } } } + }, + "securityDefinitions": { + "ApiKey": { + "description: "some", + "type": "apiKey", + "name": "X-API-KEY", + "in": "header" + } } }` diff --git a/testdata/main.go b/testdata/main.go index 2cadfe4fb..d70a642e8 100644 --- a/testdata/main.go +++ b/testdata/main.go @@ -21,6 +21,7 @@ package main // @securityDefinitions.apikey ApiKeyAuth // @in header // @name Authorization +// @description some description // @securitydefinitions.oauth2.application OAuth2Application // @tokenUrl https://example.com/oauth/token From 90170b958922a030d8d3b7e4de5cde1050417842 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Apr 2022 09:16:50 +0300 Subject: [PATCH 02/12] chore(deps): bump github.com/swaggo/http-swagger in /example/markdown (#1185) Bumps [github.com/swaggo/http-swagger](https://github.com/swaggo/http-swagger) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/swaggo/http-swagger/releases) - [Changelog](https://github.com/swaggo/http-swagger/blob/master/.goreleaser.yml) - [Commits](https://github.com/swaggo/http-swagger/compare/v1.2.5...v1.2.6) --- updated-dependencies: - dependency-name: github.com/swaggo/http-swagger dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- example/markdown/go.mod | 2 +- example/markdown/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/markdown/go.mod b/example/markdown/go.mod index ebf01f47d..6e06dec8b 100644 --- a/example/markdown/go.mod +++ b/example/markdown/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/gorilla/mux v1.7.3 - github.com/swaggo/http-swagger v1.2.5 + github.com/swaggo/http-swagger v1.2.6 github.com/swaggo/swag v1.7.9 ) diff --git a/example/markdown/go.sum b/example/markdown/go.sum index c89dbe6ef..8b36cf767 100644 --- a/example/markdown/go.sum +++ b/example/markdown/go.sum @@ -59,8 +59,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/http-swagger v1.2.5 h1:iDWoHpJMLNo4nwGOPXsOoqlB9wB6M4xgjhws8x3KQcs= -github.com/swaggo/http-swagger v1.2.5/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA= +github.com/swaggo/http-swagger v1.2.6 h1:ihTjChUoSRMpFMjWw+0AkL1Ti4r6v8pCgVYLmQVRlRw= +github.com/swaggo/http-swagger v1.2.6/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA= github.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc= github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= From 636f456e3135fea11170567d778a45faeee1e882 Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Tue, 26 Apr 2022 20:40:41 +0300 Subject: [PATCH 03/12] chore: improve code quality (#1184) --- cmd/swag/main.go | 36 +-- field_parser.go | 424 ++++++++++++++------------ format/format.go | 7 +- formater.go => formatter.go | 220 ++++++++----- formater_test.go => formatter_test.go | 90 +++--- gen/gen.go | 39 ++- gen/gen_test.go | 48 +++ operation.go | 299 ++++++++++++------ operation_test.go | 18 +- packages.go | 100 +++--- parser.go | 236 +++++++++----- parser_test.go | 14 +- schema.go | 5 + schema_test.go | 6 +- spec.go | 18 +- spec_test.go | 17 +- swagger.go | 2 + 17 files changed, 1007 insertions(+), 572 deletions(-) rename formater.go => formatter.go (62%) rename formater_test.go => formatter_test.go (83%) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 0a18900f0..579178032 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -112,8 +112,8 @@ var initFlags = []cli.Flag{ }, } -func initAction(c *cli.Context) error { - strategy := c.String(propertyStrategyFlag) +func initAction(ctx *cli.Context) error { + strategy := ctx.String(propertyStrategyFlag) switch strategy { case swag.CamelCase, swag.SnakeCase, swag.PascalCase: @@ -121,27 +121,27 @@ func initAction(c *cli.Context) error { return fmt.Errorf("not supported %s propertyStrategy", strategy) } - outputTypes := strings.Split(c.String(outputTypesFlag), ",") + outputTypes := strings.Split(ctx.String(outputTypesFlag), ",") if len(outputTypes) == 0 { return fmt.Errorf("no output types specified") } return gen.New().Build(&gen.Config{ - SearchDir: c.String(searchDirFlag), - Excludes: c.String(excludeFlag), - MainAPIFile: c.String(generalInfoFlag), + SearchDir: ctx.String(searchDirFlag), + Excludes: ctx.String(excludeFlag), + MainAPIFile: ctx.String(generalInfoFlag), PropNamingStrategy: strategy, - OutputDir: c.String(outputFlag), + OutputDir: ctx.String(outputFlag), OutputTypes: outputTypes, - ParseVendor: c.Bool(parseVendorFlag), - ParseDependency: c.Bool(parseDependencyFlag), - MarkdownFilesDir: c.String(markdownFilesFlag), - ParseInternal: c.Bool(parseInternalFlag), - GeneratedTime: c.Bool(generatedTimeFlag), - CodeExampleFilesDir: c.String(codeExampleFilesFlag), - ParseDepth: c.Int(parseDepthFlag), - InstanceName: c.String(instanceNameFlag), - OverridesFile: c.String(overridesFileFlag), + ParseVendor: ctx.Bool(parseVendorFlag), + ParseDependency: ctx.Bool(parseDependencyFlag), + MarkdownFilesDir: ctx.String(markdownFilesFlag), + ParseInternal: ctx.Bool(parseInternalFlag), + GeneratedTime: ctx.Bool(generatedTimeFlag), + CodeExampleFilesDir: ctx.String(codeExampleFilesFlag), + ParseDepth: ctx.Int(parseDepthFlag), + InstanceName: ctx.String(instanceNameFlag), + OverridesFile: ctx.String(overridesFileFlag), }) } @@ -192,8 +192,8 @@ func main() { }, }, } - err := app.Run(os.Args) - if err != nil { + + if err := app.Run(os.Args); err != nil { log.Fatal(err) } } diff --git a/field_parser.go b/field_parser.go index b5cf26242..a00b490dc 100644 --- a/field_parser.go +++ b/field_parser.go @@ -10,10 +10,17 @@ import ( "sync" "unicode" + "github.com/go-openapi/jsonreference" "github.com/go-openapi/spec" ) -var _ FieldParser = &tagBaseFieldParser{} +var _ FieldParser = &tagBaseFieldParser{p: nil, field: nil, tag: ""} + +const ( + requiredLabel = "required" + swaggerTypeTag = "swaggertype" + swaggerIgnoreTag = "swaggerignore" +) type tagBaseFieldParser struct { p *Parser @@ -22,46 +29,47 @@ type tagBaseFieldParser struct { } func newTagBaseFieldParser(p *Parser, field *ast.Field) FieldParser { - ps := &tagBaseFieldParser{ + fieldParser := tagBaseFieldParser{ p: p, field: field, + tag: "", } - if ps.field.Tag != nil { - ps.tag = reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1)) + if fieldParser.field.Tag != nil { + fieldParser.tag = reflect.StructTag(strings.ReplaceAll(field.Tag.Value, "`", "")) } - return ps + return &fieldParser } -func (ps *tagBaseFieldParser) ShouldSkip() (bool, error) { +func (ps *tagBaseFieldParser) ShouldSkip() bool { // Skip non-exported fields. if !ast.IsExported(ps.field.Names[0].Name) { - return true, nil + return true } if ps.field.Tag == nil { - return false, nil + return false } - ignoreTag := ps.tag.Get("swaggerignore") + ignoreTag := ps.tag.Get(swaggerIgnoreTag) if strings.EqualFold(ignoreTag, "true") { - return true, nil + return true } // json:"tag,hoge" - name := strings.TrimSpace(strings.Split(ps.tag.Get("json"), ",")[0]) + name := strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0]) if name == "-" { - return true, nil + return true } - return false, nil + return false } func (ps *tagBaseFieldParser) FieldName() (string, error) { var name string if ps.field.Tag != nil { // json:"tag,hoge" - name = strings.TrimSpace(strings.Split(ps.tag.Get("json"), ",")[0]) + name = strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0]) if name != "" { return name, nil @@ -79,34 +87,40 @@ func (ps *tagBaseFieldParser) FieldName() (string, error) { } func toSnakeCase(in string) string { - runes := []rune(in) - length := len(runes) - - var out []rune - for i := 0; i < length; i++ { - if i > 0 && unicode.IsUpper(runes[i]) && - ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) { + var ( + runes = []rune(in) + length = len(runes) + out []rune + ) + + for idx := 0; idx < length; idx++ { + if idx > 0 && unicode.IsUpper(runes[idx]) && + ((idx+1 < length && unicode.IsLower(runes[idx+1])) || unicode.IsLower(runes[idx-1])) { out = append(out, '_') } - out = append(out, unicode.ToLower(runes[i])) + + out = append(out, unicode.ToLower(runes[idx])) } return string(out) } func toLowerCamelCase(in string) string { - runes := []rune(in) + var flag bool - var out []rune - flag := false + out := make([]rune, len(in)) + + runes := []rune(in) for i, curr := range runes { if (i == 0 && unicode.IsUpper(curr)) || (flag && unicode.IsUpper(curr)) { - out = append(out, unicode.ToLower(curr)) + out[i] = unicode.ToLower(curr) flag = true - } else { - out = append(out, curr) - flag = false + + continue } + + out[i] = curr + flag = false } return string(out) @@ -117,7 +131,7 @@ func (ps *tagBaseFieldParser) CustomSchema() (*spec.Schema, error) { return nil, nil } - typeTag := ps.tag.Get("swaggertype") + typeTag := ps.tag.Get(swaggerTypeTag) if typeTag != "" { return BuildCustomSchema(strings.Split(typeTag, ",")) } @@ -126,7 +140,6 @@ func (ps *tagBaseFieldParser) CustomSchema() (*spec.Schema, error) { } type structField struct { - desc string schemaType string arrayType string formatType string @@ -138,11 +151,8 @@ type structField struct { maxItems *int64 minItems *int64 exampleValue interface{} - defaultValue interface{} - extensions map[string]interface{} enums []interface{} enumVarNames []interface{} - readOnly bool unique bool } @@ -155,32 +165,42 @@ func splitNotWrapped(s string, sep rune) []string { '{': '}', } - result := make([]string, 0) - current := "" - var openCount = 0 - var openChar rune + var ( + result = make([]string, 0) + current = strings.Builder{} + openCount = 0 + openChar rune + ) + for _, char := range s { - if openChar == 0 && openCloseMap[char] != 0 { + switch { + case openChar == 0 && openCloseMap[char] != 0: openChar = char + openCount++ - current += string(char) - } else if char == openChar { + + current.WriteRune(char) + case char == openChar: openCount++ - current = current + string(char) - } else if openCount > 0 && char == openCloseMap[openChar] { + + current.WriteRune(char) + case openCount > 0 && char == openCloseMap[openChar]: openCount-- - current += string(char) - } else if openCount == 0 && char == sep { - result = append(result, current) + + current.WriteRune(char) + case openCount == 0 && char == sep: + result = append(result, current.String()) + openChar = 0 - current = "" - } else { - current += string(char) + + current = strings.Builder{} + default: + current.WriteRune(char) } } - if current != "" { - result = append(result, current) + if current.String() != "" { + result = append(result, current.String()) } return result @@ -196,157 +216,110 @@ func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error { if ps.field.Doc != nil { schema.Description = strings.TrimSpace(ps.field.Doc.Text()) } + if schema.Description == "" && ps.field.Comment != nil { schema.Description = strings.TrimSpace(ps.field.Comment.Text()) } + return nil } - structField := &structField{ + field := &structField{ schemaType: types[0], formatType: ps.tag.Get(formatTag), - readOnly: ps.tag.Get(readOnlyTag) == "true", } if len(types) > 1 && (types[0] == ARRAY || types[0] == OBJECT) { - structField.arrayType = types[1] + field.arrayType = types[1] } - if ps.field.Doc != nil { - structField.desc = strings.TrimSpace(ps.field.Doc.Text()) - } - if structField.desc == "" && ps.field.Comment != nil { - structField.desc = strings.TrimSpace(ps.field.Comment.Text()) - } - - jsonTag := ps.tag.Get(jsonTag) - // json:"name,string" or json:",string" + jsonTagValue := ps.tag.Get(jsonTag) - exampleTag, ok := ps.tag.Lookup(exampleTag) - if ok { - structField.exampleValue = exampleTag - if !strings.Contains(jsonTag, ",string") { - example, err := defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag) - if err != nil { - return err - } - structField.exampleValue = example - } + bindingTagValue := ps.tag.Get(bindingTag) + if bindingTagValue != "" { + parseValidTags(bindingTagValue, field) } - bindingTag := ps.tag.Get(bindingTag) - if bindingTag != "" { - ps.parseValidTags(bindingTag, structField) + validateTagValue := ps.tag.Get(validateTag) + if validateTagValue != "" { + parseValidTags(validateTagValue, field) } - validateTag := ps.tag.Get(validateTag) - if validateTag != "" { - ps.parseValidTags(validateTag, structField) - } - - extensionsTag := ps.tag.Get(extensionsTag) - if extensionsTag != "" { - structField.extensions = map[string]interface{}{} - for _, val := range splitNotWrapped(extensionsTag, ',') { - parts := strings.SplitN(val, "=", 2) - if len(parts) == 2 { - structField.extensions[parts[0]] = parts[1] - } else { - if len(parts[0]) > 0 && string(parts[0][0]) == "!" { - structField.extensions[parts[0][1:]] = false - } else { - structField.extensions[parts[0]] = true - } - } - } - } - - enumsTag := ps.tag.Get(enumsTag) - if enumsTag != "" { - enumType := structField.schemaType - if structField.schemaType == ARRAY { - enumType = structField.arrayType - } - - structField.enums = nil - for _, e := range strings.Split(enumsTag, ",") { - value, err := defineType(enumType, e) - if err != nil { - return err - } - structField.enums = append(structField.enums, value) - } - } - varnamesTag := ps.tag.Get("x-enum-varnames") - if varnamesTag != "" { - if structField.extensions == nil { - structField.extensions = map[string]interface{}{} - } - varNames := strings.Split(varnamesTag, ",") - if len(varNames) != len(structField.enums) { - return fmt.Errorf("invalid count of x-enum-varnames. expected %d, got %d", len(structField.enums), len(varNames)) - } - structField.enumVarNames = nil - for _, v := range varNames { - structField.enumVarNames = append(structField.enumVarNames, v) - } - structField.extensions["x-enum-varnames"] = structField.enumVarNames - } - defaultTag := ps.tag.Get(defaultTag) - if defaultTag != "" { - value, err := defineType(structField.schemaType, defaultTag) + enumsTagValue := ps.tag.Get(enumsTag) + if enumsTagValue != "" { + err := parseEnumTags(enumsTagValue, field) if err != nil { return err } - structField.defaultValue = value } - if IsNumericType(structField.schemaType) || IsNumericType(structField.arrayType) { + if IsNumericType(field.schemaType) || IsNumericType(field.arrayType) { maximum, err := getFloatTag(ps.tag, maximumTag) if err != nil { return err } + if maximum != nil { - structField.maximum = maximum + field.maximum = maximum } minimum, err := getFloatTag(ps.tag, minimumTag) if err != nil { return err } + if minimum != nil { - structField.minimum = minimum + field.minimum = minimum } multipleOf, err := getFloatTag(ps.tag, multipleOfTag) if err != nil { return err } + if multipleOf != nil { - structField.multipleOf = multipleOf + field.multipleOf = multipleOf } } - if structField.schemaType == STRING || structField.arrayType == STRING { - maxLength, err := getIntTag(ps.tag, "maxLength") + if field.schemaType == STRING || field.arrayType == STRING { + maxLength, err := getIntTag(ps.tag, maxLengthTag) if err != nil { return err } + if maxLength != nil { - structField.maxLength = maxLength + field.maxLength = maxLength } - minLength, err := getIntTag(ps.tag, "minLength") + minLength, err := getIntTag(ps.tag, minLengthTag) if err != nil { return err } + if minLength != nil { - structField.minLength = minLength + field.minLength = minLength + } + } + + // json:"name,string" or json:",string" + exampleTagValue, ok := ps.tag.Lookup(exampleTag) + if ok { + field.exampleValue = exampleTagValue + + if !strings.Contains(jsonTagValue, ",string") { + example, err := defineTypeOfExample(field.schemaType, field.arrayType, exampleTagValue) + if err != nil { + return err + } + + field.exampleValue = example } } // perform this after setting everything else (min, max, etc...) - if strings.Contains(jsonTag, ",string") { // @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types." + if strings.Contains(jsonTagValue, ",string") { + // @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types." defaultValues := map[string]string{ // Zero Values as string STRING: "", @@ -355,51 +328,103 @@ func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error { NUMBER: "0", } - defaultValue, ok := defaultValues[structField.schemaType] + defaultValue, ok := defaultValues[field.schemaType] if ok { - structField.schemaType = STRING + field.schemaType = STRING + *schema = *PrimitiveSchema(field.schemaType) - if structField.exampleValue == nil { + if field.exampleValue == nil { // if exampleValue is not defined by the user, // we will force an example with a correct value // (eg: int->"0", bool:"false") - structField.exampleValue = defaultValue + field.exampleValue = defaultValue } } } - if structField.schemaType == STRING && types[0] != STRING { - *schema = *PrimitiveSchema(structField.schemaType) + if ps.field.Doc != nil { + schema.Description = strings.TrimSpace(ps.field.Doc.Text()) } - schema.Description = structField.desc - schema.ReadOnly = structField.readOnly + if schema.Description == "" && ps.field.Comment != nil { + schema.Description = strings.TrimSpace(ps.field.Comment.Text()) + } + + schema.ReadOnly = ps.tag.Get(readOnlyTag) == "true" + if !reflect.ValueOf(schema.Ref).IsZero() && schema.ReadOnly { schema.AllOf = []spec.Schema{*spec.RefSchema(schema.Ref.String())} - schema.Ref = spec.Ref{} // clear out existing ref + schema.Ref = spec.Ref{ + Ref: jsonreference.Ref{ + HasFullURL: false, + HasURLPathOnly: false, + HasFragmentOnly: false, + HasFileScheme: false, + HasFullFilePath: false, + }, + } // clear out existing ref + } + + defaultTagValue := ps.tag.Get(defaultTag) + if defaultTagValue != "" { + value, err := defineType(field.schemaType, defaultTagValue) + if err != nil { + return err + } + + schema.Default = value } - schema.Default = structField.defaultValue - schema.Example = structField.exampleValue - if structField.schemaType != ARRAY { - schema.Format = structField.formatType + + schema.Example = field.exampleValue + + if field.schemaType != ARRAY { + schema.Format = field.formatType + } + + extensionsTagValue := ps.tag.Get(extensionsTag) + if extensionsTagValue != "" { + schema.Extensions = setExtensionParam(extensionsTagValue) } - schema.Extensions = structField.extensions + + varNamesTag := ps.tag.Get("x-enum-varnames") + if varNamesTag != "" { + if schema.Extensions == nil { + schema.Extensions = map[string]interface{}{} + } + + varNames := strings.Split(varNamesTag, ",") + if len(varNames) != len(field.enums) { + return fmt.Errorf("invalid count of x-enum-varnames. expected %d, got %d", len(field.enums), len(varNames)) + } + + field.enumVarNames = nil + + for _, v := range varNames { + field.enumVarNames = append(field.enumVarNames, v) + } + + schema.Extensions["x-enum-varnames"] = field.enumVarNames + } + eleSchema := schema - if structField.schemaType == ARRAY { + + if field.schemaType == ARRAY { // For Array only - schema.MaxItems = structField.maxItems - schema.MinItems = structField.minItems - schema.UniqueItems = structField.unique + schema.MaxItems = field.maxItems + schema.MinItems = field.minItems + schema.UniqueItems = field.unique eleSchema = schema.Items.Schema - eleSchema.Format = structField.formatType - } - eleSchema.Maximum = structField.maximum - eleSchema.Minimum = structField.minimum - eleSchema.MultipleOf = structField.multipleOf - eleSchema.MaxLength = structField.maxLength - eleSchema.MinLength = structField.minLength - eleSchema.Enum = structField.enums + eleSchema.Format = field.formatType + } + + eleSchema.Maximum = field.maximum + eleSchema.Minimum = field.minimum + eleSchema.MultipleOf = field.multipleOf + eleSchema.MaxLength = field.maxLength + eleSchema.MinLength = field.minLength + eleSchema.Enum = field.enums + return nil } @@ -439,7 +464,7 @@ func (ps *tagBaseFieldParser) IsRequired() (bool, error) { bindingTag := ps.tag.Get(bindingTag) if bindingTag != "" { for _, val := range strings.Split(bindingTag, ",") { - if val == "required" { + if val == requiredLabel { return true, nil } } @@ -448,7 +473,7 @@ func (ps *tagBaseFieldParser) IsRequired() (bool, error) { validateTag := ps.tag.Get(validateTag) if validateTag != "" { for _, val := range strings.Split(validateTag, ",") { - if val == "required" { + if val == requiredLabel { return true, nil } } @@ -457,27 +482,24 @@ func (ps *tagBaseFieldParser) IsRequired() (bool, error) { return false, nil } -func (ps *tagBaseFieldParser) parseValidTags(validTag string, sf *structField) { +func parseValidTags(validTag string, sf *structField) { // `validate:"required,max=10,min=1"` // ps. required checked by IsRequired(). for _, val := range strings.Split(validTag, ",") { var ( - valKey string valValue string + keyVal = strings.Split(val, "=") ) - kv := strings.Split(val, "=") - switch len(kv) { + + switch len(keyVal) { case 1: - valKey = kv[0] case 2: - valKey = kv[0] - valValue = kv[1] + valValue = strings.ReplaceAll(strings.ReplaceAll(keyVal[1], utf8HexComma, ","), utf8Pipe, "|") default: continue } - valValue = strings.Replace(strings.Replace(valValue, utf8HexComma, ",", -1), utf8Pipe, "|", -1) - switch valKey { + switch keyVal[0] { case "max", "lte": sf.setMax(valValue) case "min", "gte": @@ -497,6 +519,26 @@ func (ps *tagBaseFieldParser) parseValidTags(validTag string, sf *structField) { } } +func parseEnumTags(enumTag string, field *structField) error { + enumType := field.schemaType + if field.schemaType == ARRAY { + enumType = field.arrayType + } + + field.enums = nil + + for _, e := range strings.Split(enumTag, ",") { + value, err := defineType(enumType, e) + if err != nil { + return err + } + + field.enums = append(field.enums, value) + } + + return nil +} + func (sf *structField) setOneOf(valValue string) { if len(sf.enums) != 0 { return @@ -513,6 +555,7 @@ func (sf *structField) setOneOf(valValue string) { if err != nil { continue } + sf.enums = append(sf.enums, value) } } @@ -522,6 +565,7 @@ func (sf *structField) setMin(valValue string) { if err != nil { return } + switch sf.schemaType { case INTEGER, NUMBER: sf.minimum = &value @@ -539,6 +583,7 @@ func (sf *structField) setMax(valValue string) { if err != nil { return } + switch sf.schemaType { case INTEGER, NUMBER: sf.maximum = &value @@ -558,25 +603,30 @@ const ( // These code copy from // https://github.com/go-playground/validator/blob/d4271985b44b735c6f76abc7a06532ee997f9476/baked_in.go#L207 -// --- +// ---. var oneofValsCache = map[string][]string{} var oneofValsCacheRWLock = sync.RWMutex{} var splitParamsRegex = regexp.MustCompile(`'[^']*'|\S+`) -func parseOneOfParam2(s string) []string { +func parseOneOfParam2(param string) []string { oneofValsCacheRWLock.RLock() - values, ok := oneofValsCache[s] + values, ok := oneofValsCache[param] oneofValsCacheRWLock.RUnlock() + if !ok { oneofValsCacheRWLock.Lock() - values = splitParamsRegex.FindAllString(s, -1) + values = splitParamsRegex.FindAllString(param, -1) + for i := 0; i < len(values); i++ { - values[i] = strings.Replace(values[i], "'", "", -1) + values[i] = strings.ReplaceAll(values[i], "'", "") } - oneofValsCache[s] = values + + oneofValsCache[param] = values + oneofValsCacheRWLock.Unlock() } + return values } -// --- +// ---. diff --git a/format/format.go b/format/format.go index d40454cd8..e881d9d04 100644 --- a/format/format.go +++ b/format/format.go @@ -25,9 +25,6 @@ type Config struct { func (f *Fmt) Build(config *Config) error { log.Println("Formating code.... ") - formater := swag.NewFormater() - if err := formater.FormatAPI(config.SearchDir, config.Excludes, config.MainFile); err != nil { - return err - } - return nil + + return swag.NewFormatter().FormatAPI(config.SearchDir, config.Excludes, config.MainFile) } diff --git a/formater.go b/formatter.go similarity index 62% rename from formater.go rename to formatter.go index 1c903f48c..0b14e99d8 100644 --- a/formater.go +++ b/formatter.go @@ -20,8 +20,8 @@ import ( const splitTag = "&*" -// Formater implements a formater for Go source files. -type Formater struct { +// Formatter implements a formater for Go source files. +type Formatter struct { // debugging output goes here debug Debugger @@ -31,23 +31,42 @@ type Formater struct { mainFile string } -// NewFormater create a new formater instance. +// Formater creates a new formatter. +type Formater struct { + *Formatter +} + +// NewFormater Deprecated: Use NewFormatter instead. func NewFormater() *Formater { - formater := &Formater{ + formatter := Formater{ + Formatter: NewFormatter(), + } + + formatter.debug.Printf("warining: NewFormater is deprecated. use NewFormatter instead") + + return &formatter +} + +// NewFormatter create a new formater instance. +func NewFormatter() *Formatter { + formatter := Formatter{ + mainFile: "", debug: log.New(os.Stdout, "", log.LstdFlags), excludes: make(map[string]struct{}), } - return formater + + return &formatter } // FormatAPI format the swag comment. -func (f *Formater) FormatAPI(searchDir, excludeDir, mainFile string) error { +func (f *Formatter) FormatAPI(searchDir, excludeDir, mainFile string) error { searchDirs := strings.Split(searchDir, ",") for _, searchDir := range searchDirs { if _, err := os.Stat(searchDir); os.IsNotExist(err) { return fmt.Errorf("dir: %s does not exist", searchDir) } } + for _, fi := range strings.Split(excludeDir, ",") { fi = strings.TrimSpace(fi) if fi != "" { @@ -61,10 +80,12 @@ func (f *Formater) FormatAPI(searchDir, excludeDir, mainFile string) error { if err != nil { return err } + err = f.FormatMain(absMainAPIFilePath) if err != nil { return err } + f.mainFile = mainFile err = f.formatMultiSearchDir(searchDirs) @@ -75,7 +96,7 @@ func (f *Formater) FormatAPI(searchDir, excludeDir, mainFile string) error { return nil } -func (f *Formater) formatMultiSearchDir(searchDirs []string) error { +func (f *Formatter) formatMultiSearchDir(searchDirs []string) error { for _, searchDir := range searchDirs { f.debug.Printf("Format API Info, search dir:%s", searchDir) @@ -84,10 +105,11 @@ func (f *Formater) formatMultiSearchDir(searchDirs []string) error { return err } } + return nil } -func (f *Formater) visit(path string, fileInfo os.FileInfo, err error) error { +func (f *Formatter) visit(path string, fileInfo os.FileInfo, err error) error { if err := walkWith(f.excludes, false)(path, fileInfo); err != nil { return err } else if fileInfo.IsDir() { @@ -99,6 +121,7 @@ func (f *Formater) visit(path string, fileInfo os.FileInfo, err error) error { // skip if file not has suffix "*.go" return nil } + if strings.HasSuffix(strings.ToLower(path), f.mainFile) { // skip main file return nil @@ -108,16 +131,19 @@ func (f *Formater) visit(path string, fileInfo os.FileInfo, err error) error { if err != nil { return fmt.Errorf("ParseFile error:%+v", err) } + return nil } // FormatMain format the main.go comment. -func (f *Formater) FormatMain(mainFilepath string) error { +func (f *Formatter) FormatMain(mainFilepath string) error { fileSet := token.NewFileSet() + astFile, err := goparser.ParseFile(fileSet, mainFilepath, nil, goparser.ParseComments) if err != nil { return fmt.Errorf("cannot format file, err: %w path : %s ", err, mainFilepath) } + var ( formatedComments = bytes.Buffer{} // CommentCache @@ -130,12 +156,13 @@ func (f *Formater) FormatMain(mainFilepath string) error { } } - return writeFormatedComments(mainFilepath, formatedComments, oldCommentsMap) + return writeFormattedComments(mainFilepath, formatedComments, oldCommentsMap) } // FormatFile format the swag comment in go function. -func (f *Formater) FormatFile(filepath string) error { +func (f *Formatter) FormatFile(filepath string) error { fileSet := token.NewFileSet() + astFile, err := goparser.ParseFile(fileSet, filepath, nil, goparser.ParseComments) if err != nil { return fmt.Errorf("cannot format file, err: %w path : %s ", err, filepath) @@ -154,18 +181,19 @@ func (f *Formater) FormatFile(filepath string) error { } } - return writeFormatedComments(filepath, formatedComments, oldCommentsMap) + return writeFormattedComments(filepath, formatedComments, oldCommentsMap) } -func writeFormatedComments(filepath string, formatedComments bytes.Buffer, oldCommentsMap map[string]string) error { +func writeFormattedComments(filepath string, formatedComments bytes.Buffer, oldCommentsMap map[string]string) error { // Replace the file // Read the file srcBytes, err := ioutil.ReadFile(filepath) if err != nil { return fmt.Errorf("cannot open file, err: %w path : %s ", err, filepath) } - replaceSrc := string(srcBytes) - newComments := strings.Split(formatedComments.String(), "\n") + + replaceSrc, newComments := string(srcBytes), strings.Split(formatedComments.String(), "\n") + for _, e := range newComments { commentSplit := strings.Split(e, splitTag) if len(commentSplit) == 2 { @@ -176,11 +204,12 @@ func writeFormatedComments(filepath string, formatedComments bytes.Buffer, oldCo } } } + return writeBack(filepath, []byte(replaceSrc), srcBytes) } -func formatFuncDoc(commentList []*ast.Comment, formatedComments io.Writer, oldCommentsMap map[string]string) { - tabw := tabwriter.NewWriter(formatedComments, 0, 0, 2, ' ', 0) +func formatFuncDoc(commentList []*ast.Comment, formattedComments io.Writer, oldCommentsMap map[string]string) { + tabWriter := tabwriter.NewWriter(formattedComments, 0, 0, 2, ' ', 0) for _, comment := range commentList { commentLine := comment.Text @@ -193,72 +222,96 @@ func formatFuncDoc(commentList []*ast.Comment, formatedComments io.Writer, oldCo // md5 + splitTag + srcCommentLine // eg. xxx&*@Description get struct array - _, _ = fmt.Fprintln(tabw, cmd5+splitTag+c) + _, _ = fmt.Fprintln(tabWriter, cmd5+splitTag+c) } } - // format by tabwriter - _ = tabw.Flush() -} - -// Check of @Param @Success @Failure @Response @Header -var specialTagForSplit = map[string]byte{ - paramAttr: 1, - successAttr: 1, - failureAttr: 1, - responseAttr: 1, - headerAttr: 1, -} - -var skipChar = map[byte]byte{ - '"': 1, - '(': 1, - '{': 1, - '[': 1, + // format by tabWriter + _ = tabWriter.Flush() } -var skipCharEnd = map[byte]byte{ - '"': 1, - ')': 1, - '}': 1, - ']': 1, -} +func separatorFinder(comment string, replacer byte) string { + commentBytes, commentLine := []byte(comment), strings.TrimSpace(strings.TrimLeft(comment, "/")) -func separatorFinder(comment string, rp byte) string { - commentBytes := []byte(comment) - commentLine := strings.TrimSpace(strings.TrimLeft(comment, "/")) if len(commentLine) == 0 { return "" } + attribute := strings.Fields(commentLine)[0] attrLen := strings.Index(comment, attribute) + len(attribute) attribute = strings.ToLower(attribute) - var i = attrLen - - if _, ok := specialTagForSplit[attribute]; ok { - var skipFlag bool - for ; i < len(commentBytes); i++ { - if !skipFlag && commentBytes[i] == ' ' { - j := i - for j < len(commentBytes) && commentBytes[j] == ' ' { - j++ - } - commentBytes = replaceRange(commentBytes, i, j, rp) - } - if _, ok := skipChar[commentBytes[i]]; ok && !skipFlag { - skipFlag = true - } else if _, ok := skipCharEnd[commentBytes[i]]; ok && skipFlag { - skipFlag = false + + var ( + length = attrLen + + // Check of @Param @Success @Failure @Response @Header. + specialTagForSplit = map[string]byte{ + paramAttr: 1, + successAttr: 1, + failureAttr: 1, + responseAttr: 1, + headerAttr: 1, + } + ) + + _, ok := specialTagForSplit[attribute] + if ok { + return splitSpecialTags(commentBytes, length, replacer) + } + + for length < len(commentBytes) && commentBytes[length] == ' ' { + length++ + } + + if length >= len(commentBytes) { + return comment + } + + commentBytes = replaceRange(commentBytes, attrLen, length, replacer) + + return string(commentBytes) +} + +func splitSpecialTags(commentBytes []byte, length int, rp byte) string { + var ( + skipFlag bool + skipChar = map[byte]byte{ + '"': 1, + '(': 1, + '{': 1, + '[': 1, + } + + skipCharEnd = map[byte]byte{ + '"': 1, + ')': 1, + '}': 1, + ']': 1, + } + ) + + for ; length < len(commentBytes); length++ { + if !skipFlag && commentBytes[length] == ' ' { + j := length + for j < len(commentBytes) && commentBytes[j] == ' ' { + j++ } + + commentBytes = replaceRange(commentBytes, length, j, rp) } - } else { - for i < len(commentBytes) && commentBytes[i] == ' ' { - i++ + + _, found := skipChar[commentBytes[length]] + if found && !skipFlag { + skipFlag = true + + continue } - if i >= len(commentBytes) { - return comment + + _, found = skipCharEnd[commentBytes[length]] + if found && skipFlag { + skipFlag = false } - commentBytes = replaceRange(commentBytes, attrLen, i, rp) } + return string(commentBytes) } @@ -266,11 +319,15 @@ func replaceRange(s []byte, start, end int, new byte) []byte { if start > end || end < 1 { return s } + if end > len(s) { end = len(s) } + s = append(s[:start], s[end-1:]...) + s[start] = new + return s } @@ -281,23 +338,26 @@ func isSwagComment(comment string) bool { } func isBlankComment(comment string) bool { - lc := strings.TrimSpace(comment) - return len(lc) == 0 + return len(strings.TrimSpace(comment)) == 0 } -// writeBack write to file +// writeBack write to file. func writeBack(filepath string, src, old []byte) error { // make a temporary backup before overwriting original - bakname, err := backupFile(filepath+".", old, 0644) + backupName, err := backupFile(filepath+".", old, 0644) if err != nil { return err } + err = ioutil.WriteFile(filepath, src, 0644) if err != nil { - _ = os.Rename(bakname, filepath) + _ = os.Rename(backupName, filepath) + return err } - _ = os.Remove(bakname) + + _ = os.Remove(backupName) + return nil } @@ -306,21 +366,23 @@ const chmodSupported = runtime.GOOS != "windows" // backupFile writes data to a new file named filename with permissions perm, // with 0 && - IsSimplePrimitiveType(prop.Items.Schema.Type[0]): + case prop.Type[0] == ARRAY && prop.Items.Schema != nil && + len(prop.Items.Schema.Type) > 0 && IsSimplePrimitiveType(prop.Items.Schema.Type[0]): + param = createParameter(paramType, prop.Description, name, prop.Type[0], findInSlice(schema.Required, name)) param.SimpleSchema.Type = prop.Type[0] + if operation.parser != nil && operation.parser.collectionFormatInQuery != "" && param.CollectionFormat == "" { param.CollectionFormat = TransToValidCollectionFormat(operation.parser.collectionFormatInQuery) } + param.SimpleSchema.Items = &spec.Items{ SimpleSchema: spec.SimpleSchema{ Type: prop.Items.Schema.Type[0], @@ -309,6 +358,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F continue } + param.Nullable = prop.Nullable param.Format = prop.Format param.Default = prop.Default @@ -339,16 +389,18 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F if err != nil { return err } + param.Schema = schema } default: return fmt.Errorf("%s is not supported paramType", paramType) } - err := operation.parseAndExtractionParamAttribute(commentLine, objectType, refType, ¶m) + err := operation.parseParamAttribute(commentLine, objectType, refType, ¶m) if err != nil { return err } + operation.Operation.Parameters = append(operation.Operation.Parameters, param) return nil @@ -365,8 +417,8 @@ const ( validateTag = "validate" minimumTag = "minimum" maximumTag = "maximum" - minLengthTag = "minlength" - maxLengthTag = "maxlength" + minLengthTag = "minLength" + maxLengthTag = "maxLength" multipleOfTag = "multipleOf" readOnlyTag = "readonly" extensionsTag = "extensions" @@ -398,22 +450,24 @@ var regexAttributes = map[string]*regexp.Regexp{ schemaExampleTag: regexp.MustCompile(`(?i)\s+schemaExample\(.*\)`), } -func (operation *Operation) parseAndExtractionParamAttribute(commentLine, objectType, schemaType string, param *spec.Parameter) error { +func (operation *Operation) parseParamAttribute(comment, objectType, schemaType string, param *spec.Parameter) error { schemaType = TransToValidSchemeType(schemaType) + for attrKey, re := range regexAttributes { - attr, err := findAttr(re, commentLine) + attr, err := findAttr(re, comment) if err != nil { continue } + switch attrKey { case enumsTag: err = setEnumParam(param, attr, objectType, schemaType) case minimumTag, maximumTag: - err = setNumberParam(param, attrKey, schemaType, attr, commentLine) + err = setNumberParam(param, attrKey, schemaType, attr, comment) case defaultTag: err = setDefault(param, schemaType, attr) case minLengthTag, maxLengthTag: - err = setStringParam(param, attrKey, schemaType, attr, commentLine) + err = setStringParam(param, attrKey, schemaType, attr, comment) case formatTag: param.Format = attr case exampleTag: @@ -421,10 +475,11 @@ func (operation *Operation) parseAndExtractionParamAttribute(commentLine, object case schemaExampleTag: err = setSchemaExample(param, schemaType, attr) case extensionsTag: - _ = setExtensionParam(param, attr) + param.Extensions = setExtensionParam(attr) case collectionFormatTag: - err = setCollectionFormatParam(param, attrKey, objectType, attr, commentLine) + err = setCollectionFormatParam(param, attrKey, objectType, attr, comment) } + if err != nil { return err } @@ -435,8 +490,8 @@ func (operation *Operation) parseAndExtractionParamAttribute(commentLine, object func findAttr(re *regexp.Regexp, commentLine string) (string, error) { attr := re.FindString(commentLine) - l := strings.Index(attr, "(") - r := strings.Index(attr, ")") + + l, r := strings.Index(attr, "("), strings.Index(attr, ")") if l == -1 || r == -1 { return "", fmt.Errorf("can not find regex=%s, comment=%s", re.String(), commentLine) } @@ -471,12 +526,14 @@ func setNumberParam(param *spec.Parameter, name, schemaType, attr, commentLine s if err != nil { return fmt.Errorf("maximum is allow only a number. comment=%s got=%s", commentLine, attr) } + switch name { case minimumTag: param.Minimum = &n case maximumTag: param.Maximum = &n } + return nil default: return fmt.Errorf("%s is attribute to set to a number. comment=%s got=%s", name, commentLine, schemaType) @@ -503,23 +560,33 @@ func setEnumParam(param *spec.Parameter, attr, objectType, schemaType string) er return nil } -func setExtensionParam(param *spec.Parameter, attr string) error { - param.Extensions = map[string]interface{}{} +func setExtensionParam(attr string) spec.Extensions { + extensions := spec.Extensions{} + for _, val := range splitNotWrapped(attr, ',') { parts := strings.SplitN(val, "=", 2) if len(parts) == 2 { - param.Extensions.Add(parts[0], parts[1]) + extensions.Add(parts[0], parts[1]) + + continue + } + + if len(parts[0]) > 0 && string(parts[0][0]) == "!" { + extensions.Add(parts[0][1:], false) continue } - param.Extensions.Add(parts[0], true) + + extensions.Add(parts[0], true) } - return nil + + return extensions } func setCollectionFormatParam(param *spec.Parameter, name, schemaType, attr, commentLine string) error { if schemaType == ARRAY { param.CollectionFormat = TransToValidCollectionFormat(attr) + return nil } @@ -531,13 +598,12 @@ func setDefault(param *spec.Parameter, schemaType string, value string) error { if err != nil { return nil // Don't set a default value if it's not valid } + param.Default = val + return nil } -// controlCharReplacer replaces \r \n \t in example string values -var controlCharReplacer = strings.NewReplacer(`\r`, "\r", `\n`, "\n", `\t`, "\t") - func setSchemaExample(param *spec.Parameter, schemaType string, value string) error { val, err := defineType(schemaType, value) if err != nil { @@ -550,7 +616,8 @@ func setSchemaExample(param *spec.Parameter, schemaType string, value string) er switch v := val.(type) { case string: - param.Schema.Example = controlCharReplacer.Replace(v) + // replaces \r \n \t in example string values. + param.Schema.Example = strings.NewReplacer(`\r`, "\r", `\n`, "\n", `\t`, "\t").Replace(v) default: param.Schema.Example = val } @@ -563,13 +630,16 @@ func setExample(param *spec.Parameter, schemaType string, value string) error { if err != nil { return nil // Don't set a example value if it's not valid } + param.Example = val + return nil } // defineType enum value define the type (object and array unsupported). func defineType(schemaType string, value string) (v interface{}, err error) { schemaType = TransToValidSchemeType(schemaType) + switch schemaType { case STRING: return value, nil @@ -597,8 +667,7 @@ func defineType(schemaType string, value string) (v interface{}, err error) { // ParseTagsComment parses comment for given `tag` comment string. func (operation *Operation) ParseTagsComment(commentLine string) { - tags := strings.Split(commentLine, ",") - for _, tag := range tags { + for _, tag := range strings.Split(commentLine, ",") { operation.Tags = append(operation.Tags, strings.TrimSpace(tag)) } } @@ -617,13 +686,13 @@ func (operation *Operation) ParseProduceComment(commentLine string) error { // `produce` (`Content-Type:` response header) or // `accept` (`Accept:` request header). func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) error { - mimeTypes := strings.Split(mimeTypeList, ",") - for _, typeName := range mimeTypes { + for _, typeName := range strings.Split(mimeTypeList, ",") { if mimeTypePattern.MatchString(typeName) { *typeList = append(*typeList, typeName) continue } + aliasMimeType, ok := mimeTypeAliases[typeName] if !ok { return fmt.Errorf(format, typeName) @@ -643,6 +712,7 @@ func (operation *Operation) ParseRouterComment(commentLine string) error { if len(matches) != 3 { return fmt.Errorf("can not parse router comment \"%s\"", commentLine) } + signature := RouteProperties{ Path: matches[1], HTTPMethod: strings.ToUpper(matches[2]), @@ -659,35 +729,41 @@ func (operation *Operation) ParseRouterComment(commentLine string) error { // ParseSecurityComment parses comment for given `security` comment string. func (operation *Operation) ParseSecurityComment(commentLine string) error { - //var securityMap map[string][]string = map[string][]string{} + var ( + securityMap = make(map[string][]string) + securitySource = commentLine[strings.Index(commentLine, "@Security")+1:] + ) - var securityMap = make(map[string][]string) - securitySource := commentLine[strings.Index(commentLine, "@Security")+1:] for _, securityOption := range strings.Split(securitySource, "||") { securityOption = strings.TrimSpace(securityOption) - l := strings.Index(securityOption, "[") - r := strings.Index(securityOption, "]") - if !(l == -1 && r == -1) { - scopes := securityOption[l+1 : r] - var s []string + + left, right := strings.Index(securityOption, "["), strings.Index(securityOption, "]") + + if !(left == -1 && right == -1) { + scopes := securityOption[left+1 : right] + + var options []string + for _, scope := range strings.Split(scopes, ",") { - s = append(s, strings.TrimSpace(scope)) + options = append(options, strings.TrimSpace(scope)) } - securityKey := securityOption[0:l] - securityMap[securityKey] = append(securityMap[securityKey], s...) + securityKey := securityOption[0:left] + securityMap[securityKey] = append(securityMap[securityKey], options...) } else { securityKey := strings.TrimSpace(securityOption) securityMap[securityKey] = []string{} } } + operation.Security = append(operation.Security, securityMap) + return nil } // findTypeDef attempts to find the *ast.TypeSpec for a specific type given the // type's name and the package's import path. -// TODO: improve finding external pkg +// TODO: improve finding external pkg. func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { cwd, err := os.Getwd() if err != nil { @@ -723,7 +799,6 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { } // TODO: possibly cache pkgInfo since it's an expensive operation - for i := range pkgInfo.Files { for _, astDeclaration := range pkgInfo.Files[i].Decls { generalDeclaration, ok := astDeclaration.(*ast.GenDecl) @@ -743,7 +818,7 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { return nil, fmt.Errorf("type spec not found") } -var responsePattern = regexp.MustCompile(`^([\w,]+)[\s]+([\w{}]+)[\s]+([\w\-.\\{}=,\[\]]+)[^"]*(.*)?`) +var responsePattern = regexp.MustCompile(`^([\w,]+)\s+([\w{}]+)\s+([\w\-.\\{}=,\[\]]+)[^"]*(.*)?`) // ResponseType{data1=Type1,data2=Type2}. var combinedPattern = regexp.MustCompile(`^([\w\-./\[\]]+){(.*)}$`) @@ -752,9 +827,9 @@ func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) switch { case refType == NIL: return nil, nil - case refType == "interface{}": + case refType == INTERFACE: return PrimitiveSchema(OBJECT), nil - case refType == "any": + case refType == ANY: return PrimitiveSchema(OBJECT), nil case IsGolangPrimitiveType(refType): refType = TransToValidSchemeType(refType) @@ -775,10 +850,12 @@ func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) if idx < 0 { return nil, fmt.Errorf("invalid type: %s", refType) } + refType = refType[idx+1:] - if refType == "interface{}" || refType == "any" { + if refType == INTERFACE || refType == ANY { return spec.MapProperty(nil), nil } + schema, err := operation.parseObjectSchema(refType, astFile) if err != nil { return nil, err @@ -801,45 +878,46 @@ func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) } } +func parseFields(s string) []string { + nestLevel := 0 + + return strings.FieldsFunc(s, func(char rune) bool { + if char == '{' { + nestLevel++ + + return false + } else if char == '}' { + nestLevel-- + + return false + } + + return char == ',' && nestLevel == 0 + }) +} + func (operation *Operation) parseCombinedObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) { matches := combinedPattern.FindStringSubmatch(refType) if len(matches) != 3 { return nil, fmt.Errorf("invalid type: %s", refType) } - refType = matches[1] - schema, err := operation.parseObjectSchema(refType, astFile) + + schema, err := operation.parseObjectSchema(matches[1], astFile) if err != nil { return nil, err } - parseFields := func(s string) []string { - n := 0 - - return strings.FieldsFunc(s, func(r rune) bool { - if r == '{' { - n++ - - return false - } else if r == '}' { - n-- - - return false - } - - return r == ',' && n == 0 - }) - } + fields, props := parseFields(matches[2]), map[string]spec.Schema{} - fields := parseFields(matches[2]) - props := map[string]spec.Schema{} for _, field := range fields { - matches := strings.SplitN(field, "=", 2) - if len(matches) == 2 { - schema, err := operation.parseObjectSchema(matches[1], astFile) + keyVal := strings.SplitN(field, "=", 2) + if len(keyVal) == 2 { + schema, err := operation.parseObjectSchema(keyVal[1], astFile) if err != nil { return nil, err } - props[matches[0]] = *schema + + props[keyVal[0]] = *schema } } @@ -861,6 +939,7 @@ func (operation *Operation) parseAPIObjectSchema(schemaType, refType string, ast if !strings.HasPrefix(refType, "[]") { return operation.parseObjectSchema(refType, astFile) } + refType = refType[2:] fallthrough @@ -889,6 +968,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as } description := strings.Trim(matches[4], "\"") + schema, err := operation.parseAPIObjectSchema(strings.Trim(matches[2], "{}"), matches[3], astFile) if err != nil { return err @@ -900,6 +980,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as continue } + code, err := strconv.Atoi(codeStr) if err != nil { return fmt.Errorf("can not parse response comment \"%s\"", commentLine) @@ -924,6 +1005,23 @@ func newHeaderSpec(schemaType, description string) spec.Header { HeaderProps: spec.HeaderProps{ Description: description, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: nil, + }, + CommonValidations: spec.CommonValidations{ + Maximum: nil, + ExclusiveMaximum: false, + Minimum: nil, + ExclusiveMinimum: false, + MaxLength: nil, + MinLength: nil, + Pattern: "", + MaxItems: nil, + MinItems: nil, + UniqueItems: false, + MultipleOf: nil, + Enum: nil, + }, } } @@ -966,6 +1064,7 @@ func (operation *Operation) ParseResponseHeaderComment(commentLine string, _ *as if err != nil { return fmt.Errorf("can not parse response comment \"%s\"", commentLine) } + if operation.Responses.StatusCodeResponses != nil { response, responseExist := operation.Responses.StatusCodeResponses[code] if responseExist { @@ -979,7 +1078,7 @@ func (operation *Operation) ParseResponseHeaderComment(commentLine string, _ *as return nil } -var emptyResponsePattern = regexp.MustCompile(`([\w,]+)[\s]+"(.*)"`) +var emptyResponsePattern = regexp.MustCompile(`([\w,]+)\s+"(.*)"`) // ParseEmptyResponseComment parse only comment out status code and description,eg: @Success 200 "it's ok". func (operation *Operation) ParseEmptyResponseComment(commentLine string) error { @@ -989,6 +1088,7 @@ func (operation *Operation) ParseEmptyResponseComment(commentLine string) error } description := strings.Trim(matches[2], "\"") + for _, codeStr := range strings.Split(matches[1], ",") { if strings.EqualFold(codeStr, defaultTag) { operation.DefaultResponse().WithDescription(description) @@ -1015,6 +1115,7 @@ func (operation *Operation) ParseEmptyResponseOnly(commentLine string) error { continue } + code, err := strconv.Atoi(codeStr) if err != nil { return fmt.Errorf("can not parse response comment \"%s\"", commentLine) @@ -1031,7 +1132,8 @@ func (operation *Operation) DefaultResponse() *spec.Response { if operation.Responses.Default == nil { operation.Responses.Default = &spec.Response{ ResponseProps: spec.ResponseProps{ - Headers: make(map[string]spec.Header), + Description: "", + Headers: make(map[string]spec.Header), }, } } @@ -1044,6 +1146,7 @@ func (operation *Operation) AddResponse(code int, response *spec.Response) { if response.Headers == nil { response.Headers = make(map[string]spec.Header) } + operation.Responses.StatusCodeResponses[code] = *response } @@ -1052,10 +1155,12 @@ func createParameter(paramType, description, paramName, schemaType string, requi // //five possible parameter types. query, path, body, header, form result := spec.Parameter{ ParamProps: spec.ParamProps{ - Name: paramName, - Description: description, - Required: required, - In: paramType, + Name: paramName, + Description: description, + Required: required, + In: paramType, + Schema: nil, + AllowEmptyValue: false, }, } @@ -1070,7 +1175,9 @@ func createParameter(paramType, description, paramName, schemaType string, requi } result.SimpleSchema = spec.SimpleSchema{ - Type: schemaType, + Type: schemaType, + Nullable: false, + Format: "", } return result @@ -1086,6 +1193,7 @@ func getCodeExampleForSummary(summaryName string, dirPath string) ([]byte, error if fileInfo.IsDir() { continue } + fileName := fileInfo.Name() if !strings.Contains(fileName, ".json") { @@ -1094,6 +1202,7 @@ func getCodeExampleForSummary(summaryName string, dirPath string) ([]byte, error if strings.Contains(fileName, summaryName) { fullPath := filepath.Join(dirPath, fileName) + commentInfo, err := ioutil.ReadFile(fullPath) if err != nil { return nil, fmt.Errorf("Failed to read code example file %s error: %s ", fullPath, err) diff --git a/operation_test.go b/operation_test.go index e9e43e8c8..d8089f0a2 100644 --- a/operation_test.go +++ b/operation_test.go @@ -1964,7 +1964,7 @@ func TestParseAndExtractionParamAttribute(t *testing.T) { op := NewOperation(nil) numberParam := spec.Parameter{} - err := op.parseAndExtractionParamAttribute( + err := op.parseParamAttribute( " default(1) maximum(100) minimum(0) format(csv)", "", NUMBER, @@ -1976,14 +1976,14 @@ func TestParseAndExtractionParamAttribute(t *testing.T) { assert.Equal(t, "csv", numberParam.SimpleSchema.Format) assert.Equal(t, float64(1), numberParam.Default) - err = op.parseAndExtractionParamAttribute(" minlength(1)", "", NUMBER, nil) + err = op.parseParamAttribute(" minlength(1)", "", NUMBER, nil) assert.Error(t, err) - err = op.parseAndExtractionParamAttribute(" maxlength(1)", "", NUMBER, nil) + err = op.parseParamAttribute(" maxlength(1)", "", NUMBER, nil) assert.Error(t, err) stringParam := spec.Parameter{} - err = op.parseAndExtractionParamAttribute( + err = op.parseParamAttribute( " default(test) maxlength(100) minlength(0) format(csv)", "", STRING, @@ -1993,21 +1993,21 @@ func TestParseAndExtractionParamAttribute(t *testing.T) { assert.Equal(t, int64(0), *stringParam.MinLength) assert.Equal(t, int64(100), *stringParam.MaxLength) assert.Equal(t, "csv", stringParam.SimpleSchema.Format) - err = op.parseAndExtractionParamAttribute(" minimum(0)", "", STRING, nil) + err = op.parseParamAttribute(" minimum(0)", "", STRING, nil) assert.Error(t, err) - err = op.parseAndExtractionParamAttribute(" maximum(0)", "", STRING, nil) + err = op.parseParamAttribute(" maximum(0)", "", STRING, nil) assert.Error(t, err) arrayParram := spec.Parameter{} - err = op.parseAndExtractionParamAttribute(" collectionFormat(tsv)", ARRAY, STRING, &arrayParram) + err = op.parseParamAttribute(" collectionFormat(tsv)", ARRAY, STRING, &arrayParram) assert.Equal(t, "tsv", arrayParram.CollectionFormat) assert.NoError(t, err) - err = op.parseAndExtractionParamAttribute(" collectionFormat(tsv)", STRING, STRING, nil) + err = op.parseParamAttribute(" collectionFormat(tsv)", STRING, STRING, nil) assert.Error(t, err) - err = op.parseAndExtractionParamAttribute(" default(0)", "", ARRAY, nil) + err = op.parseParamAttribute(" default(0)", "", ARRAY, nil) assert.NoError(t, err) } diff --git a/packages.go b/packages.go index c454f0652..dd1a0e6c7 100644 --- a/packages.go +++ b/packages.go @@ -29,13 +29,13 @@ func NewPackagesDefinitions() *PackagesDefinitions { } // CollectAstFile collect ast.file. -func (pkgs *PackagesDefinitions) CollectAstFile(packageDir, path string, astFile *ast.File) error { - if pkgs.files == nil { - pkgs.files = make(map[*ast.File]*AstFileInfo) +func (pkgDefs *PackagesDefinitions) CollectAstFile(packageDir, path string, astFile *ast.File) error { + if pkgDefs.files == nil { + pkgDefs.files = make(map[*ast.File]*AstFileInfo) } - if pkgs.packages == nil { - pkgs.packages = make(map[string]*PackageDefinitions) + if pkgDefs.packages == nil { + pkgDefs.packages = make(map[string]*PackageDefinitions) } // return without storing the file if we lack a packageDir @@ -48,23 +48,24 @@ func (pkgs *PackagesDefinitions) CollectAstFile(packageDir, path string, astFile return err } - pd, ok := pkgs.packages[packageDir] + dependency, ok := pkgDefs.packages[packageDir] if ok { // return without storing the file if it already exists - _, exists := pd.Files[path] + _, exists := dependency.Files[path] if exists { return nil } - pd.Files[path] = astFile + + dependency.Files[path] = astFile } else { - pkgs.packages[packageDir] = &PackageDefinitions{ + pkgDefs.packages[packageDir] = &PackageDefinitions{ Name: astFile.Name.Name, Files: map[string]*ast.File{path: astFile}, TypeDefinitions: make(map[string]*TypeSpecDef), } } - pkgs.files[astFile] = &AstFileInfo{ + pkgDefs.files[astFile] = &AstFileInfo{ File: astFile, Path: path, PackagePath: packageDir, @@ -96,15 +97,15 @@ func rangeFiles(files map[*ast.File]*AstFileInfo, handle func(filename string, f // ParseTypes parse types // @Return parsed definitions. -func (pkgs *PackagesDefinitions) ParseTypes() (map[*TypeSpecDef]*Schema, error) { +func (pkgDefs *PackagesDefinitions) ParseTypes() (map[*TypeSpecDef]*Schema, error) { parsedSchemas := make(map[*TypeSpecDef]*Schema) - for astFile, info := range pkgs.files { - pkgs.parseTypesFromFile(astFile, info.PackagePath, parsedSchemas) + for astFile, info := range pkgDefs.files { + pkgDefs.parseTypesFromFile(astFile, info.PackagePath, parsedSchemas) } return parsedSchemas, nil } -func (pkgs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packagePath string, parsedSchemas map[*TypeSpecDef]*Schema) { +func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packagePath string, parsedSchemas map[*TypeSpecDef]*Schema) { for _, astDeclaration := range astFile.Decls { if generalDeclaration, ok := astDeclaration.(*ast.GenDecl); ok && generalDeclaration.Tok == token.TYPE { for _, astSpec := range generalDeclaration.Specs { @@ -123,29 +124,29 @@ func (pkgs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packagePa } } - if pkgs.uniqueDefinitions == nil { - pkgs.uniqueDefinitions = make(map[string]*TypeSpecDef) + if pkgDefs.uniqueDefinitions == nil { + pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef) } fullName := typeSpecDef.FullName() - anotherTypeDef, ok := pkgs.uniqueDefinitions[fullName] + anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { continue } else { - delete(pkgs.uniqueDefinitions, fullName) + delete(pkgDefs.uniqueDefinitions, fullName) } } else { - pkgs.uniqueDefinitions[fullName] = typeSpecDef + pkgDefs.uniqueDefinitions[fullName] = typeSpecDef } - if pkgs.packages[typeSpecDef.PkgPath] == nil { - pkgs.packages[typeSpecDef.PkgPath] = &PackageDefinitions{ + if pkgDefs.packages[typeSpecDef.PkgPath] == nil { + pkgDefs.packages[typeSpecDef.PkgPath] = &PackageDefinitions{ Name: astFile.Name.Name, TypeDefinitions: map[string]*TypeSpecDef{typeSpecDef.Name(): typeSpecDef}, } - } else if _, ok = pkgs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()]; !ok { - pkgs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()] = typeSpecDef + } else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()]; !ok { + pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()] = typeSpecDef } } } @@ -153,11 +154,12 @@ func (pkgs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packagePa } } -func (pkgs *PackagesDefinitions) findTypeSpec(pkgPath string, typeName string) *TypeSpecDef { - if pkgs.packages == nil { +func (pkgDefs *PackagesDefinitions) findTypeSpec(pkgPath string, typeName string) *TypeSpecDef { + if pkgDefs.packages == nil { return nil } - pd, found := pkgs.packages[pkgPath] + + pd, found := pkgDefs.packages[pkgPath] if found { typeSpec, ok := pd.TypeDefinitions[typeName] if ok { @@ -168,7 +170,7 @@ func (pkgs *PackagesDefinitions) findTypeSpec(pkgPath string, typeName string) * return nil } -func (pkgs *PackagesDefinitions) loadExternalPackage(importPath string) error { +func (pkgDefs *PackagesDefinitions) loadExternalPackage(importPath string) error { cwd, err := os.Getwd() if err != nil { return err @@ -189,7 +191,7 @@ func (pkgs *PackagesDefinitions) loadExternalPackage(importPath string) error { for _, info := range loaderProgram.AllPackages { pkgPath := strings.TrimPrefix(info.Pkg.Path(), "vendor/") for _, astFile := range info.Files { - pkgs.parseTypesFromFile(astFile, pkgPath, nil) + pkgDefs.parseTypesFromFile(astFile, pkgPath, nil) } } @@ -201,7 +203,7 @@ func (pkgs *PackagesDefinitions) loadExternalPackage(importPath string) error { // @file current ast.File in which to search imports // @fuzzy search for the package path that the last part matches the @pkg if true // @return the package path of a package of @pkg. -func (pkgs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *ast.File, fuzzy bool) string { +func (pkgDefs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *ast.File, fuzzy bool) string { if file == nil { return "" } @@ -214,6 +216,7 @@ func (pkgs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *as matchLastPathPart := func(pkgPath string) bool { paths := strings.Split(pkgPath, "/") + return paths[len(paths)-1] == pkg } @@ -223,26 +226,33 @@ func (pkgs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *as if imp.Name.Name == pkg { return strings.Trim(imp.Path.Value, `"`) } + if imp.Name.Name == "_" { hasAnonymousPkg = true } continue } - if pkgs.packages != nil { + + if pkgDefs.packages != nil { path := strings.Trim(imp.Path.Value, `"`) if fuzzy { if matchLastPathPart(path) { return path } - } else if pd, ok := pkgs.packages[path]; ok && pd.Name == pkg { + + continue + } + + pd, ok := pkgDefs.packages[path] + if ok && pd.Name == pkg { return path } } } // match unnamed package - if hasAnonymousPkg && pkgs.packages != nil { + if hasAnonymousPkg && pkgDefs.packages != nil { for _, imp := range file.Imports { if imp.Name == nil { continue @@ -253,7 +263,7 @@ func (pkgs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *as if matchLastPathPart(path) { return path } - } else if pd, ok := pkgs.packages[path]; ok && pd.Name == pkg { + } else if pd, ok := pkgDefs.packages[path]; ok && pd.Name == pkg { return path } } @@ -267,12 +277,13 @@ func (pkgs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *as // @typeName the name of the target type, if it starts with a package name, find its own package path from imports on top of @file // @file the ast.file in which @typeName is used // @pkgPath the package path of @file. -func (pkgs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File, parseDependency bool) *TypeSpecDef { +func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File, parseDependency bool) *TypeSpecDef { if IsGolangPrimitiveType(typeName) { return nil } + if file == nil { // for test - return pkgs.uniqueDefinitions[typeName] + return pkgDefs.uniqueDefinitions[typeName] } parts := strings.Split(typeName, ".") @@ -290,42 +301,43 @@ func (pkgs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File, p } if !isAliasPkgName(file, parts[0]) { - typeDef, ok := pkgs.uniqueDefinitions[typeName] + typeDef, ok := pkgDefs.uniqueDefinitions[typeName] if ok { return typeDef } } - pkgPath := pkgs.findPackagePathFromImports(parts[0], file, false) + + pkgPath := pkgDefs.findPackagePathFromImports(parts[0], file, false) if len(pkgPath) == 0 { // check if the current package if parts[0] == file.Name.Name { - pkgPath = pkgs.files[file].PackagePath + pkgPath = pkgDefs.files[file].PackagePath } else if parseDependency { // take it as an external package, needs to be loaded - if pkgPath = pkgs.findPackagePathFromImports(parts[0], file, true); len(pkgPath) > 0 { - if err := pkgs.loadExternalPackage(pkgPath); err != nil { + if pkgPath = pkgDefs.findPackagePathFromImports(parts[0], file, true); len(pkgPath) > 0 { + if err := pkgDefs.loadExternalPackage(pkgPath); err != nil { return nil } } } } - return pkgs.findTypeSpec(pkgPath, parts[1]) + return pkgDefs.findTypeSpec(pkgPath, parts[1]) } - typeDef, ok := pkgs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] + typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] if ok { return typeDef } - typeDef = pkgs.findTypeSpec(pkgs.files[file].PackagePath, typeName) + typeDef = pkgDefs.findTypeSpec(pkgDefs.files[file].PackagePath, typeName) if typeDef != nil { return typeDef } for _, imp := range file.Imports { if imp.Name != nil && imp.Name.Name == "." { - typeDef := pkgs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), typeName) + typeDef := pkgDefs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), typeName) if typeDef != nil { return typeDef } diff --git a/parser.go b/parser.go index 980c1d7b2..a390714f4 100644 --- a/parser.go +++ b/parser.go @@ -65,7 +65,7 @@ var ( // ErrFailedConvertPrimitiveType Failed to convert for swag to interpretable type. ErrFailedConvertPrimitiveType = errors.New("swag property: failed convert primitive type") - // ErrSkippedField .swaggo specifies field should be skipped + // ErrSkippedField .swaggo specifies field should be skipped. ErrSkippedField = errors.New("field is skipped by global overrides") ) @@ -142,12 +142,12 @@ type Parser struct { Overrides map[string]string } -// FieldParserFactory create FieldParser +// FieldParserFactory create FieldParser. type FieldParserFactory func(ps *Parser, field *ast.Field) FieldParser -// FieldParser parse struct field +// FieldParser parse struct field. type FieldParser interface { - ShouldSkip() (bool, error) + ShouldSkip() bool FieldName() (string, error) CustomSchema() (*spec.Schema, error) ComplementSchema(schema *spec.Schema) error @@ -161,8 +161,6 @@ type Debugger interface { // New creates a new Parser with default properties. func New(options ...func(*Parser)) *Parser { - // parser.swagger.SecurityDefinitions = - parser := &Parser{ swagger: &spec.Swagger{ SwaggerProps: spec.SwaggerProps{ @@ -177,10 +175,16 @@ func New(options ...func(*Parser)) *Parser { }, Paths: &spec.Paths{ Paths: make(map[string]spec.PathItem), + VendorExtensible: spec.VendorExtensible{ + Extensions: nil, + }, }, Definitions: make(map[string]spec.Schema), SecurityDefinitions: make(map[string]*spec.SecurityScheme), }, + VendorExtensible: spec.VendorExtensible{ + Extensions: nil, + }, }, packages: NewPackagesDefinitions(), debug: log.New(os.Stdout, "", log.LstdFlags), @@ -284,22 +288,22 @@ func (parser *Parser) ParseAPIMultiSearchDir(searchDirs []string, mainAPIFile st } if parser.ParseDependency { - var t depth.Tree - t.ResolveInternal = true - t.MaxDepth = parseDepth + var tree depth.Tree + tree.ResolveInternal = true + tree.MaxDepth = parseDepth pkgName, err := getPkgName(filepath.Dir(absMainAPIFilePath)) if err != nil { return err } - err = t.Resolve(pkgName) + err = tree.Resolve(pkgName) if err != nil { return fmt.Errorf("pkg %s cannot find all dependencies, %s", pkgName, err) } - for i := 0; i < len(t.Root.Deps); i++ { - err := parser.getAllGoFileInfoFromDeps(&t.Root.Deps[i]) + for i := 0; i < len(tree.Root.Deps); i++ { + err := parser.getAllGoFileInfoFromDeps(&tree.Root.Deps[i]) if err != nil { return err } @@ -329,7 +333,9 @@ func (parser *Parser) ParseAPIMultiSearchDir(searchDirs []string, mainAPIFile st func getPkgName(searchDir string) (string, error) { cmd := exec.Command("go", "list", "-f={{.ImportPath}}") cmd.Dir = searchDir + var stdout, stderr strings.Builder + cmd.Stdout = &stdout cmd.Stderr = &stderr @@ -342,7 +348,9 @@ func getPkgName(searchDir string) (string, error) { if outStr[0] == '_' { // will shown like _/{GOPATH}/src/{YOUR_PACKAGE} when NOT enable GO MODULE. outStr = strings.TrimPrefix(outStr, "_"+build.Default.GOPATH+"/src/") } + f := strings.Split(outStr, "\n") + outStr = f[0] return outStr, nil @@ -370,7 +378,8 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error { if !isGeneralAPIComment(comments) { continue } - err := parseGeneralAPIInfo(parser, comments) + + err = parseGeneralAPIInfo(parser, comments) if err != nil { return err } @@ -383,14 +392,15 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { previousAttribute := "" // parsing classic meta data model - for i := 0; i < len(comments); i++ { - commentLine := comments[i] + for line := 0; line < len(comments); line++ { + commentLine := comments[line] attribute := strings.Split(commentLine, " ")[0] value := strings.TrimSpace(commentLine[len(attribute):]) multilineBlock := false if previousAttribute == attribute { multilineBlock = true } + switch strings.ToLower(attribute) { case versionAttr: parser.swagger.Info.Version = value @@ -402,12 +412,14 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { continue } + parser.swagger.Info.Description = value case "@description.markdown": commentInfo, err := getMarkdownForTag("api", parser.markdownFileDir) if err != nil { return err } + parser.swagger.Info.Description = string(commentInfo) case "@termsofservice": parser.swagger.Info.TermsOfService = value @@ -451,56 +463,66 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { replaceLastTag(parser.swagger.Tags, tag) case "@tag.description.markdown": tag := parser.swagger.Tags[len(parser.swagger.Tags)-1] + commentInfo, err := getMarkdownForTag(tag.TagProps.Name, parser.markdownFileDir) if err != nil { return err } + 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, + URL: value, + Description: "", } + 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) case "@securitydefinitions.basic": parser.swagger.SecurityDefinitions[value] = spec.BasicAuth() case "@securitydefinitions.apikey": - attrMap, _, extensions, err := parseSecAttr(attribute, []string{"@in", "@name"}, comments, &i) + attrMap, _, extensions, err := parseSecAttr(attribute, []string{"@in", "@name"}, comments, &line) if err != nil { return err } + parser.swagger.SecurityDefinitions[value] = tryAddDescription(spec.APIKeyAuth(attrMap["@name"], attrMap["@in"]), extensions) case "@securitydefinitions.oauth2.application": - attrMap, scopes, extensions, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &i) + attrMap, scopes, extensions, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &line) if err != nil { return err } + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Application(attrMap["@tokenurl"], scopes, extensions), extensions) case "@securitydefinitions.oauth2.implicit": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@authorizationurl"}, comments, &i) + attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@authorizationurl"}, comments, &line) if err != nil { return err } + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Implicit(attrs["@authorizationurl"], scopes, ext), ext) case "@securitydefinitions.oauth2.password": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &i) + attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &line) if err != nil { return err } + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Password(attrs["@tokenurl"], scopes, ext), ext) case "@securitydefinitions.oauth2.accesscode": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl", "@authorizationurl"}, comments, &i) + attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl", "@authorizationurl"}, comments, &line) if err != nil { return err } + parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2AccessToken(attrs["@authorizationurl"], attrs["@tokenurl"], scopes, ext), ext) case "@query.collection.format": parser.collectionFormatInQuery = value @@ -518,17 +540,21 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { break } } + // if it is present on security def, don't add it again if extExistsInSecurityDef { break } var valueJSON interface{} + split := strings.SplitAfter(commentLine, attribute+" ") if len(split) < 2 { return fmt.Errorf("annotation %s need a value", attribute) } + extensionName := "x-" + strings.SplitAfter(attribute, prefixExtension)[1] + err := json.Unmarshal([]byte(split[1]), &valueJSON) if err != nil { return fmt.Errorf("annotation %s need a valid json value", attribute) @@ -540,10 +566,12 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { if parser.swagger.Extensions == nil { parser.swagger.Extensions = make(map[string]interface{}) } + parser.swagger.Extensions[attribute[1:]] = valueJSON } } } + previousAttribute = attribute } @@ -556,6 +584,7 @@ func tryAddDescription(spec *spec.SecurityScheme, extensions map[string]interfac spec.Description = str } } + return spec } @@ -592,32 +621,40 @@ func parseSecAttr(context string, search []string, lines []string, index *int) ( for ; *index < len(lines); *index++ { v := lines[*index] + securityAttr := strings.ToLower(strings.Split(v, " ")[0]) for _, findterm := range search { if securityAttr == findterm { attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):]) + continue } } + isExists, err := isExistsScope(securityAttr) if err != nil { return nil, nil, nil, err } + if isExists { scopes[securityAttr[len(scopeAttrPrefix):]] = v[len(securityAttr):] } + if strings.HasPrefix(securityAttr, "@x-") { // Add the custom attribute without the @ extensions[securityAttr[1:]] = strings.TrimSpace(v[len(securityAttr):]) } + // Not mandatory field if securityAttr == "@description" { extensions[securityAttr] = strings.TrimSpace(v[len(securityAttr):]) } + // next securityDefinitions if strings.Index(securityAttr, "@securitydefinitions.") == 0 { // Go back to the previous line and break *index-- + break } } @@ -629,8 +666,12 @@ func parseSecAttr(context string, search []string, lines []string, index *int) ( return attrMap, scopes, extensions, nil } -func secOAuth2Application(tokenURL string, scopes map[string]string, - extensions map[string]interface{}) *spec.SecurityScheme { +type ( + authExtensions map[string]interface{} + authScopes map[string]string +) + +func secOAuth2Application(tokenURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { securityScheme := spec.OAuth2Application(tokenURL) securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) for scope, description := range scopes { @@ -640,10 +681,10 @@ func secOAuth2Application(tokenURL string, scopes map[string]string, return securityScheme } -func secOAuth2Implicit(authorizationURL string, scopes map[string]string, - extensions map[string]interface{}) *spec.SecurityScheme { +func secOAuth2Implicit(authorizationURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { securityScheme := spec.OAuth2Implicit(authorizationURL) securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) + for scope, description := range scopes { securityScheme.AddScope(scope, description) } @@ -651,10 +692,10 @@ func secOAuth2Implicit(authorizationURL string, scopes map[string]string, return securityScheme } -func secOAuth2Password(tokenURL string, scopes map[string]string, - extensions map[string]interface{}) *spec.SecurityScheme { +func secOAuth2Password(tokenURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { securityScheme := spec.OAuth2Password(tokenURL) securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) + for scope, description := range scopes { securityScheme.AddScope(scope, description) } @@ -662,10 +703,10 @@ func secOAuth2Password(tokenURL string, scopes map[string]string, return securityScheme } -func secOAuth2AccessToken(authorizationURL, tokenURL string, - scopes map[string]string, extensions map[string]interface{}) *spec.SecurityScheme { +func secOAuth2AccessToken(authorizationURL, tokenURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { securityScheme := spec.OAuth2AccessToken(authorizationURL, tokenURL) securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) + for scope, description := range scopes { securityScheme.AddScope(scope, description) } @@ -673,7 +714,7 @@ func secOAuth2AccessToken(authorizationURL, tokenURL string, return securityScheme } -func handleSecuritySchemaExtensions(providedExtensions map[string]interface{}) spec.Extensions { +func handleSecuritySchemaExtensions(providedExtensions authExtensions) spec.Extensions { var extensions spec.Extensions if len(providedExtensions) > 0 { extensions = make(map[string]interface{}, len(providedExtensions)) @@ -695,6 +736,7 @@ func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) { if fileInfo.IsDir() { continue } + fileName := fileInfo.Name() if !strings.Contains(fileName, ".md") { @@ -703,6 +745,7 @@ func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) { if strings.Contains(fileName, tagName) { fullPath := filepath.Join(dirPath, fileName) + commentInfo, err := ioutil.ReadFile(fullPath) if err != nil { return nil, fmt.Errorf("Failed to read markdown file %s error: %s ", fullPath, err) @@ -749,29 +792,9 @@ func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) err } } - for _, routeProperties := range operation.RouterProperties { - var pathItem spec.PathItem - var ok bool - - pathItem, ok = parser.swagger.Paths.Paths[routeProperties.Path] - if !ok { - pathItem = spec.PathItem{} - } - - op := refRouteMethodOp(&pathItem, routeProperties.HTTPMethod) - - // check if we already have a operation for this path and method - if *op != nil { - err := fmt.Errorf("route %s %s is declared multiple times", routeProperties.HTTPMethod, routeProperties.Path) - if parser.Strict { - return err - } - parser.debug.Printf("warning: %s\n", err) - } - - *op = &operation.Operation - - parser.swagger.Paths.Paths[routeProperties.Path] = pathItem + err := processRouterOperation(parser, operation) + if err != nil { + return err } } } @@ -796,14 +819,48 @@ func refRouteMethodOp(item *spec.PathItem, method string) (op **spec.Operation) case http.MethodOptions: op = &item.Options } + return } +func processRouterOperation(parser *Parser, operation *Operation) error { + for _, routeProperties := range operation.RouterProperties { + var ( + pathItem spec.PathItem + ok bool + ) + + pathItem, ok = parser.swagger.Paths.Paths[routeProperties.Path] + if !ok { + pathItem = spec.PathItem{} + } + + op := refRouteMethodOp(&pathItem, routeProperties.HTTPMethod) + + // check if we already have an operation for this path and method + if *op != nil { + err := fmt.Errorf("route %s %s is declared multiple times", routeProperties.HTTPMethod, routeProperties.Path) + if parser.Strict { + return err + } + + parser.debug.Printf("warning: %s\n", err) + } + + *op = &operation.Operation + + parser.swagger.Paths.Paths[routeProperties.Path] = pathItem + } + + return nil +} + func convertFromSpecificToPrimitive(typeName string) (string, error) { name := typeName if strings.ContainsRune(name, '.') { name = strings.Split(name, ".")[1] } + switch strings.ToUpper(name) { case "TIME", "OBJECTID", "UUID": return STRING, nil @@ -832,6 +889,7 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( if override, ok := parser.Overrides[typeSpecDef.FullPath()]; ok { if override == "" { parser.debug.Printf("Override detected for %s: ignoring", typeSpecDef.FullPath()) + return nil, ErrSkippedField } @@ -841,6 +899,7 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( if separator == -1 { // treat as a swaggertype tag parts := strings.Split(override, ",") + return BuildCustomSchema(parts) } @@ -850,6 +909,7 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( schema, ok := parser.parsedSchemas[typeSpecDef] if !ok { var err error + schema, err = parser.ParseDefinition(typeSpecDef) if err != nil { if err == ErrRecursiveParseStruct && ref { @@ -885,8 +945,10 @@ func (parser *Parser) renameRefSchemas() { for _, refURL := range parser.toBeRenamedRefURLs { parts := strings.Split(refURL.Fragment, "/") name := parts[len(parts)-1] + if pkgPath, ok := parser.toBeRenamedSchemas[name]; ok { parts[len(parts)-1] = parser.renameSchema(name, pkgPath) + refURL.Fragment = strings.Join(parts, "/") } } @@ -915,6 +977,7 @@ func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) } else { parser.existSchemaNames[schema.Name] = schema } + parser.swagger.Definitions[schema.Name] = spec.Schema{} if schema.Schema != nil { @@ -948,8 +1011,8 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) typeName := typeSpecDef.FullName() refTypeName := TypeDocName(typeName, typeSpecDef.TypeSpec) - schema, ok := parser.parsedSchemas[typeSpecDef] - if ok { + schema, found := parser.parsedSchemas[typeSpecDef] + if found { parser.debug.Printf("Skipping '%s', already parsed.", typeName) return schema, nil @@ -965,6 +1028,7 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) }, ErrRecursiveParseStruct } + parser.structStack = append(parser.structStack, typeSpecDef) parser.debug.Printf("Generating %s", typeName) @@ -978,20 +1042,20 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) fillDefinitionDescription(definition, typeSpecDef.File, typeSpecDef) } - s := Schema{ + sch := Schema{ Name: refTypeName, PkgPath: typeSpecDef.PkgPath, Schema: definition, } - parser.parsedSchemas[typeSpecDef] = &s + parser.parsedSchemas[typeSpecDef] = &sch // update an empty schema as a result of recursion - s2, ok := parser.outputSchemas[typeSpecDef] - if ok { + s2, found := parser.outputSchemas[typeSpecDef] + if found { parser.swagger.Definitions[s2.Name] = *definition } - return &s, nil + return &sch, nil } func fullTypeName(pkgName, typeName string) string { @@ -1034,13 +1098,16 @@ func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string { } isHandlingDescription := false + for _, comment := range commentGroup.List { commentText := strings.TrimSpace(strings.TrimLeft(comment.Text, "/")) attribute := strings.Split(commentText, " ")[0] + if strings.ToLower(attribute) != descriptionAttr { if !isHandlingDescription { continue } + break } @@ -1108,8 +1175,8 @@ func (parser *Parser) parseTypeExpr(file *ast.File, typeExpr ast.Expr, ref bool) } func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec.Schema, error) { - required := make([]string, 0) - properties := make(map[string]spec.Schema) + required, properties := make([]string, 0), make(map[string]spec.Schema) + for _, field := range fields.List { fieldProps, requiredFromAnon, err := parser.parseStructField(file, field) if err != nil { @@ -1119,10 +1186,13 @@ func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec. return nil, err } + if len(fieldProps) == 0 { continue } + required = append(required, requiredFromAnon...) + for k, v := range fieldProps { properties[k] = v } @@ -1152,10 +1222,12 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st if err != nil { return nil, nil, err } + schema, err := parser.getTypeSchema(typeName, file, false) if err != nil { return nil, nil, err } + if len(schema.Type) > 0 && schema.Type[0] == OBJECT { if len(schema.Properties) == 0 { return nil, nil, nil @@ -1175,11 +1247,7 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st ps := parser.fieldParserFactory(parser, field) - ok, err := ps.ShouldSkip() - if err != nil { - return nil, nil, err - } - if ok { + if ps.ShouldSkip() { return nil, nil, nil } @@ -1192,6 +1260,7 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st if err != nil { return nil, nil, err } + if schema == nil { typeName, err := getFieldType(field.Type) if err == nil { @@ -1201,6 +1270,7 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st // unnamed type schema, err = parser.parseTypeExpr(file, field.Type, false) } + if err != nil { return nil, nil, err } @@ -1212,10 +1282,12 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st } var tagRequired []string + required, err := ps.IsRequired() if err != nil { return nil, nil, err } + if required { tagRequired = append(tagRequired, fieldName) } @@ -1234,7 +1306,6 @@ func getFieldType(field ast.Expr) (string, error) { } return fullTypeName(packageName, fieldType.Sel.Name), nil - case *ast.StarExpr: fullName, err := getFieldType(fieldType.X) if err != nil { @@ -1252,6 +1323,7 @@ func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string if schema == nil || depth == 0 { return nil } + name := schema.Ref.String() if name != "" { if pos := strings.LastIndexByte(name, '/'); pos >= 0 { @@ -1263,10 +1335,12 @@ func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string return nil } + if len(schema.Type) > 0 { switch schema.Type[0] { case ARRAY: depth-- + s := []string{schema.Type[0]} return append(s, parser.GetSchemaTypePath(schema.Items.Schema, depth)...) @@ -1274,6 +1348,7 @@ func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { // for map depth-- + s := []string{schema.Type[0]} return append(s, parser.GetSchemaTypePath(schema.AdditionalProperties.Schema, depth)...) @@ -1324,6 +1399,7 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{ if err != nil { return nil, err } + result = append(result, v) } @@ -1334,7 +1410,9 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{ } values := strings.Split(exampleValue, ",") + result := map[string]interface{}{} + for _, value := range values { mapData := strings.Split(value, ":") @@ -1343,10 +1421,14 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{ if err != nil { return nil, err } + result[mapData[0]] = v - } else { - return nil, fmt.Errorf("example value %s should format: key:value", exampleValue) + + continue + } + + return nil, fmt.Errorf("example value %s should format: key:value", exampleValue) } return result, nil @@ -1358,9 +1440,12 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{ // GetAllGoFileInfo gets all Go source files information for given searchDir. func (parser *Parser) getAllGoFileInfo(packageDir, searchDir string) error { return filepath.Walk(searchDir, func(path string, f os.FileInfo, _ error) error { - if err := parser.Skip(path, f); err != nil { + err := parser.Skip(path, f) + if err != nil { return err - } else if f.IsDir() { + } + + if f.IsDir() { return nil } @@ -1383,7 +1468,9 @@ func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg) error { if pkg.Raw == nil && pkg.Name == "C" { return nil } + srcDir := pkg.Raw.Dir + files, err := ioutil.ReadDir(srcDir) // only parsing files in the dir(don't contain sub dir files) if err != nil { return err @@ -1434,24 +1521,29 @@ func (parser *Parser) checkOperationIDUniqueness() error { for path, item := range parser.swagger.Paths.Paths { var method, id string + for method = range allMethod { op := refRouteMethodOp(&item, method) if *op != nil { id = (**op).ID + break } } + if id == "" { continue } current := fmt.Sprintf("%s %s", method, path) + previous, ok := operationsIds[id] if ok { return fmt.Errorf( "duplicated @id annotation '%s' found in '%s', previously declared in: '%s'", id, current, previous) } + operationsIds[id] = current } diff --git a/parser_test.go b/parser_test.go index 908d54a7f..392a04fe2 100644 --- a/parser_test.go +++ b/parser_test.go @@ -212,7 +212,9 @@ func TestParser_ParseGeneralApiInfo(t *testing.T) { }` gopath := os.Getenv("GOPATH") assert.NotNil(t, gopath) + p := New() + err := p.ParseGeneralAPIInfo("testdata/main.go") assert.NoError(t, err) @@ -295,7 +297,9 @@ func TestParser_ParseGeneralApiInfoTemplated(t *testing.T) { }` gopath := os.Getenv("GOPATH") assert.NotNil(t, gopath) + p := New() + err := p.ParseGeneralAPIInfo("testdata/templated.go") assert.NoError(t, err) @@ -311,7 +315,9 @@ func TestParser_ParseGeneralApiInfoExtensions(t *testing.T) { expected := "annotation @x-google-endpoints need a valid json value" gopath := os.Getenv("GOPATH") assert.NotNil(t, gopath) + p := New() + err := p.ParseGeneralAPIInfo("testdata/extensionsFail1.go") if assert.Error(t, err) { assert.Equal(t, expected, err.Error()) @@ -325,7 +331,9 @@ func TestParser_ParseGeneralApiInfoExtensions(t *testing.T) { expected := "annotation @x-google-endpoints need a value" gopath := os.Getenv("GOPATH") assert.NotNil(t, gopath) + p := New() + err := p.ParseGeneralAPIInfo("testdata/extensionsFail2.go") if assert.Error(t, err) { assert.Equal(t, expected, err.Error()) @@ -350,7 +358,9 @@ func TestParser_ParseGeneralApiInfoWithOpsInSameFile(t *testing.T) { gopath := os.Getenv("GOPATH") assert.NotNil(t, gopath) + p := New() + err := p.ParseGeneralAPIInfo("testdata/single_file_api/main.go") assert.NoError(t, err) @@ -387,6 +397,7 @@ func TestParser_ParseGeneralAPIInfoMarkdown(t *testing.T) { assert.Equal(t, expected, string(b)) p = New() + err = p.ParseGeneralAPIInfo(mainAPIFile) assert.Error(t, err) } @@ -3332,7 +3343,8 @@ func TestDefineTypeOfExample(t *testing.T) { example, err = defineTypeOfExample("array", "string", "one,two,three") assert.NoError(t, err) - arr := []string{} + + var arr []string for _, v := range example.([]interface{}) { arr = append(arr, v.(string)) diff --git a/schema.go b/schema.go index 0e72f65d7..a23d21b36 100644 --- a/schema.go +++ b/schema.go @@ -26,6 +26,8 @@ const ( STRING = "string" // FUNC represent a function value. FUNC = "func" + // INTERFACE represent a interface value. + INTERFACE = "interface{}" // ANY represent a any value. ANY = "any" // NIL represent a empty value. @@ -133,6 +135,7 @@ func TypeDocName(pkgName string, spec *ast.TypeSpec) string { } } } + if spec.Name != nil { return fullTypeName(strings.Split(pkgName, ".")[0], spec.Name.Name) } @@ -168,6 +171,7 @@ func BuildCustomSchema(types []string) (*spec.Schema, error) { if len(types) == 1 { return nil, errors.New("need array item type after array") } + schema, err := BuildCustomSchema(types[1:]) if err != nil { return nil, err @@ -178,6 +182,7 @@ func BuildCustomSchema(types []string) (*spec.Schema, error) { if len(types) == 1 { return PrimitiveSchema(types[0]), nil } + schema, err := BuildCustomSchema(types[1:]) if err != nil { return nil, err diff --git a/schema_test.go b/schema_test.go index dbb7b614c..0fe60e816 100644 --- a/schema_test.go +++ b/schema_test.go @@ -85,8 +85,10 @@ func TestIsSimplePrimitiveType(t *testing.T) { func TestBuildCustomSchema(t *testing.T) { t.Parallel() - var schema *spec.Schema - var err error + var ( + schema *spec.Schema + err error + ) schema, err = BuildCustomSchema([]string{}) assert.NoError(t, err) diff --git a/spec.go b/spec.go index 9e0ec1ad0..3a727c940 100644 --- a/spec.go +++ b/spec.go @@ -21,31 +21,33 @@ type Spec struct { // ReadDoc parses SwaggerTemplate into swagger document. func (i *Spec) ReadDoc() string { - i.Description = strings.Replace(i.Description, "\n", "\\n", -1) + i.Description = strings.ReplaceAll(i.Description, "\n", "\\n") - t, err := template.New("swagger_info").Funcs(template.FuncMap{ + tpl, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) + return string(a) }, "escape": func(v interface{}) string { // escape tabs - str := strings.Replace(v.(string), "\t", "\\t", -1) + var str = strings.ReplaceAll(v.(string), "\t", "\\t") // replace " with \", and if that results in \\", replace that with \\\" - str = strings.Replace(str, "\"", "\\\"", -1) - return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1) + str = strings.ReplaceAll(str, "\"", "\\\"") + + return strings.ReplaceAll(str, "\\\\\"", "\\\\\\\"") }, }).Parse(i.SwaggerTemplate) if err != nil { return i.SwaggerTemplate } - var tpl bytes.Buffer - if err = t.Execute(&tpl, i); err != nil { + var doc bytes.Buffer + if err = tpl.Execute(&doc, i); err != nil { return i.SwaggerTemplate } - return tpl.String() + return doc.String() } // InstanceName returns Spec instance name. diff --git a/spec_test.go b/spec_test.go index 00ab37007..75eb7f37d 100644 --- a/spec_test.go +++ b/spec_test.go @@ -1,8 +1,9 @@ package swag import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestSpec_InstanceName(t *testing.T) { @@ -16,6 +17,7 @@ func TestSpec_InstanceName(t *testing.T) { InfoInstanceName string SwaggerTemplate string } + tests := []struct { name string fields fields @@ -32,9 +34,10 @@ func TestSpec_InstanceName(t *testing.T) { want: "TestInstanceName1", }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - i := &Spec{ + doc := Spec{ Version: tt.fields.Version, Host: tt.fields.Host, BasePath: tt.fields.BasePath, @@ -44,7 +47,8 @@ func TestSpec_InstanceName(t *testing.T) { InfoInstanceName: tt.fields.InfoInstanceName, SwaggerTemplate: tt.fields.SwaggerTemplate, } - assert.Equal(t, tt.want, i.InstanceName()) + + assert.Equal(t, tt.want, doc.InstanceName()) }) } } @@ -60,6 +64,7 @@ func TestSpec_ReadDoc(t *testing.T) { InfoInstanceName string SwaggerTemplate string } + tests := []struct { name string fields fields @@ -128,9 +133,10 @@ func TestSpec_ReadDoc(t *testing.T) { want: "{{ .Schemesa }}", }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - i := &Spec{ + doc := Spec{ Version: tt.fields.Version, Host: tt.fields.Host, BasePath: tt.fields.BasePath, @@ -140,7 +146,8 @@ func TestSpec_ReadDoc(t *testing.T) { InfoInstanceName: tt.fields.InfoInstanceName, SwaggerTemplate: tt.fields.SwaggerTemplate, } - assert.Equal(t, tt.want, i.ReadDoc()) + + assert.Equal(t, tt.want, doc.ReadDoc()) }) } } diff --git a/swagger.go b/swagger.go index c00feb22b..5ffbab63e 100644 --- a/swagger.go +++ b/swagger.go @@ -23,6 +23,7 @@ type Swagger interface { func Register(name string, swagger Swagger) { swaggerMu.Lock() defer swaggerMu.Unlock() + if swagger == nil { panic("swagger is nil") } @@ -34,6 +35,7 @@ func Register(name string, swagger Swagger) { if _, ok := swags[name]; ok { panic("Register called twice for swag: " + name) } + swags[name] = swagger } From 5f6c5f85068a66ce0e58e21ebccbfbdcf31a5a27 Mon Sep 17 00:00:00 2001 From: Saigak Evgeniy Date: Wed, 27 Apr 2022 16:21:46 +0300 Subject: [PATCH 04/12] impr: add param example to readme (#1189) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12138b91e..84239d117 100644 --- a/README.md +++ b/README.md @@ -469,6 +469,7 @@ Besides that, `swag` also accepts aliases for some MIME Types as follows: // @Param string query string false "string valid" minlength(5) maxlength(10) // @Param int query int false "int valid" minimum(1) maximum(10) // @Param default query string false "string default" default(A) +// @Param example query string false "string example" example(string) // @Param collection query []string false "string collection" collectionFormat(multi) // @Param extensions query []string false "string collection" extensions(x-example=test,x-nullable) ``` @@ -477,7 +478,7 @@ It also works for the struct fields: ```go type Foo struct { - Bar string `minLength:"4" maxLength:"16"` + Bar string `minLength:"4" maxLength:"16" example:"random string"` Baz int `minimum:"10" maximum:"20" default:"15"` Qux []string `enums:"foo,bar,baz"` } @@ -497,6 +498,7 @@ Field Name | Type | Description enums | [\*] | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1. format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](https://swagger.io/specification/v2/#dataTypeFormat) for further details. collectionFormat | `string` |Determines the format of the array if type array is used. Possible values are:
  • `csv` - comma separated values `foo,bar`.
  • `ssv` - space separated values `foo bar`.
  • `tsv` - tab separated values `foo\tbar`.
  • `pipes` - pipe separated values foo|bar.
  • `multi` - corresponds to multiple parameter instances instead of multiple values for a single instance `foo=bar&foo=baz`. This is valid only for parameters [`in`](#parameterIn) "query" or "formData".
Default value is `csv`. +example | * | Declares the example for the parameter value extensions | `string` | Add extension to parameters. ### Future From 47d5a7630847872fa191913f0175963d9b3dad93 Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Wed, 27 Apr 2022 18:38:00 +0300 Subject: [PATCH 05/12] chore: updating dependencies (#1190) --- example/celler/go.mod | 8 +++---- example/celler/go.sum | 23 ++++++++++++++------ example/go-module-support/go.mod | 10 ++++----- example/go-module-support/go.sum | 28 ++++++++++++++++++------ example/markdown/go.mod | 17 ++++++--------- example/markdown/go.sum | 36 ++++++++++++++++++++----------- example/object-map-example/go.mod | 17 ++++++--------- example/object-map-example/go.sum | 35 +++++++++++++++++++----------- go.mod | 10 ++++----- go.sum | 18 ++++++++-------- 10 files changed, 122 insertions(+), 80 deletions(-) diff --git a/example/celler/go.mod b/example/celler/go.mod index e88d7dc62..1c00311eb 100644 --- a/example/celler/go.mod +++ b/example/celler/go.mod @@ -6,8 +6,8 @@ require ( github.com/gin-gonic/gin v1.7.7 github.com/gofrs/uuid v4.2.0+incompatible github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 - github.com/swaggo/gin-swagger v1.4.1 - github.com/swaggo/swag v1.7.9 + github.com/swaggo/gin-swagger v1.4.2 + github.com/swaggo/swag v1.8.1 ) require ( @@ -31,10 +31,10 @@ require ( github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/ugorji/go/codec v1.1.7 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.7 // indirect + golang.org/x/tools v0.1.10 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/example/celler/go.sum b/example/celler/go.sum index 0e9376fbf..1dfb452f8 100644 --- a/example/celler/go.sum +++ b/example/celler/go.sum @@ -89,27 +89,35 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/gin-swagger v1.4.1 h1:F2vJndw+Q+ZBOlsC6CaodqXJV3ZOf6hpg/4Y6MEx5BM= -github.com/swaggo/gin-swagger v1.4.1/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg= -github.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc= +github.com/swaggo/gin-swagger v1.4.2 h1:qDs1YrBOTnurDG/JVMc8678KhoS1B1okQGPtIqVz4YU= +github.com/swaggo/gin-swagger v1.4.2/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg= github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -122,20 +130,23 @@ golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/example/go-module-support/go.mod b/example/go-module-support/go.mod index 02c8f9ce8..e54d3c00a 100644 --- a/example/go-module-support/go.mod +++ b/example/go-module-support/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/gin-gonic/gin v1.7.7 github.com/swaggo/examples v0.0.0-20190624100559-f57286ab550c - github.com/swaggo/swag v1.7.9 + github.com/swaggo/swag v1.8.1 ) require ( @@ -29,10 +29,10 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/ugorji/go/codec v1.1.7 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect - golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.7 // indirect + golang.org/x/tools v0.1.10 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/example/go-module-support/go.sum b/example/go-module-support/go.sum index 50242b72c..e765145e5 100644 --- a/example/go-module-support/go.sum +++ b/example/go-module-support/go.sum @@ -85,26 +85,34 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/examples v0.0.0-20190624100559-f57286ab550c h1:wgBp6VweQ9dML4cKbjh0sV0xxvxgqCrCRiHG6losLv4= github.com/swaggo/examples v0.0.0-20190624100559-f57286ab550c/go.mod h1:U21M3+8BIXRyR/pwjJ7X0D36sVTzFMiOyUAdgvVfUVI= -github.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc= -github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -113,19 +121,25 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/example/markdown/go.mod b/example/markdown/go.mod index 6e06dec8b..629c9c0d5 100644 --- a/example/markdown/go.mod +++ b/example/markdown/go.mod @@ -3,25 +3,22 @@ module github.com/swaggo/swag/example/markdown go 1.17 require ( - github.com/gorilla/mux v1.7.3 + github.com/gorilla/mux v1.8.0 github.com/swaggo/http-swagger v1.2.6 - github.com/swaggo/swag v1.7.9 + github.com/swaggo/swag v1.8.1 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.5 // indirect github.com/go-openapi/swag v0.19.15 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect - golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect - golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.7 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/tools v0.1.10 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/example/markdown/go.sum b/example/markdown/go.sum index 8b36cf767..cb3034a03 100644 --- a/example/markdown/go.sum +++ b/example/markdown/go.sum @@ -1,9 +1,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= @@ -16,16 +14,18 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE= +github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -61,20 +61,27 @@ github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5 github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= github.com/swaggo/http-swagger v1.2.6 h1:ihTjChUoSRMpFMjWw+0AkL1Ti4r6v8pCgVYLmQVRlRw= github.com/swaggo/http-swagger v1.2.6/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA= -github.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc= github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -82,18 +89,23 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/example/object-map-example/go.mod b/example/object-map-example/go.mod index d487a91a0..4083a9677 100644 --- a/example/object-map-example/go.mod +++ b/example/object-map-example/go.mod @@ -5,18 +5,16 @@ go 1.17 require ( github.com/gin-gonic/gin v1.7.7 github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 - github.com/swaggo/gin-swagger v1.4.1 - github.com/swaggo/swag v1.7.9 + github.com/swaggo/gin-swagger v1.4.2 + github.com/swaggo/swag v1.8.1 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.5 // indirect github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.13.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect @@ -30,10 +28,9 @@ require ( github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/ugorji/go/codec v1.1.7 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.7 // indirect + golang.org/x/tools v0.1.10 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/example/object-map-example/go.sum b/example/object-map-example/go.sum index 2ce00520d..086a1b5a5 100644 --- a/example/object-map-example/go.sum +++ b/example/object-map-example/go.sum @@ -1,9 +1,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= @@ -23,10 +21,12 @@ github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE= +github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -87,29 +87,38 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/gin-swagger v1.4.1 h1:F2vJndw+Q+ZBOlsC6CaodqXJV3ZOf6hpg/4Y6MEx5BM= -github.com/swaggo/gin-swagger v1.4.1/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg= -github.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc= +github.com/swaggo/gin-swagger v1.4.2 h1:qDs1YrBOTnurDG/JVMc8678KhoS1B1okQGPtIqVz4YU= +github.com/swaggo/gin-swagger v1.4.2/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg= github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -120,20 +129,22 @@ golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/go.mod b/go.mod index bcd3d2e65..514946a86 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,14 @@ go 1.18 require ( github.com/KyleBanks/depth v1.2.1 - github.com/agiledragon/gomonkey/v2 v2.3.1 + github.com/agiledragon/gomonkey/v2 v2.7.0 github.com/ghodss/yaml v1.0.0 + github.com/go-openapi/jsonreference v0.19.6 github.com/go-openapi/spec v0.20.4 github.com/otiai10/copy v1.7.0 github.com/stretchr/testify v1.7.0 github.com/urfave/cli/v2 v2.3.0 - golang.org/x/tools v0.1.7 + golang.org/x/tools v0.1.10 ) require ( @@ -19,15 +20,14 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/swag v0.19.15 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect - golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect diff --git a/go.sum b/go.sum index 8cf267760..2f1ca8bda 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= -github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/agiledragon/gomonkey/v2 v2.7.0 h1:CFT/xdr6xbsIN04Yll4OhKq/vPm0MVD8ykV99jDBesM= +github.com/agiledragon/gomonkey/v2 v2.7.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -63,16 +63,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -80,8 +80,8 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 36ae7af79a267469681a35bac6b70f8dcd677acf Mon Sep 17 00:00:00 2001 From: Atte Kojo Date: Fri, 29 Apr 2022 19:50:55 +0300 Subject: [PATCH 06/12] chore: remove gomonkey dependency from formatter (#1192) --- Makefile | 8 +- format/format.go | 102 +++++++- format/format_test.go | 131 ++++++++++ formatter.go | 404 ++++++------------------------- formatter_test.go | 544 +++++++++++++----------------------------- go.mod | 2 - go.sum | 18 -- 7 files changed, 470 insertions(+), 739 deletions(-) create mode 100644 format/format_test.go diff --git a/Makefile b/Makefile index fa679d87d..d1a981711 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ GOPATH:=$(shell $(GOCMD) env GOPATH) u := $(if $(update),-u) BINARY_NAME:=swag -PACKAGES:=$(shell $(GOLIST) github.com/swaggo/swag github.com/swaggo/swag/cmd/swag github.com/swaggo/swag/gen) +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 @@ -63,9 +63,9 @@ deps: $(GOGET) golang.org/x/tools/go/loader .PHONY: devel-deps -devel-deps: +devel-deps: GO111MODULE=off $(GOGET) -v -u \ - golang.org/x/lint/golint + golang.org/x/lint/golint .PHONY: lint lint: devel-deps @@ -91,4 +91,4 @@ fmt-check: .PHONY: view-covered view-covered: $(GOTEST) -coverprofile=cover.out $(TARGET) - $(GOCMD) tool cover -html=cover.out \ No newline at end of file + $(GOCMD) tool cover -html=cover.out diff --git a/format/format.go b/format/format.go index e881d9d04..9421e0605 100644 --- a/format/format.go +++ b/format/format.go @@ -1,18 +1,33 @@ package format import ( - "log" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" "github.com/swaggo/swag" ) -type Fmt struct { +// Format implements `fmt` command for formatting swag comments in Go source +// files. +type Format struct { + formatter *swag.Formatter + + // exclude exclude dirs and files in SearchDir + exclude map[string]bool } -func New() *Fmt { - return &Fmt{} +// New creates a new Format instance +func New() *Format { + return &Format{ + exclude: map[string]bool{}, + formatter: swag.NewFormatter(), + } } +// Config specifies configuration for a format run type Config struct { // SearchDir the swag would be parse SearchDir string @@ -20,11 +35,84 @@ type Config struct { // excludes dirs and files in SearchDir,comma separated Excludes string + // MainFile (DEPRECATED) MainFile string } -func (f *Fmt) Build(config *Config) error { - log.Println("Formating code.... ") +var defaultExcludes = []string{"docs", "vendor"} + +// Build runs formatter according to configuration in config +func (f *Format) Build(config *Config) error { + searchDirs := strings.Split(config.SearchDir, ",") + for _, searchDir := range searchDirs { + if _, err := os.Stat(searchDir); os.IsNotExist(err) { + return fmt.Errorf("fmt: %w", err) + } + for _, d := range defaultExcludes { + f.exclude[filepath.Join(searchDir, d)] = true + } + } + for _, fi := range strings.Split(config.Excludes, ",") { + if fi = strings.TrimSpace(fi); fi != "" { + f.exclude[filepath.Clean(fi)] = true + } + } + for _, searchDir := range searchDirs { + err := filepath.Walk(searchDir, f.visit) + if err != nil { + return err + } + } + return nil +} + +func (f *Format) visit(path string, fileInfo os.FileInfo, err error) error { + if fileInfo.IsDir() && f.excludeDir(path) { + return filepath.SkipDir + } + if f.excludeFile(path) { + return nil + } + if err := f.format(path); err != nil { + return fmt.Errorf("fmt: %w", err) + } + return nil +} + +func (f *Format) excludeDir(path string) bool { + return f.exclude[path] || + filepath.Base(path)[0] == '.' && len(filepath.Base(path)) > 1 // exclude hidden folders +} + +func (f *Format) excludeFile(path string) bool { + return f.exclude[path] || + strings.HasSuffix(strings.ToLower(path), "_test.go") || + filepath.Ext(path) != ".go" +} + +func (f *Format) format(path string) error { + contents, err := ioutil.ReadFile(path) + if err != nil { + return err + } + formatted, err := f.formatter.Format(path, contents) + if err != nil { + return err + } + return write(path, formatted) +} - return swag.NewFormatter().FormatAPI(config.SearchDir, config.Excludes, config.MainFile) +func write(path string, contents []byte) error { + f, err := ioutil.TempFile(filepath.Split(path)) + if err != nil { + return err + } + defer os.Remove(f.Name()) + if _, err := f.Write(contents); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + return os.Rename(f.Name(), path) } diff --git a/format/format_test.go b/format/format_test.go new file mode 100644 index 000000000..508111e2b --- /dev/null +++ b/format/format_test.go @@ -0,0 +1,131 @@ +package format + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormat_Format(t *testing.T) { + fx := setup(t) + assert.NoError(t, New().Build(&Config{SearchDir: fx.basedir})) + assert.True(t, fx.isFormatted("main.go")) + assert.True(t, fx.isFormatted("api/api.go")) +} + +func TestFormat_ExcludeDir(t *testing.T) { + fx := setup(t) + assert.NoError(t, New().Build(&Config{ + SearchDir: fx.basedir, + Excludes: filepath.Join(fx.basedir, "api"), + })) + assert.False(t, fx.isFormatted("api/api.go")) +} + +func TestFormat_ExcludeFile(t *testing.T) { + fx := setup(t) + assert.NoError(t, New().Build(&Config{ + SearchDir: fx.basedir, + Excludes: filepath.Join(fx.basedir, "main.go"), + })) + assert.False(t, fx.isFormatted("main.go")) +} + +func TestFormat_DefaultExcludes(t *testing.T) { + fx := setup(t) + assert.NoError(t, New().Build(&Config{SearchDir: fx.basedir})) + assert.False(t, fx.isFormatted("api/api_test.go")) + assert.False(t, fx.isFormatted("docs/docs.go")) +} + +func TestFormat_ParseError(t *testing.T) { + fx := setup(t) + ioutil.WriteFile(filepath.Join(fx.basedir, "parse_error.go"), []byte(`package main + func invalid() {`), 0644) + assert.Error(t, New().Build(&Config{SearchDir: fx.basedir})) +} + +func TestFormat_ReadError(t *testing.T) { + fx := setup(t) + os.Chmod(filepath.Join(fx.basedir, "main.go"), 0) + assert.Error(t, New().Build(&Config{SearchDir: fx.basedir})) +} + +func TestFormat_WriteError(t *testing.T) { + fx := setup(t) + os.Chmod(fx.basedir, 0555) + assert.Error(t, New().Build(&Config{SearchDir: fx.basedir})) + os.Chmod(fx.basedir, 0755) +} + +func TestFormat_InvalidSearchDir(t *testing.T) { + formatter := New() + assert.Error(t, formatter.Build(&Config{SearchDir: "no_such_dir"})) +} + +type fixture struct { + t *testing.T + basedir string +} + +func setup(t *testing.T) *fixture { + fx := &fixture{ + t: t, + basedir: t.TempDir(), + } + for filename, contents := range testFiles { + fullpath := filepath.Join(fx.basedir, filepath.Clean(filename)) + if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(fullpath, contents, 0644); err != nil { + t.Fatal(err) + } + } + return fx +} + +func (fx *fixture) isFormatted(file string) bool { + contents, err := ioutil.ReadFile(filepath.Join(fx.basedir, filepath.Clean(file))) + if err != nil { + fx.t.Fatal(err) + } + return !bytes.Equal(testFiles[file], contents) +} + +var testFiles = map[string][]byte{ + "api/api.go": []byte(`package api + + import "net/http" + + // @Summary Add a new pet to the store + // @Description get string by ID + func GetStringByInt(w http.ResponseWriter, r *http.Request) { + //write your code + }`), + "api/api_test.go": []byte(`package api + // @Summary API Test + // @Description Should not be formatted + func TestApi(t *testing.T) {}`), + "docs/docs.go": []byte(`package docs + // @Summary Documentation package + // @Description Should not be formatted`), + "main.go": []byte(`package main + + import ( + "net/http" + + "github.com/swaggo/swag/format/testdata/api" + ) + + // @title Swagger Example API + // @version 1.0 + func main() { + http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt) + }`), + "README.md": []byte(`# Format test`), +} diff --git a/formatter.go b/formatter.go index 0b14e99d8..ca3e24c0f 100644 --- a/formatter.go +++ b/formatter.go @@ -8,381 +8,125 @@ import ( goparser "go/parser" "go/token" "io" - "io/ioutil" "log" "os" - "path/filepath" "regexp" - "runtime" "strings" "text/tabwriter" ) const splitTag = "&*" -// Formatter implements a formater for Go source files. -type Formatter struct { - // debugging output goes here - debug Debugger - - // excludes excludes dirs and files in SearchDir - excludes map[string]struct{} - - mainFile string +// Check of @Param @Success @Failure @Response @Header +var specialTagForSplit = map[string]bool{ + paramAttr: true, + successAttr: true, + failureAttr: true, + responseAttr: true, + headerAttr: true, } -// Formater creates a new formatter. -type Formater struct { - *Formatter +var skipChar = map[byte]byte{ + '"': '"', + '(': ')', + '{': '}', + '[': ']', } -// NewFormater Deprecated: Use NewFormatter instead. -func NewFormater() *Formater { - formatter := Formater{ - Formatter: NewFormatter(), - } - - formatter.debug.Printf("warining: NewFormater is deprecated. use NewFormatter instead") - - return &formatter +// Formatter implements a formatter for Go source files. +type Formatter struct { + // debugging output goes here + debug Debugger } -// NewFormatter create a new formater instance. +// NewFormatter create a new formatter instance. func NewFormatter() *Formatter { - formatter := Formatter{ - mainFile: "", - debug: log.New(os.Stdout, "", log.LstdFlags), - excludes: make(map[string]struct{}), - } - - return &formatter -} - -// FormatAPI format the swag comment. -func (f *Formatter) FormatAPI(searchDir, excludeDir, mainFile string) error { - searchDirs := strings.Split(searchDir, ",") - for _, searchDir := range searchDirs { - if _, err := os.Stat(searchDir); os.IsNotExist(err) { - return fmt.Errorf("dir: %s does not exist", searchDir) - } - } - - for _, fi := range strings.Split(excludeDir, ",") { - fi = strings.TrimSpace(fi) - if fi != "" { - fi = filepath.Clean(fi) - f.excludes[fi] = struct{}{} - } - } - - // parse main.go - absMainAPIFilePath, err := filepath.Abs(filepath.Join(searchDirs[0], mainFile)) - if err != nil { - return err - } - - err = f.FormatMain(absMainAPIFilePath) - if err != nil { - return err - } - - f.mainFile = mainFile - - err = f.formatMultiSearchDir(searchDirs) - if err != nil { - return err - } - - return nil -} - -func (f *Formatter) formatMultiSearchDir(searchDirs []string) error { - for _, searchDir := range searchDirs { - f.debug.Printf("Format API Info, search dir:%s", searchDir) - - err := filepath.Walk(searchDir, f.visit) - if err != nil { - return err - } - } - - return nil -} - -func (f *Formatter) visit(path string, fileInfo os.FileInfo, err error) error { - if err := walkWith(f.excludes, false)(path, fileInfo); err != nil { - return err - } else if fileInfo.IsDir() { - // skip if file is folder - return nil - } - - if strings.HasSuffix(strings.ToLower(path), "_test.go") || filepath.Ext(path) != ".go" { - // skip if file not has suffix "*.go" - return nil - } - - if strings.HasSuffix(strings.ToLower(path), f.mainFile) { - // skip main file - return nil - } - - err = f.FormatFile(path) - if err != nil { - return fmt.Errorf("ParseFile error:%+v", err) + formatter := &Formatter{ + debug: log.New(os.Stdout, "", log.LstdFlags), } - - return nil + return formatter } -// FormatMain format the main.go comment. -func (f *Formatter) FormatMain(mainFilepath string) error { +// Format formats swag comments in contents. It uses fileName to report errors +// that happen during parsing of contents. +func (f *Formatter) Format(fileName string, contents []byte) ([]byte, error) { fileSet := token.NewFileSet() - - astFile, err := goparser.ParseFile(fileSet, mainFilepath, nil, goparser.ParseComments) + ast, err := goparser.ParseFile(fileSet, fileName, contents, goparser.ParseComments) if err != nil { - return fmt.Errorf("cannot format file, err: %w path : %s ", err, mainFilepath) + return nil, err } + formattedComments := bytes.Buffer{} + oldComments := map[string]string{} - var ( - formatedComments = bytes.Buffer{} - // CommentCache - oldCommentsMap = make(map[string]string) - ) - - if astFile.Comments != nil { - for _, comment := range astFile.Comments { - formatFuncDoc(comment.List, &formatedComments, oldCommentsMap) + if ast.Comments != nil { + for _, comment := range ast.Comments { + formatFuncDoc(comment.List, &formattedComments, oldComments) } } - - return writeFormattedComments(mainFilepath, formatedComments, oldCommentsMap) + return formatComments(fileName, contents, formattedComments.Bytes(), oldComments), nil } -// FormatFile format the swag comment in go function. -func (f *Formatter) FormatFile(filepath string) error { - fileSet := token.NewFileSet() - - astFile, err := goparser.ParseFile(fileSet, filepath, nil, goparser.ParseComments) - if err != nil { - return fmt.Errorf("cannot format file, err: %w path : %s ", err, filepath) - } - - var ( - formatedComments = bytes.Buffer{} - // CommentCache - oldCommentsMap = make(map[string]string) - ) - - for _, astDescription := range astFile.Decls { - astDeclaration, ok := astDescription.(*ast.FuncDecl) - if ok && astDeclaration.Doc != nil && astDeclaration.Doc.List != nil { - formatFuncDoc(astDeclaration.Doc.List, &formatedComments, oldCommentsMap) +func formatComments(fileName string, contents []byte, formattedComments []byte, oldComments map[string]string) []byte { + for _, comment := range bytes.Split(formattedComments, []byte("\n")) { + splits := bytes.SplitN(comment, []byte(splitTag), 2) + if len(splits) == 2 { + hash, line := splits[0], splits[1] + contents = bytes.Replace(contents, []byte(oldComments[string(hash)]), line, 1) } } - - return writeFormattedComments(filepath, formatedComments, oldCommentsMap) -} - -func writeFormattedComments(filepath string, formatedComments bytes.Buffer, oldCommentsMap map[string]string) error { - // Replace the file - // Read the file - srcBytes, err := ioutil.ReadFile(filepath) - if err != nil { - return fmt.Errorf("cannot open file, err: %w path : %s ", err, filepath) - } - - replaceSrc, newComments := string(srcBytes), strings.Split(formatedComments.String(), "\n") - - for _, e := range newComments { - commentSplit := strings.Split(e, splitTag) - if len(commentSplit) == 2 { - commentHash, commentContent := commentSplit[0], commentSplit[1] - - if !isBlankComment(commentContent) { - replaceSrc = strings.Replace(replaceSrc, oldCommentsMap[commentHash], commentContent, 1) - } - } - } - - return writeBack(filepath, []byte(replaceSrc), srcBytes) + return contents } func formatFuncDoc(commentList []*ast.Comment, formattedComments io.Writer, oldCommentsMap map[string]string) { - tabWriter := tabwriter.NewWriter(formattedComments, 0, 0, 2, ' ', 0) + w := tabwriter.NewWriter(formattedComments, 0, 0, 2, ' ', 0) for _, comment := range commentList { - commentLine := comment.Text - if isSwagComment(commentLine) || isBlankComment(commentLine) { - cmd5 := fmt.Sprintf("%x", md5.Sum([]byte(commentLine))) - - // Find the separator and replace to \t - c := separatorFinder(commentLine, '\t') - oldCommentsMap[cmd5] = commentLine - + text := comment.Text + if attr, body, found := swagComment(text); found { + cmd5 := fmt.Sprintf("%x", md5.Sum([]byte(text))) + oldCommentsMap[cmd5] = text + + formatted := "// " + attr + if body != "" { + formatted += "\t" + splitComment2(attr, body) + } // md5 + splitTag + srcCommentLine // eg. xxx&*@Description get struct array - _, _ = fmt.Fprintln(tabWriter, cmd5+splitTag+c) - } - } - // format by tabWriter - _ = tabWriter.Flush() -} - -func separatorFinder(comment string, replacer byte) string { - commentBytes, commentLine := []byte(comment), strings.TrimSpace(strings.TrimLeft(comment, "/")) - - if len(commentLine) == 0 { - return "" - } - - attribute := strings.Fields(commentLine)[0] - attrLen := strings.Index(comment, attribute) + len(attribute) - attribute = strings.ToLower(attribute) - - var ( - length = attrLen - - // Check of @Param @Success @Failure @Response @Header. - specialTagForSplit = map[string]byte{ - paramAttr: 1, - successAttr: 1, - failureAttr: 1, - responseAttr: 1, - headerAttr: 1, + _, _ = fmt.Fprintln(w, cmd5+splitTag+formatted) } - ) - - _, ok := specialTagForSplit[attribute] - if ok { - return splitSpecialTags(commentBytes, length, replacer) - } - - for length < len(commentBytes) && commentBytes[length] == ' ' { - length++ - } - - if length >= len(commentBytes) { - return comment } - - commentBytes = replaceRange(commentBytes, attrLen, length, replacer) - - return string(commentBytes) -} - -func splitSpecialTags(commentBytes []byte, length int, rp byte) string { - var ( - skipFlag bool - skipChar = map[byte]byte{ - '"': 1, - '(': 1, - '{': 1, - '[': 1, - } - - skipCharEnd = map[byte]byte{ - '"': 1, - ')': 1, - '}': 1, - ']': 1, - } - ) - - for ; length < len(commentBytes); length++ { - if !skipFlag && commentBytes[length] == ' ' { - j := length - for j < len(commentBytes) && commentBytes[j] == ' ' { - j++ + // format by tabwriter + _ = w.Flush() +} + +func splitComment2(attr, body string) string { + if specialTagForSplit[strings.ToLower(attr)] { + for i := 0; i < len(body); i++ { + if skipEnd, ok := skipChar[body[i]]; ok { + if skipLen := strings.IndexByte(body[i+1:], skipEnd); skipLen > 0 { + i += skipLen + } + } else if body[i] == ' ' { + j := i + for ; j < len(body) && body[j] == ' '; j++ { + } + body = replaceRange(body, i, j, "\t") } - - commentBytes = replaceRange(commentBytes, length, j, rp) } - - _, found := skipChar[commentBytes[length]] - if found && !skipFlag { - skipFlag = true - - continue - } - - _, found = skipCharEnd[commentBytes[length]] - if found && skipFlag { - skipFlag = false - } - } - - return string(commentBytes) -} - -func replaceRange(s []byte, start, end int, new byte) []byte { - if start > end || end < 1 { - return s - } - - if end > len(s) { - end = len(s) } - - s = append(s[:start], s[end-1:]...) - - s[start] = new - - return s + return body } -var swagCommentExpression = regexp.MustCompile("@[A-z]+") - -func isSwagComment(comment string) bool { - return swagCommentExpression.MatchString(strings.ToLower(comment)) +func replaceRange(s string, start, end int, new string) string { + return s[:start] + new + s[end:] } -func isBlankComment(comment string) bool { - return len(strings.TrimSpace(comment)) == 0 -} +var swagCommentLineExpression = regexp.MustCompile(`^\/\/\s+(@[\S.]+)\s*(.*)`) -// writeBack write to file. -func writeBack(filepath string, src, old []byte) error { - // make a temporary backup before overwriting original - backupName, err := backupFile(filepath+".", old, 0644) - if err != nil { - return err +func swagComment(comment string) (string, string, bool) { + matches := swagCommentLineExpression.FindStringSubmatch(comment) + if matches == nil { + return "", "", false } - - err = ioutil.WriteFile(filepath, src, 0644) - if err != nil { - _ = os.Rename(backupName, filepath) - - return err - } - - _ = os.Remove(backupName) - - return nil -} - -const chmodSupported = runtime.GOOS != "windows" - -// backupFile writes data to a new file named filename with permissions perm, -// with Date: Tue, 3 May 2022 02:48:56 -0300 Subject: [PATCH 07/12] chore: update @Produce comments (#1196) --- example/celler/controller/examples.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example/celler/controller/examples.go b/example/celler/controller/examples.go index 06f95370a..8118d7f0d 100644 --- a/example/celler/controller/examples.go +++ b/example/celler/controller/examples.go @@ -14,7 +14,7 @@ import ( // @Description do ping // @Tags example // @Accept json -// @Produce json +// @Produce plain // @Success 200 {string} string "pong" // @Failure 400 {string} string "ok" // @Failure 404 {string} string "ok" @@ -29,7 +29,7 @@ func (c *Controller) PingExample(ctx *gin.Context) { // @Description plus // @Tags example // @Accept json -// @Produce json +// @Produce plain // @Param val1 query int true "used for calc" // @Param val2 query int true "used for calc" // @Success 200 {integer} string "answer" @@ -57,7 +57,7 @@ func (c *Controller) CalcExample(ctx *gin.Context) { // @Description path params // @Tags example // @Accept json -// @Produce json +// @Produce plain // @Param group_id path int true "Group ID" // @Param account_id path int true "Account ID" // @Success 200 {string} string "answer" @@ -84,7 +84,7 @@ func (c *Controller) PathParamsExample(ctx *gin.Context) { // @Description custome header // @Tags example // @Accept json -// @Produce json +// @Produce plain // @Param Authorization header string true "Authentication header" // @Success 200 {string} string "answer" // @Failure 400 {string} string "ok" @@ -117,7 +117,7 @@ func (c *Controller) SecuritiesExample(ctx *gin.Context) { // @Description attribute // @Tags example // @Accept json -// @Produce json +// @Produce plain // @Param enumstring query string false "string enums" Enums(A, B, C) // @Param enumint query int false "int enums" Enums(1, 2, 3) // @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3) From 3cedab9b1c15f68109ed154a57d3164a2f8316b7 Mon Sep 17 00:00:00 2001 From: Wang Wang Date: Mon, 16 May 2022 16:48:06 +0800 Subject: [PATCH 08/12] fix: README_zh-CN.md translate bug (#1202) Co-authored-by: victorwwang --- README_zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_zh-CN.md b/README_zh-CN.md index 75dbbcee5..45a355f9e 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -63,7 +63,7 @@ $ go install github.com/swaggo/swag/cmd/swag@latest swag init ``` -确保导入了生成的`docs/docs.go`文件,这样特定的配置文件才会被初始化。如果通用API指数没有写在`main.go`中,可以使用`-g`标识符来告知swag。 +确保导入了生成的`docs/docs.go`文件,这样特定的配置文件才会被初始化。如果通用API注释没有写在`main.go`中,可以使用`-g`标识符来告知swag。 ```bash swag init -g http/api.go From 5f6b402a3a523595cb7e2902a04f9a0e5f02e313 Mon Sep 17 00:00:00 2001 From: pytimer Date: Wed, 18 May 2022 17:08:24 +0800 Subject: [PATCH 09/12] feat: Improve performance when generating spec with external dependencies (#1108) * Imporve performance when generating spec with external dependencies * Fix code review comments * Add go1.18 test code --- cmd/swag/main.go | 7 ++ gen/gen.go | 4 + golist.go | 74 +++++++++++ golist_test.go | 116 ++++++++++++++++++ packages.go | 6 + packages_test.go | 15 +++ parser.go | 54 +++++--- parser_test.go | 104 ++++++++++++++++ testdata/golist/api/api.go | 38 ++++++ testdata/golist/api/foo.c | 3 + testdata/golist/api/foo.h | 1 + testdata/golist/main.go | 36 ++++++ testdata/golist_disablemodule/api/api.go | 14 +++ testdata/golist_disablemodule/api/foo.c | 3 + testdata/golist_disablemodule/api/foo.h | 1 + testdata/golist_disablemodule/main.go | 34 +++++ testdata/golist_invalid/main.go | 32 +++++ .../invalid_external_pkg/invalid/normal.go | 5 + testdata/invalid_external_pkg/main.go | 3 + 19 files changed, 536 insertions(+), 14 deletions(-) create mode 100644 golist.go create mode 100644 golist_test.go create mode 100644 testdata/golist/api/api.go create mode 100644 testdata/golist/api/foo.c create mode 100644 testdata/golist/api/foo.h create mode 100644 testdata/golist/main.go create mode 100644 testdata/golist_disablemodule/api/api.go create mode 100644 testdata/golist_disablemodule/api/foo.c create mode 100644 testdata/golist_disablemodule/api/foo.h create mode 100644 testdata/golist_disablemodule/main.go create mode 100644 testdata/golist_invalid/main.go create mode 100644 testdata/invalid_external_pkg/invalid/normal.go create mode 100644 testdata/invalid_external_pkg/main.go diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 579178032..dde1e0b8c 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -29,6 +29,7 @@ const ( parseDepthFlag = "parseDepth" instanceNameFlag = "instanceName" overridesFileFlag = "overridesFile" + parseGoListFlag = "parseGoList" ) var initFlags = []cli.Flag{ @@ -110,6 +111,11 @@ var initFlags = []cli.Flag{ Value: gen.DefaultOverridesFile, Usage: "File to read global type overrides from.", }, + &cli.BoolFlag{ + Name: parseGoListFlag, + Value: true, + Usage: "Parse dependency via 'go list'", + }, } func initAction(ctx *cli.Context) error { @@ -142,6 +148,7 @@ func initAction(ctx *cli.Context) error { ParseDepth: ctx.Int(parseDepthFlag), InstanceName: ctx.String(instanceNameFlag), OverridesFile: ctx.String(overridesFileFlag), + ParseGoList: ctx.Bool(parseGoListFlag), }) } diff --git a/gen/gen.go b/gen/gen.go index ce9f0db4d..7198433f6 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -105,6 +105,9 @@ type Config struct { // OverridesFile defines global type overrides. OverridesFile string + + // ParseGoList whether swag use go list to parse dependency + ParseGoList bool } // Build builds swagger json file for given searchDir and mainAPIFile. Returns json. @@ -146,6 +149,7 @@ func (g *Gen) Build(config *Config) error { swag.SetCodeExamplesDirectory(config.CodeExampleFilesDir), swag.SetStrict(config.Strict), swag.SetOverrides(overrides), + swag.ParseUsingGoList(config.ParseGoList), ) p.PropNamingStrategy = config.PropNamingStrategy diff --git a/golist.go b/golist.go new file mode 100644 index 000000000..b8663abde --- /dev/null +++ b/golist.go @@ -0,0 +1,74 @@ +package swag + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "go/build" + "os/exec" + "path/filepath" +) + +func listPackages(ctx context.Context, dir string, env []string, args ...string) (pkgs []*build.Package, finalErr error) { + cmd := exec.CommandContext(ctx, "go", append([]string{"list", "-json", "-e"}, args...)...) + cmd.Env = env + cmd.Dir = dir + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + var stderrBuf bytes.Buffer + cmd.Stderr = &stderrBuf + defer func() { + if stderrBuf.Len() > 0 { + finalErr = fmt.Errorf("%v\n%s", finalErr, stderrBuf.Bytes()) + } + }() + + err = cmd.Start() + if err != nil { + return nil, err + } + dec := json.NewDecoder(stdout) + for dec.More() { + var pkg build.Package + err = dec.Decode(&pkg) + if err != nil { + return nil, err + } + pkgs = append(pkgs, &pkg) + } + err = cmd.Wait() + if err != nil { + return nil, err + } + return pkgs, nil +} + +func (parser *Parser) getAllGoFileInfoFromDepsByList(pkg *build.Package) error { + ignoreInternal := pkg.Goroot && !parser.ParseInternal + if ignoreInternal { // ignored internal + return nil + } + + srcDir := pkg.Dir + var err error + for i := range pkg.GoFiles { + err = parser.parseFile(pkg.ImportPath, filepath.Join(srcDir, pkg.GoFiles[i]), nil) + if err != nil { + return err + } + } + + // parse .go source files that import "C" + for i := range pkg.CgoFiles { + err = parser.parseFile(pkg.ImportPath, filepath.Join(srcDir, pkg.CgoFiles[i]), nil) + if err != nil { + return err + } + } + + return nil +} diff --git a/golist_test.go b/golist_test.go new file mode 100644 index 000000000..6b11da7b7 --- /dev/null +++ b/golist_test.go @@ -0,0 +1,116 @@ +package swag + +import ( + "context" + "errors" + "fmt" + "go/build" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestListPackages(t *testing.T) { + + cases := []struct { + name string + args []string + searchDir string + except error + }{ + { + name: "errorArgs", + args: []string{"-abc"}, + searchDir: "testdata/golist", + except: fmt.Errorf("exit status 2"), + }, + { + name: "normal", + args: []string{"-deps"}, + searchDir: "testdata/golist", + except: nil, + }, + { + name: "list error", + args: []string{"-deps"}, + searchDir: "testdata/golist_not_exist", + except: errors.New("searchDir not exist"), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + _, err := listPackages(context.TODO(), c.searchDir, nil, c.args...) + if c.except != nil { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} + +func TestGetAllGoFileInfoFromDepsByList(t *testing.T) { + p := New(ParseUsingGoList(true)) + pwd, err := os.Getwd() + assert.NoError(t, err) + cases := []struct { + name string + buildPackage *build.Package + ignoreInternal bool + except error + }{ + { + name: "normal", + buildPackage: &build.Package{ + Name: "main", + ImportPath: "github.com/swaggo/swag/testdata/golist", + Dir: "testdata/golist", + GoFiles: []string{"main.go"}, + CgoFiles: []string{"api/api.go"}, + }, + except: nil, + }, + { + name: "ignore internal", + buildPackage: &build.Package{ + Goroot: true, + }, + ignoreInternal: true, + except: nil, + }, + { + name: "gofiles error", + buildPackage: &build.Package{ + Dir: "testdata/golist_not_exist", + GoFiles: []string{"main.go"}, + }, + except: errors.New("file not exist"), + }, + { + name: "cgofiles error", + buildPackage: &build.Package{ + Dir: "testdata/golist_not_exist", + CgoFiles: []string{"main.go"}, + }, + except: errors.New("file not exist"), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if c.ignoreInternal { + p.ParseInternal = false + } + c.buildPackage.Dir = filepath.Join(pwd, c.buildPackage.Dir) + err := p.getAllGoFileInfoFromDepsByList(c.buildPackage) + if c.except != nil { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} diff --git a/packages.go b/packages.go index dd1a0e6c7..fc7a6bd2b 100644 --- a/packages.go +++ b/packages.go @@ -6,6 +6,7 @@ import ( "go/token" "os" "path/filepath" + "runtime" "sort" "strings" @@ -78,6 +79,11 @@ func (pkgDefs *PackagesDefinitions) CollectAstFile(packageDir, path string, astF func rangeFiles(files map[*ast.File]*AstFileInfo, handle func(filename string, file *ast.File) error) error { sortedFiles := make([]*AstFileInfo, 0, len(files)) for _, info := range 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()) { + continue + } sortedFiles = append(sortedFiles, info) } diff --git a/packages_test.go b/packages_test.go index 9163aa0a0..d74ba4d3a 100644 --- a/packages_test.go +++ b/packages_test.go @@ -4,6 +4,7 @@ import ( "go/ast" "go/token" "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -151,6 +152,20 @@ func TestPackage_rangeFiles(t *testing.T) { Path: "testdata/simple/api/api.go", PackagePath: "api", }, + { + Name: &ast.Ident{Name: "foo.go"}, + }: { + File: &ast.File{Name: &ast.Ident{Name: "foo.go"}}, + Path: "vendor/foo/foo.go", + PackagePath: "vendor/foo", + }, + { + Name: &ast.Ident{Name: "bar.go"}, + }: { + File: &ast.File{Name: &ast.Ident{Name: "bar.go"}}, + Path: filepath.Join(runtime.GOROOT(), "bar.go"), + PackagePath: "bar", + }, } var sorted []string diff --git a/parser.go b/parser.go index a390714f4..c8a18a6db 100644 --- a/parser.go +++ b/parser.go @@ -1,6 +1,7 @@ package swag import ( + "context" "encoding/json" "errors" "fmt" @@ -140,6 +141,9 @@ type Parser struct { // Overrides allows global replacements of types. A blank replacement will be skipped. Overrides map[string]string + + // parseGoList whether swag use go list to parse dependency + parseGoList bool } // FieldParserFactory create FieldParser. @@ -261,6 +265,13 @@ func SetOverrides(overrides map[string]string) func(parser *Parser) { } } +// ParseUsingGoList sets whether swag use go list to parse dependency +func ParseUsingGoList(enabled bool) func(parser *Parser) { + return func(p *Parser) { + p.parseGoList = enabled + } +} + // ParseAPI parses general api info for given searchDir and mainAPIFile. func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string, parseDepth int) error { return parser.ParseAPIMultiSearchDir([]string{searchDir}, mainAPIFile, parseDepth) @@ -287,26 +298,41 @@ func (parser *Parser) ParseAPIMultiSearchDir(searchDirs []string, mainAPIFile st return err } + // Use 'go list' command instead of depth.Resolve() if parser.ParseDependency { - var tree depth.Tree - tree.ResolveInternal = true - tree.MaxDepth = parseDepth - - pkgName, err := getPkgName(filepath.Dir(absMainAPIFilePath)) - if err != nil { - return err - } + if parser.parseGoList { + pkgs, err := listPackages(context.Background(), filepath.Dir(absMainAPIFilePath), nil, "-deps") + if err != nil { + return fmt.Errorf("pkg %s cannot find all dependencies, %s", filepath.Dir(absMainAPIFilePath), err) + } - err = tree.Resolve(pkgName) - if err != nil { - return fmt.Errorf("pkg %s cannot find all dependencies, %s", pkgName, err) - } + length := len(pkgs) + for i := 0; i < length; i++ { + err := parser.getAllGoFileInfoFromDepsByList(pkgs[i]) + if err != nil { + return err + } + } + } else { + var t depth.Tree + t.ResolveInternal = true + t.MaxDepth = parseDepth - for i := 0; i < len(tree.Root.Deps); i++ { - err := parser.getAllGoFileInfoFromDeps(&tree.Root.Deps[i]) + pkgName, err := getPkgName(filepath.Dir(absMainAPIFilePath)) if err != nil { return err } + + err = t.Resolve(pkgName) + if err != nil { + return fmt.Errorf("pkg %s cannot find all dependencies, %s", pkgName, err) + } + for i := 0; i < len(t.Root.Deps); i++ { + err := parser.getAllGoFileInfoFromDeps(&t.Root.Deps[i]) + if err != nil { + return err + } + } } } diff --git a/parser_test.go b/parser_test.go index 392a04fe2..45d02bdfd 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3,6 +3,7 @@ package swag import ( "bytes" "encoding/json" + "errors" "go/ast" goparser "go/parser" "go/token" @@ -2159,6 +2160,109 @@ func TestParseExternalModels(t *testing.T) { assert.Equal(t, string(expected), string(b)) } +func TestParseGoList(t *testing.T) { + mainAPIFile := "main.go" + p := New(ParseUsingGoList(true)) + p.ParseDependency = true + + go111moduleEnv := os.Getenv("GO111MODULE") + + cases := []struct { + name string + gomodule bool + searchDir string + err error + run func(searchDir string) error + }{ + { + name: "disableGOMODULE", + gomodule: false, + searchDir: "testdata/golist_disablemodule", + run: func(searchDir string) error { + return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + }, + }, + { + name: "enableGOMODULE", + gomodule: true, + searchDir: "testdata/golist", + run: func(searchDir string) error { + return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + }, + }, + { + name: "invalid_main", + gomodule: true, + searchDir: "testdata/golist_invalid", + err: errors.New("no such file or directory"), + run: func(searchDir string) error { + return p.ParseAPI(searchDir, "invalid/main.go", defaultParseDepth) + }, + }, + { + name: "internal_invalid_pkg", + gomodule: true, + searchDir: "testdata/golist_invalid", + err: errors.New("expected 'package', found This"), + run: func(searchDir string) error { + mockErrGoFile := "testdata/golist_invalid/err.go" + f, err := os.OpenFile(mockErrGoFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer f.Close() + _, err = f.Write([]byte(`package invalid + +function a() {}`)) + if err != nil { + return err + } + defer os.Remove(mockErrGoFile) + return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + }, + }, + { + name: "invalid_pkg", + gomodule: true, + searchDir: "testdata/golist_invalid", + err: errors.New("expected 'package', found This"), + run: func(searchDir string) error { + mockErrGoFile := "testdata/invalid_external_pkg/invalid/err.go" + f, err := os.OpenFile(mockErrGoFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer f.Close() + _, err = f.Write([]byte(`package invalid + +function a() {}`)) + if err != nil { + return err + } + defer os.Remove(mockErrGoFile) + return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if c.gomodule { + os.Setenv("GO111MODULE", "on") + } else { + os.Setenv("GO111MODULE", "off") + } + err := c.run(c.searchDir) + os.Setenv("GO111MODULE", go111moduleEnv) + if c.err == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } +} + func TestParser_ParseStructArrayObject(t *testing.T) { t.Parallel() diff --git a/testdata/golist/api/api.go b/testdata/golist/api/api.go new file mode 100644 index 000000000..09191daff --- /dev/null +++ b/testdata/golist/api/api.go @@ -0,0 +1,38 @@ +package api + +/* +#include "foo.h" +*/ +import "C" +import ( + "fmt" + "net/http" +) + +func PrintInt(i, j int) { + res := C.add(C.int(i), C.int(j)) + fmt.Println(res) +} + +type Foo struct { + ID int `json:"id"` + Name string `json:"name"` + PhotoUrls []string `json:"photoUrls"` + Status string `json:"status"` +} + +// GetFoo example +// @Summary Get foo +// @Description get foo +// @ID foo +// @Accept json +// @Produce json +// @Param some_id query int true "Some ID" +// @Param some_foo formData Foo true "Foo" +// @Success 200 {string} string "ok" +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 404 {object} web.APIError "Can not find ID" +// @Router /testapi/foo [get] +func GetFoo(w http.ResponseWriter, r *http.Request) { + // write your code +} diff --git a/testdata/golist/api/foo.c b/testdata/golist/api/foo.c new file mode 100644 index 000000000..082fc1e6f --- /dev/null +++ b/testdata/golist/api/foo.c @@ -0,0 +1,3 @@ +int add(int a, int b) { + return a + b; +} \ No newline at end of file diff --git a/testdata/golist/api/foo.h b/testdata/golist/api/foo.h new file mode 100644 index 000000000..0228a4c3c --- /dev/null +++ b/testdata/golist/api/foo.h @@ -0,0 +1 @@ +int add(int, int); \ No newline at end of file diff --git a/testdata/golist/main.go b/testdata/golist/main.go new file mode 100644 index 000000000..70143eb88 --- /dev/null +++ b/testdata/golist/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/example/basic/api" + goapi "github.com/swaggo/swag/testdata/golist/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @query.collection.format multi +// @host petstore.swagger.io +// @BasePath /v2 +func main() { + goapi.PrintInt(10, 5) + http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt) + http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString) + http.HandleFunc("/testapi/upload", api.Upload) + http.HandleFunc("/testapi/foo", goapi.GetFoo) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/golist_disablemodule/api/api.go b/testdata/golist_disablemodule/api/api.go new file mode 100644 index 000000000..1f1f28d60 --- /dev/null +++ b/testdata/golist_disablemodule/api/api.go @@ -0,0 +1,14 @@ +package api + +/* +#include "foo.h" +*/ +import "C" +import ( + "fmt" +) + +func PrintInt(i, j int) { + res := C.add(C.int(i), C.int(j)) + fmt.Println(res) +} diff --git a/testdata/golist_disablemodule/api/foo.c b/testdata/golist_disablemodule/api/foo.c new file mode 100644 index 000000000..082fc1e6f --- /dev/null +++ b/testdata/golist_disablemodule/api/foo.c @@ -0,0 +1,3 @@ +int add(int a, int b) { + return a + b; +} \ No newline at end of file diff --git a/testdata/golist_disablemodule/api/foo.h b/testdata/golist_disablemodule/api/foo.h new file mode 100644 index 000000000..0228a4c3c --- /dev/null +++ b/testdata/golist_disablemodule/api/foo.h @@ -0,0 +1 @@ +int add(int, int); \ No newline at end of file diff --git a/testdata/golist_disablemodule/main.go b/testdata/golist_disablemodule/main.go new file mode 100644 index 000000000..ab7d61735 --- /dev/null +++ b/testdata/golist_disablemodule/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/example/basic/api" + internalapi "github.com/swaggo/swag/testdata/golist_disablemodule/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @host petstore.swagger.io +// @BasePath /v2 +func main() { + internalapi.PrintInt(0, 1) + http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt) + http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString) + http.HandleFunc("/testapi/upload", api.Upload) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/golist_invalid/main.go b/testdata/golist_invalid/main.go new file mode 100644 index 000000000..69bae8025 --- /dev/null +++ b/testdata/golist_invalid/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/example/basic/api" + "github.com/swaggo/swag/testdata/invalid_external_pkg/invalid" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @host petstore.swagger.io +// @BasePath /v2 +func main() { + invalid.Foo() + http.HandleFunc("/testapi/upload", api.Upload) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/invalid_external_pkg/invalid/normal.go b/testdata/invalid_external_pkg/invalid/normal.go new file mode 100644 index 000000000..f58a341df --- /dev/null +++ b/testdata/invalid_external_pkg/invalid/normal.go @@ -0,0 +1,5 @@ +package invalid + +func Foo() { + +} diff --git a/testdata/invalid_external_pkg/main.go b/testdata/invalid_external_pkg/main.go new file mode 100644 index 000000000..38dd16da6 --- /dev/null +++ b/testdata/invalid_external_pkg/main.go @@ -0,0 +1,3 @@ +package main + +func main() {} From 3b580a0804332f627c5d42fee2d59e91c8ae3e7c Mon Sep 17 00:00:00 2001 From: jixiuf Date: Sat, 21 May 2022 04:23:23 +0800 Subject: [PATCH 10/12] feat: add --quiet=true for swag init, make the debug logger quiet. (#1206) Co-authored-by: jixiufeng --- cmd/swag/main.go | 12 ++++++++++++ gen/gen.go | 3 +++ gen/gen_test.go | 29 +++++++++++++++++++++++++++++ parser.go | 5 ++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index dde1e0b8c..0c689f626 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io/ioutil" "log" "os" "strings" @@ -30,9 +31,15 @@ const ( instanceNameFlag = "instanceName" overridesFileFlag = "overridesFile" parseGoListFlag = "parseGoList" + quietFlag = "quiet" ) var initFlags = []cli.Flag{ + &cli.BoolFlag{ + Name: quietFlag, + Aliases: []string{"q"}, + Usage: "Make the logger quiet.", + }, &cli.StringFlag{ Name: generalInfoFlag, Aliases: []string{"g"}, @@ -131,6 +138,10 @@ func initAction(ctx *cli.Context) error { if len(outputTypes) == 0 { return fmt.Errorf("no output types specified") } + var logger swag.Debugger + if ctx.Bool(quietFlag) { + logger = log.New(ioutil.Discard, "", log.LstdFlags) + } return gen.New().Build(&gen.Config{ SearchDir: ctx.String(searchDirFlag), @@ -149,6 +160,7 @@ func initAction(ctx *cli.Context) error { InstanceName: ctx.String(instanceNameFlag), OverridesFile: ctx.String(overridesFileFlag), ParseGoList: ctx.Bool(parseGoListFlag), + Debugger: logger, }) } diff --git a/gen/gen.go b/gen/gen.go index 7198433f6..ecdb9373d 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -57,6 +57,8 @@ func New() *Gen { // Config presents Gen configurations. type Config struct { + Debugger swag.Debugger + // SearchDir the swag would parse,comma separated if multiple SearchDir string @@ -145,6 +147,7 @@ func (g *Gen) Build(config *Config) error { log.Println("Generate swagger docs....") p := swag.New(swag.SetMarkdownFileDirectory(config.MarkdownFilesDir), + swag.SetDebugger(config.Debugger), swag.SetExcludedDirsAndFiles(config.Excludes), swag.SetCodeExamplesDirectory(config.CodeExampleFilesDir), swag.SetStrict(config.Strict), diff --git a/gen/gen_test.go b/gen/gen_test.go index 475d8f73e..62a26c2d3 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -1,10 +1,12 @@ package gen import ( + "bytes" "encoding/json" "errors" "fmt" "io/ioutil" + "log" "os" "os/exec" "path" @@ -750,3 +752,30 @@ func TestGen_TypeOverridesFile(t *testing.T) { assert.NoError(t, err) }) } +func TestGen_Debugger(t *testing.T) { + var buf bytes.Buffer + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, + PropNamingStrategy: "", + Debugger: log.New(&buf, "", log.LstdFlags), + } + assert.True(t, buf.Len() == 0) + assert.NoError(t, New().Build(config)) + assert.True(t, buf.Len() > 0) + + expectedFiles := []string{ + filepath.Join(config.OutputDir, "docs.go"), + filepath.Join(config.OutputDir, "swagger.json"), + filepath.Join(config.OutputDir, "swagger.yaml"), + } + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + require.NoError(t, err) + } + + _ = os.Remove(expectedFile) + } +} diff --git a/parser.go b/parser.go index c8a18a6db..f489ea37d 100644 --- a/parser.go +++ b/parser.go @@ -245,7 +245,10 @@ func SetStrict(strict bool) func(*Parser) { // SetDebugger allows the use of user-defined implementations. func SetDebugger(logger Debugger) func(parser *Parser) { return func(p *Parser) { - p.debug = logger + if logger != nil { + p.debug = logger + } + } } From e767abb3b9f829224205cbd37cc0f7fe432078ce Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Fri, 20 May 2022 23:41:14 +0300 Subject: [PATCH 11/12] chore: refactor parser (#1191) --- parser.go | 265 ++++++++++++++++++++++--------------------------- parser_test.go | 71 +++++++------ 2 files changed, 158 insertions(+), 178 deletions(-) diff --git a/parser.go b/parser.go index f489ea37d..815b2f8c6 100644 --- a/parser.go +++ b/parser.go @@ -49,9 +49,21 @@ const ( deprecatedAttr = "@deprecated" securityAttr = "@security" titleAttr = "@title" + conNameAttr = "@contact.name" + conURLAttr = "@contact.url" + conEmailAttr = "@contact.email" + licNameAttr = "@license.name" + licURLAttr = "@license.url" versionAttr = "@version" descriptionAttr = "@description" descriptionMarkdownAttr = "@description.markdown" + secBasicAttr = "@securitydefinitions.basic" + secAPIKeyAttr = "@securitydefinitions.apikey" + secApplicationAttr = "@securitydefinitions.oauth2.application" + secImplicitAttr = "@securitydefinitions.oauth2.implicit" + secPasswordAttr = "@securitydefinitions.oauth2.password" + secAccessCodeAttr = "@securitydefinitions.oauth2.accesscode" + tosAttr = "@termsofservice" xCodeSamplesAttr = "@x-codesamples" scopeAttrPrefix = "@scope." ) @@ -385,14 +397,6 @@ func getPkgName(searchDir string) (string, error) { return outStr, nil } -func initIfEmpty(license *spec.License) *spec.License { - if license == nil { - return new(spec.License) - } - - return license -} - // ParseGeneralAPIInfo parses general api info for given mainAPIFile path. func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error { fileTree, err := goparser.ParseFile(token.NewFileSet(), mainAPIFile, nil, goparser.ParseComments) @@ -425,16 +429,15 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { commentLine := comments[line] attribute := strings.Split(commentLine, " ")[0] value := strings.TrimSpace(commentLine[len(attribute):]) + multilineBlock := false if previousAttribute == attribute { multilineBlock = true } - switch strings.ToLower(attribute) { - case versionAttr: - parser.swagger.Info.Version = value - case titleAttr: - parser.swagger.Info.Title = value + switch attr := strings.ToLower(attribute); attr { + case versionAttr, titleAttr, tosAttr, licNameAttr, licURLAttr, conNameAttr, conURLAttr, conEmailAttr: + setSwaggerInfo(parser.swagger, attr, value) case descriptionAttr: if multilineBlock { parser.swagger.Info.Description += "\n" + value @@ -442,32 +445,20 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { continue } - parser.swagger.Info.Description = value - case "@description.markdown": + setSwaggerInfo(parser.swagger, attr, value) + case descriptionMarkdownAttr: commentInfo, err := getMarkdownForTag("api", parser.markdownFileDir) if err != nil { return err } - parser.swagger.Info.Description = string(commentInfo) - case "@termsofservice": - parser.swagger.Info.TermsOfService = value - case "@contact.name": - parser.swagger.Info.Contact.Name = value - case "@contact.email": - parser.swagger.Info.Contact.Email = value - case "@contact.url": - parser.swagger.Info.Contact.URL = value - case "@license.name": - parser.swagger.Info.License = initIfEmpty(parser.swagger.Info.License) - parser.swagger.Info.License.Name = value - case "@license.url": - parser.swagger.Info.License = initIfEmpty(parser.swagger.Info.License) - parser.swagger.Info.License.URL = value + setSwaggerInfo(parser.swagger, descriptionAttr, string(commentInfo)) + case "@host": parser.swagger.Host = value case "@basepath": parser.swagger.BasePath = value + case acceptAttr: err := parser.ParseAcceptComment(value) if err != nil { @@ -479,7 +470,7 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { return err } case "@schemes": - parser.swagger.Schemes = getSchemes(commentLine) + parser.swagger.Schemes = strings.Split(value, " ") case "@tag.name": parser.swagger.Tags = append(parser.swagger.Tags, spec.Tag{ TagProps: spec.TagProps{ @@ -516,43 +507,15 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { tag.TagProps.ExternalDocs.Description = value replaceLastTag(parser.swagger.Tags, tag) - case "@securitydefinitions.basic": - parser.swagger.SecurityDefinitions[value] = spec.BasicAuth() - case "@securitydefinitions.apikey": - attrMap, _, extensions, err := parseSecAttr(attribute, []string{"@in", "@name"}, comments, &line) - if err != nil { - return err - } - - parser.swagger.SecurityDefinitions[value] = tryAddDescription(spec.APIKeyAuth(attrMap["@name"], attrMap["@in"]), extensions) - case "@securitydefinitions.oauth2.application": - attrMap, scopes, extensions, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &line) - if err != nil { - return err - } - parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Application(attrMap["@tokenurl"], scopes, extensions), extensions) - case "@securitydefinitions.oauth2.implicit": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@authorizationurl"}, comments, &line) + case secBasicAttr, secAPIKeyAttr, secApplicationAttr, secImplicitAttr, secPasswordAttr, secAccessCodeAttr: + scheme, err := parseSecAttributes(attribute, comments, &line) if err != nil { return err } - parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Implicit(attrs["@authorizationurl"], scopes, ext), ext) - case "@securitydefinitions.oauth2.password": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl"}, comments, &line) - if err != nil { - return err - } + parser.swagger.SecurityDefinitions[value] = scheme - parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2Password(attrs["@tokenurl"], scopes, ext), ext) - case "@securitydefinitions.oauth2.accesscode": - attrs, scopes, ext, err := parseSecAttr(attribute, []string{"@tokenurl", "@authorizationurl"}, comments, &line) - if err != nil { - return err - } - - parser.swagger.SecurityDefinitions[value] = tryAddDescription(secOAuth2AccessToken(attrs["@authorizationurl"], attrs["@tokenurl"], scopes, ext), ext) case "@query.collection.format": parser.collectionFormatInQuery = value default: @@ -607,47 +570,62 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { return nil } -func tryAddDescription(spec *spec.SecurityScheme, extensions map[string]interface{}) *spec.SecurityScheme { - if val, ok := extensions["@description"]; ok { - if str, ok := val.(string); ok { - spec.Description = str - } +func setSwaggerInfo(swagger *spec.Swagger, attribute, value string) { + switch attribute { + case versionAttr: + swagger.Info.Version = value + case titleAttr: + swagger.Info.Title = value + case tosAttr: + swagger.Info.TermsOfService = value + case descriptionAttr: + swagger.Info.Description = value + case conNameAttr: + swagger.Info.Contact.Name = value + case conEmailAttr: + swagger.Info.Contact.Email = value + case conURLAttr: + swagger.Info.Contact.URL = value + case licNameAttr: + swagger.Info.License = initIfEmpty(swagger.Info.License) + swagger.Info.License.Name = value + case licURLAttr: + swagger.Info.License = initIfEmpty(swagger.Info.License) + swagger.Info.License.URL = value } - - return spec -} - -// ParseAcceptComment parses comment for given `accept` comment string. -func (parser *Parser) ParseAcceptComment(commentLine string) error { - return parseMimeTypeList(commentLine, &parser.swagger.Consumes, "%v accept type can't be accepted") -} - -// ParseProduceComment parses comment for given `produce` comment string. -func (parser *Parser) ParseProduceComment(commentLine string) error { - return parseMimeTypeList(commentLine, &parser.swagger.Produces, "%v produce type can't be accepted") } -func isGeneralAPIComment(comments []string) bool { - for _, commentLine := range comments { - attribute := strings.ToLower(strings.Split(commentLine, " ")[0]) - switch attribute { - // The @summary, @router, @success, @failure annotation belongs to Operation - case summaryAttr, routerAttr, successAttr, failureAttr, responseAttr: - return false - } +func parseSecAttributes(context string, lines []string, index *int) (*spec.SecurityScheme, error) { + const ( + in = "@in" + name = "@name" + descriptionAttr = "@description" + tokenURL = "@tokenurl" + authorizationURL = "@authorizationurl" + ) + + var search []string + + attribute := strings.ToLower(strings.Split(lines[*index], " ")[0]) + switch attribute { + case secBasicAttr: + return spec.BasicAuth(), nil + case secAPIKeyAttr: + search = []string{in, name} + case secApplicationAttr, secPasswordAttr: + search = []string{tokenURL} + case secImplicitAttr: + search = []string{authorizationURL} + case secAccessCodeAttr: + search = []string{tokenURL, authorizationURL} } - return true -} - -func parseSecAttr(context string, search []string, lines []string, index *int) (map[string]string, map[string]string, map[string]interface{}, error) { - attrMap := map[string]string{} - scopes := map[string]string{} - extensions := map[string]interface{}{} - // For the first line we get the attributes in the context parameter, so we skip to the next one *index++ + attrMap, scopes := make(map[string]string), make(map[string]string) + extensions, description := make(map[string]interface{}), "" + for ; *index < len(lines); *index++ { v := lines[*index] @@ -662,7 +640,7 @@ func parseSecAttr(context string, search []string, lines []string, index *int) ( isExists, err := isExistsScope(securityAttr) if err != nil { - return nil, nil, nil, err + return nil, err } if isExists { @@ -675,8 +653,8 @@ func parseSecAttr(context string, search []string, lines []string, index *int) ( } // Not mandatory field - if securityAttr == "@description" { - extensions[securityAttr] = strings.TrimSpace(v[len(securityAttr):]) + if securityAttr == descriptionAttr { + description = strings.TrimSpace(v[len(securityAttr):]) } // next securityDefinitions @@ -689,70 +667,66 @@ func parseSecAttr(context string, search []string, lines []string, index *int) ( } if len(attrMap) != len(search) { - return nil, nil, nil, fmt.Errorf("%s is %v required", context, search) + return nil, fmt.Errorf("%s is %v required", context, search) } - return attrMap, scopes, extensions, nil -} - -type ( - authExtensions map[string]interface{} - authScopes map[string]string -) + var scheme *spec.SecurityScheme -func secOAuth2Application(tokenURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { - securityScheme := spec.OAuth2Application(tokenURL) - securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) - for scope, description := range scopes { - securityScheme.AddScope(scope, description) + switch attribute { + case secAPIKeyAttr: + scheme = spec.APIKeyAuth(attrMap[name], attrMap[in]) + case secApplicationAttr: + scheme = spec.OAuth2Application(attrMap[tokenURL]) + case secImplicitAttr: + scheme = spec.OAuth2Implicit(attrMap[authorizationURL]) + case secPasswordAttr: + scheme = spec.OAuth2Password(attrMap[tokenURL]) + case secAccessCodeAttr: + scheme = spec.OAuth2AccessToken(attrMap[authorizationURL], attrMap[tokenURL]) } - return securityScheme -} + scheme.Description = description -func secOAuth2Implicit(authorizationURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { - securityScheme := spec.OAuth2Implicit(authorizationURL) - securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) + for extKey, extValue := range extensions { + scheme.AddExtension(extKey, extValue) + } - for scope, description := range scopes { - securityScheme.AddScope(scope, description) + for scope, scopeDescription := range scopes { + scheme.AddScope(scope, scopeDescription) } - return securityScheme + return scheme, nil } -func secOAuth2Password(tokenURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { - securityScheme := spec.OAuth2Password(tokenURL) - securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) - - for scope, description := range scopes { - securityScheme.AddScope(scope, description) +func initIfEmpty(license *spec.License) *spec.License { + if license == nil { + return new(spec.License) } - return securityScheme + return license } -func secOAuth2AccessToken(authorizationURL, tokenURL string, scopes authScopes, extensions authExtensions) *spec.SecurityScheme { - securityScheme := spec.OAuth2AccessToken(authorizationURL, tokenURL) - securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions) - - for scope, description := range scopes { - securityScheme.AddScope(scope, description) - } +// ParseAcceptComment parses comment for given `accept` comment string. +func (parser *Parser) ParseAcceptComment(commentLine string) error { + return parseMimeTypeList(commentLine, &parser.swagger.Consumes, "%v accept type can't be accepted") +} - return securityScheme +// ParseProduceComment parses comment for given `produce` comment string. +func (parser *Parser) ParseProduceComment(commentLine string) error { + return parseMimeTypeList(commentLine, &parser.swagger.Produces, "%v produce type can't be accepted") } -func handleSecuritySchemaExtensions(providedExtensions authExtensions) spec.Extensions { - var extensions spec.Extensions - if len(providedExtensions) > 0 { - extensions = make(map[string]interface{}, len(providedExtensions)) - for key, value := range providedExtensions { - extensions[key] = value +func isGeneralAPIComment(comments []string) bool { + for _, commentLine := range comments { + attribute := strings.ToLower(strings.Split(commentLine, " ")[0]) + switch attribute { + // The @summary, @router, @success, @failure annotation belongs to Operation + case summaryAttr, routerAttr, successAttr, failureAttr, responseAttr: + return false } } - return extensions + return true } func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) { @@ -800,13 +774,6 @@ func isExistsScope(scope string) (bool, error) { return strings.Contains(scope, scopeAttrPrefix), nil } -// getSchemes parses swagger schemes for given commentLine. -func getSchemes(commentLine string) []string { - attribute := strings.ToLower(strings.Split(commentLine, " ")[0]) - - return strings.Split(strings.TrimSpace(commentLine[len(attribute):]), " ") -} - // ParseRouterAPIInfo parses router api info for given astFile. func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) error { for _, astDescription := range astFile.Decls { @@ -1394,7 +1361,7 @@ 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) +// defineTypeOfExample example value define the type (object and array unsupported). func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{}, error) { switch schemaType { case STRING: diff --git a/parser_test.go b/parser_test.go index 45d02bdfd..07ac66518 100644 --- a/parser_test.go +++ b/parser_test.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" "reflect" + "strings" "testing" "github.com/go-openapi/spec" @@ -810,14 +811,6 @@ func TestParser_ParseType(t *testing.T) { assert.NotNil(t, p.packages.uniqueDefinitions["web.Pet2"]) } -func TestGetSchemes(t *testing.T) { - t.Parallel() - - schemes := getSchemes("@schemes http https") - expectedSchemes := []string{"http", "https"} - assert.Equal(t, expectedSchemes, schemes) -} - func TestParseSimpleApi1(t *testing.T) { t.Parallel() @@ -3565,57 +3558,77 @@ func TestTryAddDescription(t *testing.T) { extensions map[string]interface{} } tests := []struct { - name string - args args - want *spec.SecurityScheme + name string + lines []string + args args + want *spec.SecurityScheme }{ { name: "added description", - args: args{ - spec: &spec.SecurityScheme{}, - extensions: map[string]interface{}{ - "@description": "some description", - }, + lines: []string{ + "@securitydefinitions.apikey test", + "@in header", + "@name x-api-key", + "@description some description", }, want: &spec.SecurityScheme{ SecuritySchemeProps: spec.SecuritySchemeProps{ + Name: "x-api-key", + Type: "apiKey", + In: "header", Description: "some description", }, }, }, { name: "no description", - args: args{ - spec: &spec.SecurityScheme{}, - extensions: map[string]interface{}{ - "@not-description": "some description", - }, + lines: []string{ + "@securitydefinitions.oauth2.application swagger", + "@tokenurl https://example.com/oauth/token", + "@not-description some description", }, want: &spec.SecurityScheme{ SecuritySchemeProps: spec.SecuritySchemeProps{ + Type: "oauth2", + Flow: "application", + TokenURL: "https://example.com/oauth/token", Description: "", }, }, }, + { name: "description has invalid format", - args: args{ - spec: &spec.SecurityScheme{}, - extensions: map[string]interface{}{ - "@description": 12345, - }, + lines: []string{ + "@securitydefinitions.oauth2.implicit swagger", + "@authorizationurl https://example.com/oauth/token", + "@description 12345", }, + want: &spec.SecurityScheme{ SecuritySchemeProps: spec.SecuritySchemeProps{ - Description: "", + Type: "oauth2", + Flow: "implicit", + AuthorizationURL: "https://example.com/oauth/token", + Description: "12345", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tryAddDescription(tt.args.spec, tt.args.extensions); !reflect.DeepEqual(got, tt.want) { - t.Errorf("tryAddDescription() = %v, want %v", got, tt.want) + swag := spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + SecurityDefinitions: make(map[string]*spec.SecurityScheme), + }, + } + line := 0 + commentLine := tt.lines[line] + attribute := strings.Split(commentLine, " ")[0] + value := strings.TrimSpace(commentLine[len(attribute):]) + secAttr, _ := parseSecAttributes(attribute, tt.lines, &line) + if !reflect.DeepEqual(secAttr, tt.want) { + t.Errorf("setSwaggerSecurity() = %#v, want %#v", swag.SecurityDefinitions[value], tt.want) } }) } From 67cb7684c8adf9591bb221880e2079c34019dc39 Mon Sep 17 00:00:00 2001 From: Archie Skeoch <89483637+Skisocks@users.noreply.github.com> Date: Tue, 24 May 2022 06:21:42 +0100 Subject: [PATCH 12/12] fix: array enum varnames in arrays (#1187) --- field_parser.go | 18 +++++++++++----- field_parser_test.go | 25 ++++++++++++++++++++-- testdata/simple/expected.json | 38 ++++++++++++++++++++++++++++++++++ testdata/simple/web/handler.go | 33 +++++++++++++++-------------- 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/field_parser.go b/field_parser.go index a00b490dc..bd4fa4218 100644 --- a/field_parser.go +++ b/field_parser.go @@ -388,10 +388,6 @@ func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error { varNamesTag := ps.tag.Get("x-enum-varnames") if varNamesTag != "" { - if schema.Extensions == nil { - schema.Extensions = map[string]interface{}{} - } - varNames := strings.Split(varNamesTag, ",") if len(varNames) != len(field.enums) { return fmt.Errorf("invalid count of x-enum-varnames. expected %d, got %d", len(field.enums), len(varNames)) @@ -403,7 +399,19 @@ func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error { field.enumVarNames = append(field.enumVarNames, v) } - schema.Extensions["x-enum-varnames"] = field.enumVarNames + if field.schemaType == ARRAY { + // Add the var names in the items schema + if schema.Items.Schema.Extensions == nil { + schema.Items.Schema.Extensions = map[string]interface{}{} + } + schema.Items.Schema.Extensions["x-enum-varnames"] = field.enumVarNames + } else { + // Add to top level schema + if schema.Extensions == nil { + schema.Extensions = map[string]interface{}{} + } + schema.Extensions["x-enum-varnames"] = field.enumVarNames + } } eleSchema := schema diff --git a/field_parser_test.go b/field_parser_test.go index 1b0e1d639..aa89d6ac8 100644 --- a/field_parser_test.go +++ b/field_parser_test.go @@ -140,9 +140,8 @@ func TestDefaultFieldParser(t *testing.T) { Value: `json:"test" enums:"0,1,2" x-enum-varnames:"Daily,Weekly,Monthly"`, }}, ).ComplementSchema(&schema) - schema.Extensions.Add("x-enum-varnames", []string{"Daily", "Weekly", "Monthly"}) assert.NoError(t, err) - assert.Equal(t, []string{"Daily", "Weekly", "Monthly"}, schema.Extensions["x-enum-varnames"]) + assert.Equal(t, []interface{}{"Daily", "Weekly", "Monthly"}, schema.Extensions["x-enum-varnames"]) schema = spec.Schema{} schema.Type = []string{"int"} @@ -153,6 +152,28 @@ func TestDefaultFieldParser(t *testing.T) { }}, ).ComplementSchema(&schema) assert.Error(t, err) + + // Test for an array of enums + schema = spec.Schema{} + schema.Type = []string{"array"} + schema.Items = &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"int"}, + }, + }, + } + schema.Extensions = map[string]interface{}{} + schema.Enum = []interface{}{} + err = newTagBaseFieldParser( + &Parser{}, + &ast.Field{Tag: &ast.BasicLit{ + Value: `json:"test" enums:"0,1,2" x-enum-varnames:"Daily,Weekly,Monthly"`, + }}, + ).ComplementSchema(&schema) + assert.NoError(t, err) + assert.Equal(t, []interface{}{"Daily", "Weekly", "Monthly"}, schema.Items.Schema.Extensions["x-enum-varnames"]) + assert.Equal(t, spec.Extensions{}, schema.Extensions) }) t.Run("Default tag", func(t *testing.T) { diff --git a/testdata/simple/expected.json b/testdata/simple/expected.json index 159eee394..e3190a02b 100644 --- a/testdata/simple/expected.json +++ b/testdata/simple/expected.json @@ -518,6 +518,30 @@ ] } }, + "food_brands": { + "type": "array", + "items": { + "type": "string" + }, + "x-some-extension": true + }, + "food_types": { + "type": "array", + "items": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "x-enum-varnames": [ + "Wet", + "Dry", + "Raw" + ] + }, + "x-some-extension": true + }, "id": { "type": "integer", "format": "int64", @@ -572,6 +596,20 @@ "multipleOf": 0.01, "example": 3.25 }, + "single_enum_varname": { + "type": "integer", + "enum": [ + 1, + 2, + 3 + ], + "x-enum-varnames": [ + "one", + "two", + "three" + ], + "x-some-extension": true + }, "status": { "type": "string", "enum": [ diff --git a/testdata/simple/web/handler.go b/testdata/simple/web/handler.go index 8f42b07a7..09ee251e7 100644 --- a/testdata/simple/web/handler.go +++ b/testdata/simple/web/handler.go @@ -20,21 +20,24 @@ type Pet struct { PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"` } `json:"small_category"` } `json:"category"` - Name string `json:"name" example:"poti" binding:"required"` - PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"` - Tags []Tag `json:"tags"` - Pets *[]Pet2 `json:"pets"` - Pets2 []*Pet2 `json:"pets2"` - Status string `json:"status" enums:"healthy,ill"` - Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000" multipleOf:"0.01"` - IsAlive bool `json:"is_alive" example:"true" default:"true"` - Data interface{} `json:"data"` - Hidden string `json:"-"` - UUID uuid.UUID `json:"uuid"` - Decimal decimal.Decimal `json:"decimal"` - IntArray []int `json:"int_array" example:"1,2"` - StringMap map[string]string `json:"string_map" example:"key1:value,key2:value2"` - EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"` + Name string `json:"name" example:"poti" binding:"required"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"` + Tags []Tag `json:"tags"` + Pets *[]Pet2 `json:"pets"` + Pets2 []*Pet2 `json:"pets2"` + Status string `json:"status" enums:"healthy,ill"` + Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000" multipleOf:"0.01"` + IsAlive bool `json:"is_alive" example:"true" default:"true"` + Data interface{} `json:"data"` + Hidden string `json:"-"` + UUID uuid.UUID `json:"uuid"` + Decimal decimal.Decimal `json:"decimal"` + IntArray []int `json:"int_array" example:"1,2"` + StringMap map[string]string `json:"string_map" example:"key1:value,key2:value2"` + EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"` + FoodTypes []string `json:"food_types" swaggertype:"array,integer" enums:"0,1,2" x-enum-varnames:"Wet,Dry,Raw" extensions:"x-some-extension"` + FoodBrands []string `json:"food_brands" extensions:"x-some-extension"` + SingleEnumVarname string `json:"single_enum_varname" swaggertype:"integer" enums:"1,2,3" x-enum-varnames:"one,two,three" extensions:"x-some-extension"` } type Tag struct {