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