Skip to content

Commit

Permalink
added security definition description (#1174)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmorelli92 committed Apr 19, 2022
1 parent 050b0aa commit d209f71
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 40 deletions.
50 changes: 25 additions & 25 deletions README.md
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
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
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":
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
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
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
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

0 comments on commit d209f71

Please sign in to comment.