Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added security definition description #1174

Merged
merged 11 commits into from
Apr 19, 2022
50 changes: 25 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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.
)

Expand All @@ -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
Expand Down Expand Up @@ -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
```
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -487,7 +487,7 @@ type Foo struct {

Field Name | Type | Description
---|:---:|---
<a name="validate"></a>validate | `string` | Determines the validation for the parameter. Possible values are: `required`.
<a name="validate"></a>validate | `string` | Determines the validation for the parameter. Possible values are: `required`.
<a name="parameterDefault"></a>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.
<a name="parameterMaximum"></a>maximum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2.
<a name="parameterMinimum"></a>minimum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3.
Expand All @@ -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
Expand Down Expand Up @@ -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"
```
Expand Down Expand Up @@ -751,7 +751,7 @@ Rendered:
"id": "integer"
}
```


### Use swaggerignore tag to exclude a field

Expand Down
1 change: 1 addition & 0 deletions example/celler/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 34 additions & 14 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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":
pmorelli92 marked this conversation as resolved.
Show resolved Hide resolved
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:
Expand Down Expand Up @@ -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")
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand Down
84 changes: 83 additions & 1 deletion parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"log"
"os"
"path/filepath"
"reflect"
"testing"

"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -152,6 +154,7 @@ func TestParser_ParseGeneralApiInfo(t *testing.T) {
"paths": {},
"securityDefinitions": {
"ApiKeyAuth": {
"description": "some description",
"type": "apiKey",
"name": "Authorization",
"in": "header"
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
}
})
}
}
8 changes: 8 additions & 0 deletions swagger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ var doc = `{
}
}
}
},
"securityDefinitions": {
"ApiKey": {
"description: "some",
"type": "apiKey",
"name": "X-API-KEY",
"in": "header"
}
}
}`

Expand Down
1 change: 1 addition & 0 deletions testdata/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down