1
0

Updated libraries

This commit is contained in:
konrad
2019-05-07 21:42:24 +02:00
parent 2b160b73c3
commit 3d7fd9ca20
313 changed files with 37947 additions and 6783 deletions

View File

@ -1,4 +1,5 @@
testdata/simple/docs
dist
testdata/simple*/docs
cover.out
# Test binary, build with `go test -c`

View File

@ -1 +0,0 @@
^example

19
vendor/github.com/swaggo/swag/.goreleaser.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
build:
main: cmd/swag/main.go
archive:
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

View File

@ -1,15 +1,19 @@
language: go
sudo: false
go:
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
before_install:
- make deps
install:
- make install
script:
- make fmt-check
- make lint
- make vet
- make build
- make test

View File

@ -1,36 +1,78 @@
#! /usr/bin/make
GOCMD=$(shell which go)
GOLINT=$(shell which golint)
GOIMPORT=$(shell which goimports)
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
GOLIST=$(GOCMD) list
BINARY_NAME=swag
PACKAGES=$(shell $(GOLIST) -f {{.Dir}} ./... | grep -v /example)
GOCMD:=$(shell which go)
GOLINT:=$(shell which golint)
GOIMPORT:=$(shell which goimports)
GOFMT:=$(shell which gofmt)
GOBUILD:=$(GOCMD) build
GOCLEAN:=$(GOCMD) clean
GOTEST:=$(GOCMD) test
GOGET:=$(GOCMD) get
GOLIST:=$(GOCMD) list
GOVET:=$(GOCMD) vet
BINARY_NAME:=swag
PACKAGES:=$(shell $(GOLIST) ./...)
GOFILES:=$(shell find . -name "*.go" -type f)
export GO111MODULE := on
all: test build
.PHONY: build
build:
$(GOBUILD) -o $(BINARY_NAME) -v ./cmd/...
.PHONY: test
test:
$(GOTEST) -v ./...
echo "mode: count" > coverage.out
for PKG in $(PACKAGES); do \
$(GOCMD) test -v -covermode=count -coverprofile=profile.out $$PKG > tmp.out; \
cat tmp.out; \
if grep -q "^--- FAIL" tmp.out; then \
rm tmp.out; \
exit 1; \
elif grep -q "build failed" tmp.out; then \
rm tmp.out; \
exit; \
fi; \
if [ -f profile.out ]; then \
cat profile.out | grep -v "mode:" >> coverage.out; \
rm profile.out; \
fi; \
done
.PHONY: clean
clean:
$(GOCLEAN)
rm -f $(BINARY_NAME)
lint:
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
deps:
.PHONY: install
install:
$(GOGET) -v ./...
$(GOGET) github.com/stretchr/testify/assert
$(GOGET) golang.org/x/lint/golint
$(GOGET) golang.org/x/tools/cmd/goimports
.PHONY: lint
lint:
which golint || $(GOGET) -u golang.org/x/lint/golint
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
.PHONY: vet
vet:
$(GOVET) $(PACKAGES)
.PHONY: fmt
fmt:
$(GOFMT) -s -w $(GOFILES)
.PHONY: fmt-check
fmt-check:
@diff=$$($(GOFMT) -s -d $(GOFILES)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi;
.PHONY: view-covered
view-covered:
$(GOTEST) -coverprofile=cover.out $(TARGET)
$(GOCMD) tool cover -html=cover.out
$(GOCMD) tool cover -html=cover.out

View File

@ -1,71 +1,77 @@
# swag
<p align="center">
<img alt="swaggo" src="https://raw.githubusercontent.com/swaggo/swag/master/assets/swaggo.png" width="200">
</p>
<img align="right" width="180px" src="https://raw.githubusercontent.com/swaggo/swag/master/assets/swaggo.png">
<p align="center">
Automatically generate RESTful API documentation with Swagger 2.0 for Go.
</p>
[![Travis Status](https://img.shields.io/travis/swaggo/swag/master.svg)](https://travis-ci.org/swaggo/swag)
[![Coverage Status](https://img.shields.io/codecov/c/github/swaggo/swag/master.svg)](https://codecov.io/gh/swaggo/swag)
[![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)
[![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)
<p align="center">
<a href="https://travis-ci.org/swaggo/swag"><img alt="Travis Status" src="https://img.shields.io/travis/swaggo/swag/master.svg"></a>
<a href="https://codecov.io/gh/swaggo/swag"><img alt="Coverage Status" src="https://img.shields.io/codecov/c/github/swaggo/swag/master.svg"></a>
<a href="https://goreportcard.com/badge/github.com/swaggo/swag"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/swaggo/swag"></a>
<a href="https://codebeat.co/projects/github-com-swaggo-swag-master"><img alt="codebeat badge" src="https://codebeat.co/badges/71e2f5e5-9e6b-405d-baf9-7cc8b5037330" /></a>
<a href="https://godoc.org/github.com/swaggo/swag"><img alt="Go Doc" src="https://godoc.org/github.com/swaggo/swagg?status.svg"></a>
</p>
<p align="center">gopher image source is <a href="https://github.com/tenntenn/gopher-stickers">tenntenn/gopher-stickers.</a> It has licenses <a href="http://creativecommons.org/licenses/by/3.0/deed.en">creative commons licensing.</a></p>
## Content
- [Getting started](#getting-started)
- [Go web frameworks](#supported-web-frameworks)
- [Supported Web Frameworks](#supported-web-frameworks)
- [How to use it with Gin](#how-to-use-it-with-gin)
- [Implementation Status](#implementation-status)
- [swag cli](#swag-cli)
- [General API Info](#general-api-info)
- [Security](#security)
- [API Operation](#api-operation)
- [TIPS](#tips)
- [User defined structure with an array type](#user-defined-structure-with-an-array-type)
- [Use multiple path params](#use-multiple-path-params)
- [Example value of struct](#example-value-of-struct)
- [Description of struct](#description-of-struct)
- [About the Project](#about-the-project)
## Summary
Swag converts Go annotations to Swagger Documentation 2.0. We've created a variety of plugins for popular [Go web frameworks](#supported-web-frameworks). This allows you to quickly integrate with an existing Go project (using Swagger UI).
## Examples
[swaggo + gin](https://github.com/swaggo/swag/tree/master/example)
## Contents
- [Getting started](#getting-started)
- [Supported Web Frameworks](#supported-web-frameworks)
- [How to use it with Gin](#how-to-use-it-with-gin)
- [Implementation Status](#implementation-status)
- [Declarative Comments Format](#declarative-comments-format)
- [General API Info](##general-api-info)
- [API Operation](#api-operation)
- [Security](#security)
- [Examples](#examples)
- [Descriptions over multiple lines](#descriptions-over-multiple-lines)
- [User defined structure with an array type](#user-defined-structure-with-an-array-type)
- [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)
- [Description of struct](#description-of-struct)
- [Override swagger type of a struct field](#Override-swagger-type-of-a-struct-field)
- [Add extension info to struct field](#add-extension-info-to-struct-field)
- [How to using security annotations](#how-to-using-security-annotations)
- [About the Project](#about-the-project)
## Getting started
1. Add comments to your API source code, [See Declarative Comments Format](#general-api-info).
1. Add comments to your API source code, See [Declarative Comments Format](#declarative-comments-format).
2. Download swag by using:
```sh
$ go get -u github.com/swaggo/swag/cmd/swag
```
Or download the pre-compiled binaries binray form [release page](https://github.com/swaggo/swag/releases).
3. Run `swag init` in the project's root folder which contains the `main.go` file. This will parse your comments and generate the required files (`docs` folder and `docs/docs.go`).
```sh
$ swag init
```
4. In order to serve these files, you can utilize one of our supported plugins. For go's core library, check out [net/http](https://github.com/swaggo/http-swagger).
* Make sure to import the generated `docs/docs.go` so that your specific configuration gets `init`'ed.
* If your General API annotation do not live in `main.go`, you can let swag know with `-g`.
Make sure to import the generated `docs/docs.go` so that your specific configuration gets `init`'ed. If your General API annotations do not live in `main.go`, you can let swag know with `-g` flag.
```sh
swag init -g http/api.go
```
## swag cli
```sh
$ swag init -h
NAME:
swag init - Create docs.go
USAGE:
swag init [command options] [arguments...]
OPTIONS:
--generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go")
--dir value, -d value Directory you want to parse (default: "./")
--propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase")
--output value, -o value Output directory for al the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs")
--parseVendor Parse go files in 'vendor' folder, disabled by default --output value, -o value Output directory for al the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs")
```
## Supported Web Frameworks
- [gin](http://github.com/swaggo/gin-swagger)
@ -215,6 +221,7 @@ import (
// @Produce json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Header 200 {string} Token "qwerty"
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
@ -241,6 +248,7 @@ func (c *Controller) ShowAccount(ctx *gin.Context) {
// @Produce json
// @Param q query string false "name search by q"
// @Success 200 {array} model.Account
// @Header 200 {string} Token "qwerty"
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
@ -286,67 +294,33 @@ $ swag init
- [x] Grouping Operations With Tags
- [ ] Swagger Extensions
# swag cli
# Declarative Comments Format
```console
$ swag init -h
NAME:
swag init - Create docs.go
USAGE:
swag init [command options] [arguments...]
OPTIONS:
--generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go")
--dir value, -d value Directory you want to parse (default: "./")
--swagger value, -s value Output the swagger conf for json and yaml (default: "./docs/swagger")
--propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase")
```
# General API Info
## General API Info
**Example**
[celler/main.go](https://github.com/swaggo/swag/blob/master/example/celler/main.go)
| annotation | description | example |
|-----------------------|-------------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
| title | **Required.** The title of the application. | // @title Swagger Example API |
| version | **Required.** Provides the version of the application API. | // @version 1.0 |
| description | A short description of the application. | // @description This is a sample server celler server. |
| tag.name | Name of a tag. | // @tag.name This is the name of the tag |
| tag.description | Description of the tag | // @tag.description Cool Description |
| tag.docs.url | Url of the external Documentation of the tag | // @tag.docs.url https://example.com |
| tag.docs.descripiton | Description of the external Documentation of the tag | // @tag.docs.descirption Best example documentation |
| termsOfService | The Terms of Service for the API. | // @termsOfService http://swagger.io/terms/ |
| contact.name | The contact information for the exposed API. | // @contact.name API Support |
| contact.url | The URL pointing to the contact information. MUST be in the format of a URL. | // @contact.url http://www.swagger.io/support |
| contact.email | The email address of the contact person/organization. MUST be in the format of an email address.| // @contact.email support@swagger.io |
| license.name | **Required.** The license name used for the API. | // @license.name Apache 2.0 |
| license.url | A URL to the license used for the API. MUST be in the format of a URL. | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html |
| host | The host (name or ip) serving the API. | // @host localhost:8080 |
| BasePath | The base path on which the API is served. | // @BasePath /api/v1 |
| annotation | description | example |
|-------------|--------------------------------------------|---------------------------------|
| title | **Required.** The title of the application.| // @title Swagger Example API |
| version | **Required.** Provides the version of the application API.| // @version 1.0 |
| description | A short description of the application. |// @description This is a sample server celler server. |
| tag.name | Name of a tag.| // @tag.name This is the name of the tag |
| tag.description | Description of the tag | // @tag.description Cool Description |
| tag.docs.url | Url of the external Documentation of the tag | // @tag.docs.url https://example.com|
| tag.docs.descripiton | Description of the external Documentation of the tag| // @tag.docs.descirption Best example documentation |
| termsOfService | The Terms of Service for the API.| // @termsOfService http://swagger.io/terms/ |
| contact.name | The contact information for the exposed API.| // @contact.name API Support |
| contact.url | The URL pointing to the contact information. MUST be in the format of a URL. | // @contact.url http://www.swagger.io/support|
| contact.email| The email address of the contact person/organization. MUST be in the format of an email address.| // @contact.email support@swagger.io |
| license.name | **Required.** The license name used for the API.|// @license.name Apache 2.0|
| license.url | A URL to the license used for the API. MUST be in the format of a URL. | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html |
| host | The host (name or ip) serving the API. | // @host localhost:8080 |
| BasePath | The base path on which the API is served. | // @BasePath /api/v1 |
| schemes | The transfer protocol for the operation that separated by spaces. | // @schemes http https |
## Security
| 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 |
# API Operation
## API Operation
**Example**
[celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller)
@ -364,50 +338,30 @@ OPTIONS:
| security | [Security](#security) to each API operation. |
| success | Success response that separated by spaces. `return code`,`{param type}`,`data type`,`comment` |
| failure | Failure response that separated by spaces. `return code`,`{param type}`,`data type`,`comment` |
| header | Header in response that separated by spaces. `return code`,`{param type}`,`data type`,`comment` |
| router | Path definition that separated by spaces. `path`,`[httpMethod]` |
## Mime Types
| Mime Type | annotation |
|-----------------------------------|-----------------------------------------------------------|
| application/json | application/json, json |
| text/xml | text/xml, xml |
| text/plain | text/plain, plain |
| html | text/html, html |
| multipart/form-data | multipart/form-data, mpfd |
| application/x-www-form-urlencoded | application/x-www-form-urlencoded, x-www-form-urlencoded |
| application/vnd.api+json | application/vnd.api+json, json-api |
| application/x-json-stream | application/x-json-stream, json-stream |
| application/octet-stream | application/octet-stream, octet-stream |
| image/png | image/png, png |
| image/jpeg | image/jpeg, jpeg |
| image/gif | image/gif, gif |
`swag` accepts all MIME Types which are in the correct format, that is, match `*/*`.
Besides that, `swag` also accepts aliases for some MIME Types as follows:
## Security
| Alias | MIME Type |
|-----------------------|-----------------------------------|
| json | application/json |
| xml | text/xml |
| plain | text/plain |
| html | text/html |
| mpfd | multipart/form-data |
| x-www-form-urlencoded | application/x-www-form-urlencoded |
| json-api | application/vnd.api+json |
| json-stream | application/x-json-stream |
| octet-stream | application/octet-stream |
| png | image/png |
| jpeg | image/jpeg |
| gif | image/gif |
General API info.
```go
// @securityDefinitions.basic BasicAuth
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
```
Each API operation.
```go
// @Security ApiKeyAuth
```
Make it AND condition
```go
// @Security ApiKeyAuth
// @Security OAuth2Application[write, admin]
```
## Param Type
@ -426,6 +380,26 @@ Make it AND condition
- boolean (bool)
- user defined struct
## Security
| 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 |
## Attribute
```go
@ -457,12 +431,12 @@ Field Name | Type | Description
<a name="parameterMaxLength"></a>maxLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1.
<a name="parameterMinLength"></a>minLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2.
<a name="parameterEnums"></a>enums | [\*] | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1.
<a name="parameterFormat"></a>format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](#dataTypeFormat) for further details.
### Future
Field Name | Type | Description
---|:---:|---
<a name="parameterFormat"></a>format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](#dataTypeFormat) for further details.
<a name="parameterMultipleOf"></a>multipleOf | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1.
<a name="parameterPattern"></a>pattern | `string` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3.
<a name="parameterMaxItems"></a>maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2.
@ -470,7 +444,17 @@ Field Name | Type | Description
<a name="parameterUniqueItems"></a>uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4.
<a name="parameterCollectionFormat"></a>collectionFormat | `string` | Determines the format of the array if type array is used. Possible values are: <ul><li>`csv` - comma separated values `foo,bar`. <li>`ssv` - space separated values `foo bar`. <li>`tsv` - tab separated values `foo\tbar`. <li>`pipes` - pipe separated values <code>foo&#124;bar</code>. <li>`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". </ul> Default value is `csv`.
## TIPS
## Examples
### Descriptions over multiple lines
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
// @description This is the second line
// @description And so forth.
```
### User defined structure with an array type
@ -486,6 +470,13 @@ type Account struct {
Name string `json:"name" example:"account name"`
}
```
### Add a headers in response
```go
// @Success 200 {string} string "ok"
// @Header 200 {string} Location "/entity/1"
// @Header 200 {string} Token "qwerty"
```
### Use multiple path params
@ -511,22 +502,129 @@ type Account struct {
```go
type Account struct {
// ID this is userid
ID int `json:"id"
// ID this is userid
ID int `json:"id"`
Name string `json:"name"` // This is Name
}
```
### Override swagger type of a struct field
```go
type TimestampTime struct {
time.Time
}
///implement encoding.JSON.Marshaler interface
func (t *TimestampTime) MarshalJSON() ([]byte, error) {
bin := make([]byte, 16)
bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10)
return bin, nil
}
func (t *TimestampTime) UnmarshalJSON(bin []byte) error {
v, err := strconv.ParseInt(string(bin), 10, 64)
if err != nil {
return err
}
t.Time = time.Unix(v, 0)
return nil
}
///
type Account struct {
// Override primitive type by simply specifying it via `swaggertype` tag
ID sql.NullInt64 `json:"id" swaggertype:"integer"`
// Override struct type to a primitive type 'integer' by specifying it via `swaggertype` tag
RegisterTime TimestampTime `json:"register_time" swaggertype:"primitive,integer"`
// Array types can be overridden using "array,<prim_type>" format
Coeffs []big.Float `json:"coeffs" swaggertype:"array,number"`
}
```
### Add extension info to struct field
```go
type Account struct {
ID int `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-"
}
```
generate swagger doc as follows:
```go
"Account": {
"type": "object",
"properties": {
"id": {
"type": "string",
"x-nullable": true,
"x-abc": "def"
}
}
}
```
### How to using security annotations
General API info.
```go
// @securityDefinitions.basic BasicAuth
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
```
Each API operation.
```go
// @Security ApiKeyAuth
```
Make it AND condition
```go
// @Security ApiKeyAuth
// @Security OAuth2Application[write, admin]
```
## About the Project
This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks).
This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en).
## Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/swaggo/swag/graphs/contributors"><img src="https://opencollective.com/swag/contributors.svg?width=890&button=false" /></a>
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/swag#backer)]
<a href="https://opencollective.com/swag#backers" target="_blank"><img src="https://opencollective.com/swag/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/swag#sponsor)]
<a href="https://opencollective.com/swag/sponsor/0/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/1/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/2/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/3/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/4/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/5/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/6/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/7/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/8/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/swag/sponsor/9/website" target="_blank"><img src="https://opencollective.com/swag/sponsor/9/avatar.svg"></a>
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_large)

View File

@ -20,18 +20,25 @@ func main() {
Aliases: []string{"i"},
Usage: "Create docs.go",
Action: func(c *cli.Context) error {
dir := c.String("dir")
searchDir := c.String("dir")
mainAPIFile := c.String("generalInfo")
swaggerConfDir := c.String("swagger")
strategy := c.String("propertyStrategy")
outputDir := c.String("output")
parseVendor := c.Bool("parseVendor")
switch strategy {
case swag.CamelCase, swag.SnakeCase, swag.PascalCase:
default:
return errors.Errorf("not supported %s propertyStrategy", strategy)
}
gen.New().Build(dir, mainAPIFile, swaggerConfDir, strategy)
return nil
return gen.New().Build(&gen.Config{
SearchDir: searchDir,
MainAPIFile: mainAPIFile,
PropNamingStrategy: strategy,
OutputDir: outputDir,
ParseVendor: parseVendor,
})
},
Flags: []cli.Flag{
cli.StringFlag{
@ -44,16 +51,20 @@ func main() {
Value: "./",
Usage: "Directory you want to parse",
},
cli.StringFlag{
Name: "swagger, s",
Value: "./docs/swagger",
Usage: "Output the swagger conf for json and yaml",
},
cli.StringFlag{
Name: "propertyStrategy, p",
Value: "camelcase",
Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase",
},
cli.StringFlag{
Name: "output, o",
Value: "./docs",
Usage: "Output directory for al the generated files(swagger.json, swagger.yaml and doc.go)",
},
cli.BoolFlag{
Name: "parseVendor",
Usage: "Parse go files in 'vendor' folder, disabled by default",
},
},
},
}

30
vendor/github.com/swaggo/swag/debug.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package swag
import (
"log"
)
const (
test = iota
release
)
var swagMode = release
func isRelease() bool {
return swagMode == release
}
//Println calls Output to print to the standard logger when release mode.
func Println(v ...interface{}) {
if isRelease() {
log.Println(v...)
}
}
//Printf calls Output to print to the standard logger when release mode.
func Printf(format string, v ...interface{}) {
if isRelease() {
log.Printf(format, v...)
}
}

View File

@ -2,6 +2,7 @@ package gen
import (
"encoding/json"
"fmt"
"log"
"os"
"path"
@ -22,12 +23,38 @@ func New() *Gen {
return &Gen{}
}
// Config presents Gen configurations.
type Config struct {
// SearchDir the swag would be parse
SearchDir string
//OutputDir represents the output directory for al the generated files
OutputDir string
//MainAPIFile the Go file path in which 'swagger general API Info' is written
MainAPIFile string
//PropNamingStrategy represents property naming strategy like snakecase,camelcase,pascalcase
PropNamingStrategy string
//ParseVendor whether swag should be parse vendor folder
ParseVendor bool
}
// Build builds swagger json file for gived searchDir and mainAPIFile. Returns json
func (g *Gen) Build(searchDir, mainAPIFile, swaggerConfDir, propNamingStrategy string) error {
func (g *Gen) Build(config *Config) error {
if _, err := os.Stat(config.SearchDir); os.IsNotExist(err) {
return fmt.Errorf("dir: %s is not exist", config.SearchDir)
}
log.Println("Generate swagger docs....")
p := swag.New()
p.PropNamingStrategy = propNamingStrategy
p.ParseAPI(searchDir, mainAPIFile)
p.PropNamingStrategy = config.PropNamingStrategy
p.ParseVendor = config.ParseVendor
if err := p.ParseAPI(config.SearchDir, config.MainAPIFile); err != nil {
return err
}
swagger := p.GetSwagger()
b, err := json.MarshalIndent(swagger, "", " ")
@ -35,15 +62,14 @@ func (g *Gen) Build(searchDir, mainAPIFile, swaggerConfDir, propNamingStrategy s
return err
}
os.MkdirAll(path.Join(searchDir, "docs"), os.ModePerm)
docs, err := os.Create(path.Join(searchDir, "docs", "docs.go"))
os.MkdirAll(config.OutputDir, os.ModePerm)
docs, err := os.Create(path.Join(config.OutputDir, "docs.go"))
if err != nil {
return err
}
defer docs.Close()
os.Mkdir(swaggerConfDir, os.ModePerm)
swaggerJSON, err := os.Create(path.Join(swaggerConfDir, "swagger.json"))
swaggerJSON, err := os.Create(path.Join(config.OutputDir, "swagger.json"))
if err != nil {
return err
}
@ -51,7 +77,7 @@ func (g *Gen) Build(searchDir, mainAPIFile, swaggerConfDir, propNamingStrategy s
defer swaggerJSON.Close()
swaggerJSON.Write(b)
swaggerYAML, err := os.Create(path.Join(swaggerConfDir, "swagger.yaml"))
swaggerYAML, err := os.Create(path.Join(config.OutputDir, "swagger.yaml"))
if err != nil {
return err
}
@ -75,6 +101,9 @@ func (g *Gen) Build(searchDir, mainAPIFile, swaggerConfDir, propNamingStrategy s
}
log.Printf("create docs.go at %+v", docs.Name())
log.Printf("create swagger.json at %+v", swaggerJSON.Name())
log.Printf("create swagger.yaml at %+v", swaggerYAML.Name())
return nil
}

31
vendor/github.com/swaggo/swag/go.mod generated vendored Normal file
View File

@ -0,0 +1,31 @@
module github.com/swaggo/swag
require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/ghodss/yaml v1.0.0
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 // indirect
github.com/gin-gonic/gin v1.3.0
github.com/go-openapi/jsonpointer v0.18.0 // indirect
github.com/go-openapi/jsonreference v0.18.0
github.com/go-openapi/spec v0.18.0
github.com/go-openapi/swag v0.18.0 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe // indirect
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pkg/errors v0.8.1
github.com/satori/go.uuid v1.2.0
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
github.com/stretchr/testify v1.3.0
github.com/swaggo/gin-swagger v1.1.0
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780 // indirect
github.com/urfave/cli v1.20.0
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 // indirect
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
)

106
vendor/github.com/swaggo/swag/go.sum generated vendored Normal file
View File

@ -0,0 +1,106 @@
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0 h1:KVRzjXpMzgdM4GEMDmDTnGcY5yBwGWreJwmmk4k35yU=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0 h1:oP2OUNdG1l2r5kYhrfVMXO54gWmzcfAwP/GFuHpNTkE=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/spec v0.18.0 h1:aIjeyG5mo5/FrvDkpKKEGZPmF9MPHahS72mzfVqeQXQ=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0 h1:1DU8Km1MRGv9Pj7BNLmkA+umwTStwDHttXvx3NhJA70=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe h1:W/GaMY0y69G4cFlmsC6B9sbuo2fP8OFP1ABjt4kPz+w=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/swaggo/gin-swagger v1.1.0 h1:ZI6/82S07DkkrMfGKbJhKj1R+QNTICkeAJP06pU36pU=
github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU=
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs=
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4=
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780 h1:vG/gY/PxA3v3l04qxe3tDjXyu3bozii8ulSlIPOYKhI=
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 h1:78jEq2G3J16aXneH23HSnTQQTCwMHoyO8VEiUH+bpPM=
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190110015856-aa033095749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89 h1:iWXXYN3edZ3Nd/7I6Rt1sXrWVmhF9bgVtlEJ7BbH124=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -5,7 +5,6 @@ import (
"go/ast"
goparser "go/parser"
"go/token"
"log"
"net/http"
"os"
"regexp"
@ -28,6 +27,23 @@ type Operation struct {
parser *Parser
}
var mimeTypeAliases = map[string]string{
"json": "application/json",
"xml": "text/xml",
"plain": "text/plain",
"html": "text/html",
"mpfd": "multipart/form-data",
"x-www-form-urlencoded": "application/x-www-form-urlencoded",
"json-api": "application/vnd.api+json",
"json-stream": "application/x-json-stream",
"octet-stream": "application/octet-stream",
"png": "image/png",
"jpeg": "image/jpeg",
"gif": "image/gif",
}
var mimeTypePattern = regexp.MustCompile("^[^/]+/[^/]+$")
// NewOperation creates a new Operation with default properties.
// map[int]Response
func NewOperation() *Operation {
@ -53,7 +69,7 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
if operation.Description == "" {
operation.Description = lineRemainder
} else {
operation.Description += "<br>" + lineRemainder
operation.Description += "\n" + lineRemainder
}
case "@summary":
operation.Summary = lineRemainder
@ -81,28 +97,32 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
}
}
}
case "@header":
if err := operation.ParseResponseHeaderComment(lineRemainder, astFile); err != nil {
return err
}
case "@router":
if err := operation.ParseRouterComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
if err := operation.ParseRouterComment(lineRemainder); err != nil {
return err
}
case "@security":
if err := operation.ParseSecurityComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
if err := operation.ParseSecurityComment(lineRemainder); err != nil {
return err
}
}
return nil
}
var paramPattern = regexp.MustCompile(`(\S+)[\s]+([\w]+)[\s]+([\S.]+)[\s]+([\w]+)[\s]+"([^"]+)"`)
// ParseParamComment parses params return []string of param properties
// @Param queryText form string true "The email for login"
// [param name] [paramType] [data type] [is mandatory?] [Comment]
// @Param some_id path int true "Some ID"
// E.g. @Param queryText form string true "The email for login"
// [param name] [paramType] [data type] [is mandatory?] [Comment]
// E.g. @Param some_id path int true "Some ID"
func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.File) error {
re := regexp.MustCompile(`([-\w]+)[\s]+([\w]+)[\s]+([\S.]+)[\s]+([\w]+)[\s]+"([^"]+)"`)
matches := re.FindStringSubmatch(commentLine)
matches := paramPattern.FindStringSubmatch(commentLine)
if len(matches) != 6 {
return fmt.Errorf("can not parse param comment \"%s\"", commentLine)
return fmt.Errorf("missing required param comment parameters \"%s\"", commentLine)
}
name := matches[1]
paramType := matches[2]
@ -121,52 +141,68 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
param = createParameter(paramType, description, name, TransToValidSchemeType(schemaType), required)
case "body":
param = createParameter(paramType, description, name, "object", required) // TODO: if Parameter types can be objects, but also primitives and arrays
// TODO: this snippets have to extract out
refSplit := strings.Split(schemaType, ".")
if len(refSplit) == 2 {
pkgName := refSplit[0]
typeName := refSplit[1]
if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok {
operation.parser.registerTypes[schemaType] = typeSpec
} else {
var typeSpec *ast.TypeSpec
if astFile != nil {
for _, imp := range astFile.Imports {
if imp.Name != nil && imp.Name.Name == pkgName { // the import had an alias that matched
break
}
impPath := strings.Replace(imp.Path.Value, `"`, ``, -1)
if strings.HasSuffix(impPath, "/"+pkgName) {
var err error
typeSpec, err = findTypeDef(impPath, typeName)
if err != nil {
return errors.Wrapf(err, "can not find ref type: %q", schemaType)
}
break
}
}
}
if typeSpec == nil {
return fmt.Errorf("can not find ref type:\"%s\"", schemaType)
}
operation.parser.TypeDefinitions[pkgName][typeName] = typeSpec
operation.parser.registerTypes[schemaType] = typeSpec
}
param.Schema.Ref = spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + schemaType),
}
if err := operation.registerSchemaType(schemaType, astFile); err != nil {
return err
}
param.Schema.Ref = spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + schemaType),
}
case "formData":
param = createParameter(paramType, description, name, TransToValidSchemeType(schemaType), required)
default:
return fmt.Errorf("%s is not supported paramType", paramType)
}
if err := operation.parseAndExtractionParamAttribute(commentLine, schemaType, &param); err != nil {
return err
}
param = operation.parseAndExtractionParamAttribute(commentLine, schemaType, param)
operation.Operation.Parameters = append(operation.Operation.Parameters, param)
return nil
}
func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.File) error {
refSplit := strings.Split(schemaType, ".")
if len(refSplit) != 2 {
return nil
}
pkgName := refSplit[0]
typeName := refSplit[1]
if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok {
operation.parser.registerTypes[schemaType] = typeSpec
return nil
}
var typeSpec *ast.TypeSpec
if astFile == nil {
return fmt.Errorf("can not register schema type: %q reason: astFile == nil", schemaType)
}
for _, imp := range astFile.Imports {
if imp.Name != nil && imp.Name.Name == pkgName { // the import had an alias that matched
break
}
impPath := strings.Replace(imp.Path.Value, `"`, ``, -1)
if strings.HasSuffix(impPath, "/"+pkgName) {
var err error
typeSpec, err = findTypeDef(impPath, typeName)
if err != nil {
return errors.Wrapf(err, "can not find type def: %q", schemaType)
}
break
}
}
if typeSpec == nil {
return fmt.Errorf("can not find schema type: %q", schemaType)
}
if _, ok := operation.parser.TypeDefinitions[pkgName]; !ok {
operation.parser.TypeDefinitions[pkgName] = make(map[string]*ast.TypeSpec)
}
operation.parser.TypeDefinitions[pkgName][typeName] = typeSpec
operation.parser.registerTypes[schemaType] = typeSpec
return nil
}
var regexAttributes = map[string]*regexp.Regexp{
// for Enums(A, B)
"enums": regexp.MustCompile(`(?i)enums\(.*\)`),
@ -184,99 +220,104 @@ var regexAttributes = map[string]*regexp.Regexp{
"format": regexp.MustCompile(`(?i)format\(.*\)`),
}
func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schemaType string, param spec.Parameter) spec.Parameter {
func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schemaType string, param *spec.Parameter) error {
schemaType = TransToValidSchemeType(schemaType)
for attrKey, re := range regexAttributes {
switch attrKey {
case "enums":
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if !(l == -1 && r == -1) {
enums := strings.Split(attr[l+1:r], ",")
for _, e := range enums {
e = strings.TrimSpace(e)
param.Enum = append(param.Enum, defineType(schemaType, e))
}
enums, err := findAttrList(re, commentLine)
if err != nil {
break
}
for _, e := range enums {
e = strings.TrimSpace(e)
param.Enum = append(param.Enum, defineType(schemaType, e))
}
case "maxinum":
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if !(l == -1 && r == -1) {
if schemaType != "integer" && schemaType != "number" {
log.Panicf("maxinum is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
attr = strings.TrimSpace(attr[l+1 : r])
n, err := strconv.ParseFloat(attr, 64)
if err != nil {
log.Panicf("maximum is allow only a number. comment=%s got=%s", commentLine, attr)
}
param.Maximum = &n
attr, err := findAttr(re, commentLine)
if err != nil {
break
}
if schemaType != "integer" && schemaType != "number" {
return fmt.Errorf("maxinum is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
n, err := strconv.ParseFloat(attr, 64)
if err != nil {
return fmt.Errorf("maximum is allow only a number. comment=%s got=%s", commentLine, attr)
}
param.Maximum = &n
case "mininum":
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if !(l == -1 && r == -1) {
if schemaType != "integer" && schemaType != "number" {
log.Panicf("mininum is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
attr = strings.TrimSpace(attr[l+1 : r])
n, err := strconv.ParseFloat(attr, 64)
if err != nil {
log.Panicf("mininum is allow only a number got=%s", attr)
}
param.Minimum = &n
attr, err := findAttr(re, commentLine)
if err != nil {
break
}
if schemaType != "integer" && schemaType != "number" {
return fmt.Errorf("mininum is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
n, err := strconv.ParseFloat(attr, 64)
if err != nil {
return fmt.Errorf("mininum is allow only a number got=%s", attr)
}
param.Minimum = &n
case "default":
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if !(l == -1 && r == -1) {
attr = strings.TrimSpace(attr[l+1 : r])
param.Default = defineType(schemaType, attr)
attr, err := findAttr(re, commentLine)
if err != nil {
break
}
param.Default = defineType(schemaType, attr)
case "maxlength":
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if !(l == -1 && r == -1) {
if schemaType != "string" {
log.Panicf("maxlength is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
attr = strings.TrimSpace(attr[l+1 : r])
n, err := strconv.ParseInt(attr, 10, 64)
if err != nil {
log.Panicf("maxlength is allow only a number got=%s", attr)
}
param.MaxLength = &n
attr, err := findAttr(re, commentLine)
if err != nil {
break
}
if schemaType != "string" {
return fmt.Errorf("maxlength is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
n, err := strconv.ParseInt(attr, 10, 64)
if err != nil {
return fmt.Errorf("maxlength is allow only a number got=%s", attr)
}
param.MaxLength = &n
case "minlength":
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if !(l == -1 && r == -1) {
if schemaType != "string" {
log.Panicf("maxlength is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
attr = strings.TrimSpace(attr[l+1 : r])
n, err := strconv.ParseInt(attr, 10, 64)
if err != nil {
log.Panicf("minlength is allow only a number got=%s", attr)
}
param.MinLength = &n
attr, err := findAttr(re, commentLine)
if err != nil {
break
}
if schemaType != "string" {
return fmt.Errorf("maxlength is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
}
n, err := strconv.ParseInt(attr, 10, 64)
if err != nil {
return fmt.Errorf("minlength is allow only a number got=%s", attr)
}
param.MinLength = &n
case "format":
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if !(l == -1 && r == -1) {
param.Format = strings.TrimSpace(attr[l+1 : r])
attr, err := findAttr(re, commentLine)
if err != nil {
break
}
param.Format = attr
}
}
return param
return nil
}
func findAttr(re *regexp.Regexp, commentLine string) (string, error) {
attr := re.FindString(commentLine)
l := strings.Index(attr, "(")
r := strings.Index(attr, ")")
if l == -1 || r == -1 {
return "", fmt.Errorf("can not find regex=%s, comment=%s", re.String(), commentLine)
}
return strings.TrimSpace(attr[l+1 : r]), nil
}
func findAttrList(re *regexp.Regexp, commentLine string) ([]string, error) {
attr, err := findAttr(re, commentLine)
if err != nil {
return []string{""}, err
}
return strings.Split(attr, ","), nil
}
// defineType enum value define the type (object and array unsupported)
@ -318,82 +359,40 @@ func (operation *Operation) ParseTagsComment(commentLine string) {
// ParseAcceptComment parses comment for given `accept` comment string.
func (operation *Operation) ParseAcceptComment(commentLine string) error {
accepts := strings.Split(commentLine, ",")
for _, a := range accepts {
switch a {
case "json", "application/json":
operation.Consumes = append(operation.Consumes, "application/json")
case "xml", "text/xml":
operation.Consumes = append(operation.Consumes, "text/xml")
case "plain", "text/plain":
operation.Consumes = append(operation.Consumes, "text/plain")
case "html", "text/html":
operation.Consumes = append(operation.Consumes, "text/html")
case "mpfd", "multipart/form-data":
operation.Consumes = append(operation.Consumes, "multipart/form-data")
case "x-www-form-urlencoded", "application/x-www-form-urlencoded":
operation.Consumes = append(operation.Consumes, "application/x-www-form-urlencoded")
case "json-api", "application/vnd.api+json":
operation.Consumes = append(operation.Consumes, "application/vnd.api+json")
case "json-stream", "application/x-json-stream":
operation.Consumes = append(operation.Consumes, "application/x-json-stream")
case "octet-stream", "application/octet-stream":
operation.Consumes = append(operation.Consumes, "application/octet-stream")
case "png", "image/png":
operation.Consumes = append(operation.Consumes, "image/png")
case "jpeg", "image/jpeg":
operation.Consumes = append(operation.Consumes, "image/jpeg")
case "gif", "image/gif":
operation.Consumes = append(operation.Consumes, "image/gif")
default:
return fmt.Errorf("%v accept type can't accepted", a)
return parseMimeTypeList(commentLine, &operation.Consumes, "%v accept type can't be accepted")
}
// ParseProduceComment parses comment for given `produce` comment string.
func (operation *Operation) ParseProduceComment(commentLine string) error {
return parseMimeTypeList(commentLine, &operation.Produces, "%v produce type can't be accepted")
}
// parseMimeTypeList parses a list of MIME Types for a comment like
// `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 {
if mimeTypePattern.MatchString(typeName) {
*typeList = append(*typeList, typeName)
continue
}
if aliasMimeType, ok := mimeTypeAliases[typeName]; ok {
*typeList = append(*typeList, aliasMimeType)
continue
}
return fmt.Errorf(format, typeName)
}
return nil
}
// ParseProduceComment parses comment for gived `produce` comment string.
func (operation *Operation) ParseProduceComment(commentLine string) error {
produces := strings.Split(commentLine, ",")
for _, a := range produces {
switch a {
case "json", "application/json":
operation.Produces = append(operation.Produces, "application/json")
case "xml", "text/xml":
operation.Produces = append(operation.Produces, "text/xml")
case "plain", "text/plain":
operation.Produces = append(operation.Produces, "text/plain")
case "html", "text/html":
operation.Produces = append(operation.Produces, "text/html")
case "mpfd", "multipart/form-data":
operation.Produces = append(operation.Produces, "multipart/form-data")
case "x-www-form-urlencoded", "application/x-www-form-urlencoded":
operation.Produces = append(operation.Produces, "application/x-www-form-urlencoded")
case "json-api", "application/vnd.api+json":
operation.Produces = append(operation.Produces, "application/vnd.api+json")
case "json-stream", "application/x-json-stream":
operation.Produces = append(operation.Produces, "application/x-json-stream")
case "octet-stream", "application/octet-stream":
operation.Produces = append(operation.Produces, "application/octet-stream")
case "png", "image/png":
operation.Produces = append(operation.Produces, "image/png")
case "jpeg", "image/jpeg":
operation.Produces = append(operation.Produces, "image/jpeg")
case "gif", "image/gif":
operation.Produces = append(operation.Produces, "image/gif")
default:
return fmt.Errorf("%v produce type can't accepted", a)
}
}
return nil
}
var routerPattern = regexp.MustCompile(`([\w\.\/\-{}\+]+)[^\[]+\[([^\]]+)`)
// ParseRouterComment parses comment for gived `router` comment string.
func (operation *Operation) ParseRouterComment(commentLine string) error {
re := regexp.MustCompile(`([\w\.\/\-{}\+]+)[^\[]+\[([^\]]+)`)
var matches []string
if matches = re.FindStringSubmatch(commentLine); len(matches) != 3 {
if matches = routerPattern.FindStringSubmatch(commentLine); len(matches) != 3 {
return fmt.Errorf("can not parse router comment \"%s\"", commentLine)
}
path := matches[1]
@ -433,6 +432,7 @@ func (operation *Operation) ParseSecurityComment(commentLine string) error {
// 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
func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) {
cwd, err := os.Getwd()
if err != nil {
@ -485,12 +485,13 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) {
return nil, errors.New("type spec not found")
}
var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/]+)[^"]*(.*)?`)
// ParseResponseComment parses comment for gived `response` comment string.
func (operation *Operation) ParseResponseComment(commentLine string, astFile *ast.File) error {
re := regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/]+)[^"]*(.*)?`)
var matches []string
if matches = re.FindStringSubmatch(commentLine); len(matches) != 5 {
if matches = responsePattern.FindStringSubmatch(commentLine); len(matches) != 5 {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
@ -508,46 +509,8 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as
refType := matches[3]
if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
refSplit := strings.Split(refType, ".")
if len(refSplit) == 2 {
pkgName := refSplit[0]
typeName := refSplit[1]
if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok {
operation.parser.registerTypes[refType] = typeSpec
} else {
var typeSpec *ast.TypeSpec
if astFile != nil {
for _, imp := range astFile.Imports {
if imp.Name != nil && imp.Name.Name == pkgName { // the import had an alias that matched
break
}
impPath := strings.Replace(imp.Path.Value, `"`, ``, -1)
if strings.HasSuffix(impPath, "/"+pkgName) {
var err error
typeSpec, err = findTypeDef(impPath, typeName)
if err != nil {
return errors.Wrapf(err, "can not find ref type: %q", refType)
}
break
}
}
}
if typeSpec == nil {
return fmt.Errorf("can not find ref type: %q", refType)
}
if _, ok := operation.parser.TypeDefinitions[pkgName]; !ok {
operation.parser.TypeDefinitions[pkgName] = make(map[string]*ast.TypeSpec)
}
operation.parser.TypeDefinitions[pkgName][typeName] = typeSpec
operation.parser.registerTypes[refType] = typeSpec
}
if err := operation.registerSchemaType(refType, astFile); err != nil {
return err
}
}
@ -595,12 +558,59 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as
return nil
}
// ParseEmptyResponseComment parse only comment out status code and description,eg: @Success 200 "it's ok"
func (operation *Operation) ParseEmptyResponseComment(commentLine string) error {
re := regexp.MustCompile(`([\d]+)[\s]+"(.*)"`)
// ParseResponseHeaderComment parses comment for gived `response header` comment string.
func (operation *Operation) ParseResponseHeaderComment(commentLine string, astFile *ast.File) error {
var matches []string
if matches = re.FindStringSubmatch(commentLine); len(matches) != 3 {
if matches = responsePattern.FindStringSubmatch(commentLine); len(matches) != 5 {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
response := spec.Response{}
code, _ := strconv.Atoi(matches[1])
responseDescription := strings.Trim(matches[4], "\"")
if responseDescription == "" {
responseDescription = http.StatusText(code)
}
response.Description = responseDescription
schemaType := strings.Trim(matches[2], "{}")
refType := matches[3]
if operation.Responses == nil {
operation.Responses = &spec.Responses{
ResponsesProps: spec.ResponsesProps{
StatusCodeResponses: make(map[int]spec.Response),
},
}
}
response, responseExist := operation.Responses.StatusCodeResponses[code]
if responseExist {
header := spec.Header{}
header.Description = responseDescription
header.Type = schemaType
if response.Headers == nil {
response.Headers = make(map[string]spec.Header)
}
response.Headers[refType] = header
operation.Responses.StatusCodeResponses[code] = response
}
return nil
}
var emptyResponsePattern = regexp.MustCompile(`([\d]+)[\s]+"(.*)"`)
// ParseEmptyResponseComment parse only comment out status code and description,eg: @Success 200 "it's ok"
func (operation *Operation) ParseEmptyResponseComment(commentLine string) error {
var matches []string
if matches = emptyResponsePattern.FindStringSubmatch(commentLine); len(matches) != 3 {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}

View File

@ -5,7 +5,6 @@ import (
"go/ast"
goparser "go/parser"
"go/token"
"log"
"net/http"
"os"
"path"
@ -51,6 +50,8 @@ type Parser struct {
PropNamingStrategy string
ParseVendor bool
// structStack stores full names of the structures that were already parsed or are being parsed now
structStack []string
}
@ -82,7 +83,7 @@ func New() *Parser {
// ParseAPI parses general api info for gived searchDir and mainAPIFile
func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error {
log.Println("Generate general API Info")
Println("Generate general API Info")
if err := parser.getAllGoFileInfo(searchDir); err != nil {
return err
}
@ -92,8 +93,10 @@ func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error {
parser.ParseType(astFile)
}
for _, astFile := range parser.files {
parser.ParseRouterAPIInfo(astFile)
for fileName, astFile := range parser.files {
if err := parser.ParseRouterAPIInfo(fileName, astFile); err != nil {
return err
}
}
parser.ParseDefinitions()
@ -122,15 +125,24 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
if fileTree.Comments != nil {
for _, comment := range fileTree.Comments {
comments := strings.Split(comment.Text(), "\n")
previousAttribute := ""
for _, commentLine := range comments {
attribute := strings.ToLower(strings.Split(commentLine, " ")[0])
multilineBlock := false
if previousAttribute == attribute {
multilineBlock = true
}
switch attribute {
case "@version":
parser.swagger.Info.Version = strings.TrimSpace(commentLine[len(attribute):])
case "@title":
parser.swagger.Info.Title = strings.TrimSpace(commentLine[len(attribute):])
case "@description":
parser.swagger.Info.Description = strings.TrimSpace(commentLine[len(attribute):])
if parser.swagger.Info.Description == "{{.Description}}" {
parser.swagger.Info.Description = strings.TrimSpace(commentLine[len(attribute):])
} else if multilineBlock {
parser.swagger.Info.Description += "\n" + strings.TrimSpace(commentLine[len(attribute):])
}
case "@termsofservice":
parser.swagger.Info.TermsOfService = strings.TrimSpace(commentLine[len(attribute):])
case "@contact.name":
@ -148,7 +160,7 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
case "@basepath":
parser.swagger.BasePath = strings.TrimSpace(commentLine[len(attribute):])
case "@schemes":
parser.swagger.Schemes = GetSchemes(commentLine)
parser.swagger.Schemes = getSchemes(commentLine)
case "@tag.name":
commentInfo := strings.TrimSpace(commentLine[len(attribute):])
parser.swagger.Tags = append(parser.swagger.Tags, spec.Tag{
@ -173,11 +185,12 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
commentInfo := strings.TrimSpace(commentLine[len(attribute):])
tag := parser.swagger.Tags[len(parser.swagger.Tags)-1]
if tag.TagProps.ExternalDocs == nil {
log.Panic("@tag.docs.description needs to come after a @tags.docs.url")
return errors.New("@tag.docs.description needs to come after a @tags.docs.url")
}
tag.TagProps.ExternalDocs.Description = commentInfo
replaceLastTag(parser.swagger.Tags, tag)
}
previousAttribute = attribute
}
for i := 0; i < len(comments); i++ {
@ -198,7 +211,7 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
}
}
if len(attrMap) != 2 {
log.Panic("@securitydefinitions.apikey is @name and @in required")
return errors.New("@securitydefinitions.apikey is @name and @in required")
}
securityMap[strings.TrimSpace(comments[i][len(attribute):])] = spec.APIKeyAuth(attrMap["@name"], attrMap["@in"])
case "@securitydefinitions.oauth2.application":
@ -208,8 +221,18 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
securityAttr := strings.ToLower(strings.Split(v, " ")[0])
if securityAttr == "@tokenurl" {
attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
} else if isExistsScope(securityAttr) {
scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
} else {
isExists, err := isExistsScope(securityAttr)
if err != nil {
return err
}
if isExists {
scopScheme, err := getScopeScheme(securityAttr)
if err != nil {
return err
}
scopes[scopScheme] = v[len(securityAttr):]
}
}
// next securityDefinitions
if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
@ -217,7 +240,7 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
}
}
if len(attrMap) != 1 {
log.Panic("@securitydefinitions.oauth2.application is @tokenUrl required")
return errors.New("@securitydefinitions.oauth2.application is @tokenUrl required")
}
securityScheme := spec.OAuth2Application(attrMap["@tokenurl"])
for scope, description := range scopes {
@ -231,8 +254,18 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
securityAttr := strings.ToLower(strings.Split(v, " ")[0])
if securityAttr == "@authorizationurl" {
attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
} else if isExistsScope(securityAttr) {
scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
} else {
isExists, err := isExistsScope(securityAttr)
if err != nil {
return err
}
if isExists {
scopScheme, err := getScopeScheme(securityAttr)
if err != nil {
return err
}
scopes[scopScheme] = v[len(securityAttr):]
}
}
// next securityDefinitions
if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
@ -240,7 +273,7 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
}
}
if len(attrMap) != 1 {
log.Panic("@securitydefinitions.oauth2.implicit is @authorizationUrl required")
return errors.New("@securitydefinitions.oauth2.implicit is @authorizationUrl required")
}
securityScheme := spec.OAuth2Implicit(attrMap["@authorizationurl"])
for scope, description := range scopes {
@ -254,8 +287,18 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
securityAttr := strings.ToLower(strings.Split(v, " ")[0])
if securityAttr == "@tokenurl" {
attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
} else if isExistsScope(securityAttr) {
scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
} else {
isExists, err := isExistsScope(securityAttr)
if err != nil {
return err
}
if isExists {
scopScheme, err := getScopeScheme(securityAttr)
if err != nil {
return err
}
scopes[scopScheme] = v[len(securityAttr):]
}
}
// next securityDefinitions
if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
@ -263,7 +306,7 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
}
}
if len(attrMap) != 1 {
log.Panic("@securitydefinitions.oauth2.password is @tokenUrl required")
return errors.New("@securitydefinitions.oauth2.password is @tokenUrl required")
}
securityScheme := spec.OAuth2Password(attrMap["@tokenurl"])
for scope, description := range scopes {
@ -277,8 +320,18 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
securityAttr := strings.ToLower(strings.Split(v, " ")[0])
if securityAttr == "@tokenurl" || securityAttr == "@authorizationurl" {
attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
} else if isExistsScope(securityAttr) {
scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
} else {
isExists, err := isExistsScope(securityAttr)
if err != nil {
return err
}
if isExists {
scopScheme, err := getScopeScheme(securityAttr)
if err != nil {
return err
}
scopes[scopScheme] = v[len(securityAttr):]
}
}
// next securityDefinitions
if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
@ -286,7 +339,7 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
}
}
if len(attrMap) != 2 {
log.Panic("@securitydefinitions.oauth2.accessCode is @tokenUrl and @authorizationUrl required")
return errors.New("@securitydefinitions.oauth2.accessCode is @tokenUrl and @authorizationUrl required")
}
securityScheme := spec.OAuth2AccessToken(attrMap["@authorizationurl"], attrMap["@tokenurl"])
for scope, description := range scopes {
@ -304,34 +357,34 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
return nil
}
func getScopeScheme(scope string) string {
func getScopeScheme(scope string) (string, error) {
scopeValue := scope[strings.Index(scope, "@scope."):]
if scopeValue == "" {
panic("@scope is empty")
return "", errors.New("@scope is empty")
}
return scope[len("@scope."):]
return scope[len("@scope."):], nil
}
func isExistsScope(scope string) bool {
func isExistsScope(scope string) (bool, error) {
s := strings.Fields(scope)
for _, v := range s {
if strings.Index(v, "@scope.") != -1 {
if strings.Index(v, ",") != -1 {
panic("@scope can't use comma(,) get=" + v)
return false, fmt.Errorf("@scope can't use comma(,) get=" + v)
}
}
}
return strings.Index(scope, "@scope.") != -1
return strings.Index(scope, "@scope.") != -1, nil
}
// GetSchemes parses swagger schemes for given commentLine
func GetSchemes(commentLine string) []string {
// 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(astFile *ast.File) {
func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) error {
for _, astDescription := range astFile.Decls {
switch astDeclaration := astDescription.(type) {
case *ast.FuncDecl:
@ -340,7 +393,7 @@ func (parser *Parser) ParseRouterAPIInfo(astFile *ast.File) {
operation.parser = parser
for _, comment := range astDeclaration.Doc.List {
if err := operation.ParseComment(comment.Text, astFile); err != nil {
log.Panicf("ParseComment panic:%+v", err)
return fmt.Errorf("ParseComment error in file %s :%+v", fileName, err)
}
}
var pathItem spec.PathItem
@ -370,6 +423,8 @@ func (parser *Parser) ParseRouterAPIInfo(astFile *ast.File) {
}
}
}
return nil
}
// ParseType parses type info for given astFile.
@ -407,7 +462,15 @@ func (parser *Parser) isInStructStack(refTypeName string) bool {
// ParseDefinitions parses Swagger Api definitions.
func (parser *Parser) ParseDefinitions() {
for refTypeName, typeSpec := range parser.registerTypes {
// sort the typeNames so that parsing definitions is deterministic
typeNames := make([]string, 0, len(parser.registerTypes))
for refTypeName := range parser.registerTypes {
typeNames = append(typeNames, refTypeName)
}
sort.Strings(typeNames)
for _, refTypeName := range typeNames {
typeSpec := parser.registerTypes[refTypeName]
ss := strings.Split(refTypeName, ".")
pkgName := ss[0]
parser.structStack = nil
@ -418,24 +481,30 @@ func (parser *Parser) ParseDefinitions() {
// ParseDefinition parses given type spec that corresponds to the type under
// given name and package, and populates swagger schema definitions registry
// with a schema for the given type
func (parser *Parser) ParseDefinition(pkgName, typeName string, typeSpec *ast.TypeSpec) {
func (parser *Parser) ParseDefinition(pkgName, typeName string, typeSpec *ast.TypeSpec) error {
refTypeName := fullTypeName(pkgName, typeName)
if _, isParsed := parser.swagger.Definitions[refTypeName]; isParsed {
log.Println("Skipping '" + refTypeName + "', already parsed.")
return
Println("Skipping '" + refTypeName + "', already parsed.")
return nil
}
if parser.isInStructStack(refTypeName) {
log.Println("Skipping '" + refTypeName + "', recursion detected.")
return
Println("Skipping '" + refTypeName + "', recursion detected.")
return nil
}
parser.structStack = append(parser.structStack, refTypeName)
log.Println("Generating " + refTypeName)
parser.swagger.Definitions[refTypeName] = parser.parseTypeExpr(pkgName, typeName, typeSpec.Type, true)
Println("Generating " + refTypeName)
schema, err := parser.parseTypeExpr(pkgName, typeName, typeSpec.Type)
if err != nil {
return err
}
parser.swagger.Definitions[refTypeName] = schema
return nil
}
func (parser *Parser) collectRequiredFields(pkgName string, properties map[string]spec.Schema) (requiredFields []string) {
func (parser *Parser) collectRequiredFields(pkgName string, properties map[string]spec.Schema, extraRequired []string) (requiredFields []string) {
// created sorted list of properties keys so when we iterate over them it's deterministic
ks := make([]string, 0, len(properties))
for k := range properties {
@ -461,6 +530,12 @@ func (parser *Parser) collectRequiredFields(pkgName string, properties map[strin
properties[k] = prop
}
if extraRequired != nil {
requiredFields = append(requiredFields, extraRequired...)
}
sort.Strings(requiredFields)
return
}
@ -473,22 +548,35 @@ func fullTypeName(pkgName, typeName string) string {
// parseTypeExpr parses given type expression that corresponds to the type under
// given name and package, and returns swagger schema for it.
func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr, flattenRequired bool) spec.Schema {
func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) (spec.Schema, error) {
//TODO: return pointer to spec.Schema
switch expr := typeExpr.(type) {
// type Foo struct {...}
case *ast.StructType:
refTypeName := fullTypeName(pkgName, typeName)
if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed {
return schema
return schema, nil
}
extraRequired := make([]string, 0)
properties := make(map[string]spec.Schema)
for _, field := range expr.Fields.List {
var fieldProps map[string]spec.Schema
var requiredFromAnon []string
if field.Names == nil {
fieldProps = parser.parseAnonymousField(pkgName, field)
var err error
fieldProps, requiredFromAnon, err = parser.parseAnonymousField(pkgName, field)
if err != nil {
return spec.Schema{}, err
}
extraRequired = append(extraRequired, requiredFromAnon...)
} else {
fieldProps = parser.parseStruct(pkgName, field)
var err error
fieldProps, err = parser.parseStruct(pkgName, field)
if err != nil {
return spec.Schema{}, err
}
}
for k, v := range fieldProps {
@ -496,17 +584,16 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr,
}
}
required := parser.collectRequiredFields(pkgName, properties)
// collect requireds from our properties and anonymous fields
required := parser.collectRequiredFields(pkgName, properties, extraRequired)
// unset required from properties because we've aggregated them
if flattenRequired {
for k, prop := range properties {
tname := prop.SchemaProps.Type[0]
if tname != "object" {
prop.SchemaProps.Required = make([]string, 0)
}
properties[k] = prop
// unset required from properties because we've collected them
for k, prop := range properties {
tname := prop.SchemaProps.Type[0]
if tname != "object" {
prop.SchemaProps.Required = make([]string, 0)
}
properties[k] = prop
}
return spec.Schema{
@ -514,25 +601,28 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr,
Type: []string{"object"},
Properties: properties,
Required: required,
},
}
}}, nil
// type Foo Baz
case *ast.Ident:
refTypeName := fullTypeName(pkgName, expr.Name)
if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed {
typedef := parser.TypeDefinitions[pkgName][expr.Name]
parser.ParseDefinition(pkgName, expr.Name, typedef)
if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok {
parser.ParseDefinition(pkgName, expr.Name, typedef)
}
}
return parser.swagger.Definitions[refTypeName]
return parser.swagger.Definitions[refTypeName], nil
// type Foo *Baz
case *ast.StarExpr:
return parser.parseTypeExpr(pkgName, typeName, expr.X, true)
return parser.parseTypeExpr(pkgName, typeName, expr.X)
// type Foo []Baz
case *ast.ArrayType:
itemSchema := parser.parseTypeExpr(pkgName, "", expr.Elt, true)
itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Elt)
if err != nil {
return spec.Schema{}, err
}
return spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
@ -540,7 +630,7 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr,
Schema: &itemSchema,
},
},
}
}, nil
// type Foo pkg.Bar
case *ast.SelectorExpr:
@ -552,20 +642,33 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr,
typedef := parser.TypeDefinitions[pkgName][typeName]
parser.ParseDefinition(pkgName, typeName, typedef)
}
return parser.swagger.Definitions[refTypeName]
return parser.swagger.Definitions[refTypeName], nil
}
// type Foo map[string]Bar
case *ast.MapType:
itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Value)
if err != nil {
return spec.Schema{}, err
}
return spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Schema: &itemSchema,
},
},
}, nil
// ...
default:
log.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
}
return spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
},
}
}, nil
}
type structField struct {
@ -582,18 +685,25 @@ type structField struct {
minLength *int64
enums []interface{}
defaultValue interface{}
extensions map[string]interface{}
}
func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties map[string]spec.Schema) {
properties = map[string]spec.Schema{}
structField := parser.parseField(field)
func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (map[string]spec.Schema, error) {
properties := map[string]spec.Schema{}
structField, err := parser.parseField(field)
if err != nil {
return properties, nil
}
if structField.name == "" {
return
return properties, nil
}
var desc string
if field.Doc != nil {
desc = strings.TrimSpace(field.Doc.Text())
}
if desc == "" && field.Comment != nil {
desc = strings.TrimSpace(field.Comment.Text())
}
// TODO: find package of schemaType and/or arrayType
if structField.crossPkg != "" {
@ -684,13 +794,20 @@ func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: structField.exampleValue,
},
VendorExtensible: spec.VendorExtensible{
Extensions: structField.extensions,
},
}
nestStruct, ok := field.Type.(*ast.StructType)
if ok {
props := map[string]spec.Schema{}
nestRequired := make([]string, 0)
for _, v := range nestStruct.Fields.List {
p := parser.parseStruct(pkgName, v)
p, err := parser.parseStruct(pkgName, v)
if err != nil {
return properties, err
}
for k, v := range p {
if v.SchemaProps.Type[0] != "object" {
nestRequired = append(nestRequired, v.SchemaProps.Required...)
@ -720,10 +837,10 @@ func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties
}
}
}
return
return properties, nil
}
func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[string]spec.Schema {
func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) (map[string]spec.Schema, []string, error) {
properties := make(map[string]spec.Schema)
fullTypeName := ""
@ -733,10 +850,17 @@ func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[
case *ast.StarExpr:
if ftypeX, ok := ftype.X.(*ast.Ident); ok {
fullTypeName = ftypeX.Name
} else if ftypeX, ok := ftype.X.(*ast.SelectorExpr); ok {
if packageX, ok := ftypeX.X.(*ast.Ident); ok {
fullTypeName = fmt.Sprintf("%s.%s", packageX.Name, ftypeX.Sel.Name)
}
} else {
Printf("Composite field type of '%T' is unhandle by parser. Skipping", ftype)
return properties, []string{}, nil
}
default:
log.Printf("Field type of '%T' is unsupported. Skipping", ftype)
return properties
Printf("Field type of '%T' is unsupported. Skipping", ftype)
return properties, []string{}, nil
}
typeName := fullTypeName
@ -746,8 +870,10 @@ func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[
}
typeSpec := parser.TypeDefinitions[pkgName][typeName]
schema := parser.parseTypeExpr(pkgName, typeName, typeSpec.Type, false)
schema, err := parser.parseTypeExpr(pkgName, typeName, typeSpec.Type)
if err != nil {
return properties, []string{}, err
}
schemaType := "unknown"
if len(schema.SchemaProps.Type) > 0 {
schemaType = schema.SchemaProps.Type[0]
@ -761,14 +887,14 @@ func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[
case "array":
properties[typeName] = schema
default:
log.Printf("Can't extract properties from a schema of type '%s'", schemaType)
Printf("Can't extract properties from a schema of type '%s'", schemaType)
}
return properties
return properties, schema.SchemaProps.Required, nil
}
func (parser *Parser) parseField(field *ast.Field) *structField {
prop := getPropertyName(field, parser)
func (parser *Parser) parseField(field *ast.Field) (*structField, error) {
prop := getPropertyName(field.Type, parser)
if len(prop.ArrayType) == 0 {
CheckSchemaType(prop.SchemaType)
} else {
@ -793,7 +919,7 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
}
if field.Tag == nil {
return structField
return structField, nil
}
// `json:"tag"` -> json:"tag"
structTag := reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1))
@ -818,8 +944,13 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
if 0 < len(parts) && len(parts) <= 2 {
newSchemaType := parts[0]
newArrayType := structField.arrayType
if len(parts) >= 2 && newSchemaType == "array" {
newArrayType = parts[1]
if len(parts) >= 2 {
if newSchemaType == "array" {
newArrayType = parts[1]
} else if newSchemaType == "primitive" {
newSchemaType = parts[1]
newArrayType = parts[1]
}
}
CheckSchemaType(newSchemaType)
@ -829,7 +960,11 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
}
}
if exampleTag := structTag.Get("example"); exampleTag != "" {
structField.exampleValue = defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag)
example, err := defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag)
if err != nil {
return nil, err
}
structField.exampleValue = example
}
if formatTag := structTag.Get("format"); formatTag != "" {
structField.formatType = formatTag
@ -850,6 +985,17 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
}
}
}
if extensionsTag := structTag.Get("extensions"); extensionsTag != "" {
structField.extensions = map[string]interface{}{}
for _, val := range strings.Split(extensionsTag, ",") {
parts := strings.SplitN(val, "=", 2)
if len(parts) == 2 {
structField.extensions[parts[0]] = parts[1]
} else {
structField.extensions[parts[0]] = true
}
}
}
if enumsTag := structTag.Get("enums"); enumsTag != "" {
enumType := structField.schemaType
if structField.schemaType == "array" {
@ -865,15 +1011,33 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
}
if IsNumericType(structField.schemaType) || IsNumericType(structField.arrayType) {
structField.maximum = getFloatTag(structTag, "maximum")
structField.minimum = getFloatTag(structTag, "minimum")
maximum, err := getFloatTag(structTag, "maximum")
if err != nil {
return nil, err
}
structField.maximum = maximum
minimum, err := getFloatTag(structTag, "minimum")
if err != nil {
return nil, err
}
structField.minimum = minimum
}
if structField.schemaType == "string" || structField.arrayType == "string" {
structField.maxLength = getIntTag(structTag, "maxLength")
structField.minLength = getIntTag(structTag, "minLength")
maxLength, err := getIntTag(structTag, "maxLength")
if err != nil {
return nil, err
}
structField.maxLength = maxLength
minLength, err := getIntTag(structTag, "minLength")
if err != nil {
return nil, err
}
structField.minLength = minLength
}
return structField
return structField, nil
}
func replaceLastTag(slice []spec.Tag, element spec.Tag) {
@ -881,32 +1045,32 @@ func replaceLastTag(slice []spec.Tag, element spec.Tag) {
slice = append(slice, element)
}
func getFloatTag(structTag reflect.StructTag, tagName string) *float64 {
func getFloatTag(structTag reflect.StructTag, tagName string) (*float64, error) {
strValue := structTag.Get(tagName)
if strValue == "" {
return nil
return nil, nil
}
value, err := strconv.ParseFloat(strValue, 64)
if err != nil {
panic(fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err))
return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err)
}
return &value
return &value, nil
}
func getIntTag(structTag reflect.StructTag, tagName string) *int64 {
func getIntTag(structTag reflect.StructTag, tagName string) (*int64, error) {
strValue := structTag.Get(tagName)
if strValue == "" {
return nil
return nil, nil
}
value, err := strconv.ParseInt(strValue, 10, 64)
if err != nil {
panic(fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err))
return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err)
}
return &value
return &value, nil
}
func toSnakeCase(in string) string {
@ -942,37 +1106,41 @@ func toLowerCamelCase(in string) string {
}
// defineTypeOfExample example value define the type (object and array unsupported)
func defineTypeOfExample(schemaType, arrayType, exampleValue string) interface{} {
func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{}, error) {
switch schemaType {
case "string":
return exampleValue
return exampleValue, nil
case "number":
v, err := strconv.ParseFloat(exampleValue, 64)
if err != nil {
panic(fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err))
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v
return v, nil
case "integer":
v, err := strconv.Atoi(exampleValue)
if err != nil {
panic(fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err))
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v
return v, nil
case "boolean":
v, err := strconv.ParseBool(exampleValue)
if err != nil {
panic(fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err))
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v
return v, nil
case "array":
values := strings.Split(exampleValue, ",")
result := make([]interface{}, 0)
for _, value := range values {
result = append(result, defineTypeOfExample(arrayType, "", value))
v, err := defineTypeOfExample(arrayType, "", value)
if err != nil {
return nil, err
}
result = append(result, v)
}
return result
return result, nil
default:
panic(fmt.Errorf("%s is unsupported type in example value", schemaType))
return nil, fmt.Errorf("%s is unsupported type in example value", schemaType)
}
}
@ -982,7 +1150,7 @@ func (parser *Parser) getAllGoFileInfo(searchDir string) error {
}
func (parser *Parser) visit(path string, f os.FileInfo, err error) error {
if err := Skip(f); err != nil {
if err := parser.Skip(path, f); err != nil {
return err
}
@ -990,7 +1158,7 @@ func (parser *Parser) visit(path string, f os.FileInfo, err error) error {
fset := token.NewFileSet() // positions are relative to fset
astFile, err := goparser.ParseFile(fset, path, nil, goparser.ParseComments)
if err != nil {
log.Panicf("ParseFile panic:%+v", err)
return fmt.Errorf("ParseFile error:%+v", err)
}
parser.files[path] = astFile
@ -999,10 +1167,12 @@ func (parser *Parser) visit(path string, f os.FileInfo, err error) error {
}
// Skip returns filepath.SkipDir error if match vendor and hidden folder
func Skip(f os.FileInfo) error {
// exclude vendor folder
if f.IsDir() && f.Name() == "vendor" {
return filepath.SkipDir
func (parser *Parser) Skip(path string, f os.FileInfo) error {
if !parser.ParseVendor { // ignore vendor
if f.IsDir() && f.Name() == "vendor" {
return filepath.SkipDir
}
}
// exclude all hidden folder

View File

@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"go/ast"
"log"
"strings"
)
@ -61,83 +60,69 @@ func parseFieldSelectorExpr(astTypeSelectorExpr *ast.SelectorExpr, parser *Parse
}
}
log.Printf("%s is not supported. but it will be set with string temporary. Please report any problems.\n", astTypeSelectorExpr.Sel.Name)
Printf("%s is not supported. but it will be set with string temporary. Please report any problems.\n", astTypeSelectorExpr.Sel.Name)
return propertyName{SchemaType: "string", ArrayType: "string"}
}
// getPropertyName returns the string value for the given field if it exists, otherwise it panics.
// allowedValues: array, boolean, integer, null, number, object, string
func getPropertyName(field *ast.Field, parser *Parser) propertyName {
if astTypeSelectorExpr, ok := field.Type.(*ast.SelectorExpr); ok {
func getPropertyName(expr ast.Expr, parser *Parser) propertyName {
if astTypeSelectorExpr, ok := expr.(*ast.SelectorExpr); ok {
return parseFieldSelectorExpr(astTypeSelectorExpr, parser, newProperty)
}
// check if it is a custom type
typeName := fmt.Sprintf("%v", field.Type)
typeName := fmt.Sprintf("%v", expr)
if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[typeName]; isCustomType {
return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}
}
if astTypeIdent, ok := field.Type.(*ast.Ident); ok {
if astTypeIdent, ok := expr.(*ast.Ident); ok {
name := astTypeIdent.Name
schemeType := TransToValidSchemeType(name)
return propertyName{SchemaType: schemeType, ArrayType: schemeType}
}
if ptr, ok := field.Type.(*ast.StarExpr); ok {
if astTypeSelectorExpr, ok := ptr.X.(*ast.SelectorExpr); ok {
return parseFieldSelectorExpr(astTypeSelectorExpr, parser, newProperty)
}
// TODO support custom pointer type?
if _, ok := ptr.X.(*ast.MapType); ok { // if map
//TODO support map
return propertyName{SchemaType: "object", ArrayType: "object"}
}
if _, ok := ptr.X.(*ast.StructType); ok { // if struct
return propertyName{SchemaType: "object", ArrayType: "object"}
}
if astTypeIdent, ok := ptr.X.(*ast.Ident); ok {
name := astTypeIdent.Name
schemeType := TransToValidSchemeType(name)
return propertyName{SchemaType: schemeType, ArrayType: schemeType}
}
if astTypeArray, ok := ptr.X.(*ast.ArrayType); ok { // if array
if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.SelectorExpr); ok {
return parseFieldSelectorExpr(astTypeArrayExpr, parser, newArrayProperty)
}
if astTypeArrayIdent, ok := astTypeArray.Elt.(*ast.Ident); ok {
name := TransToValidSchemeType(astTypeArrayIdent.Name)
return propertyName{SchemaType: "array", ArrayType: name}
}
}
if ptr, ok := expr.(*ast.StarExpr); ok {
return getPropertyName(ptr.X, parser)
}
if astTypeArray, ok := field.Type.(*ast.ArrayType); ok { // if array
if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.SelectorExpr); ok {
return parseFieldSelectorExpr(astTypeArrayExpr, parser, newArrayProperty)
}
if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.StarExpr); ok {
if astTypeArraySel, ok := astTypeArrayExpr.X.(*ast.SelectorExpr); ok {
return parseFieldSelectorExpr(astTypeArraySel, parser, newArrayProperty)
}
if astTypeArrayIdent, ok := astTypeArrayExpr.X.(*ast.Ident); ok {
name := TransToValidSchemeType(astTypeArrayIdent.Name)
return propertyName{SchemaType: "array", ArrayType: name}
}
}
itemTypeName := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArray.Elt))
if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[itemTypeName]; isCustomType {
itemTypeName = actualPrimitiveType
}
return propertyName{SchemaType: "array", ArrayType: itemTypeName}
if astTypeArray, ok := expr.(*ast.ArrayType); ok { // if array
return getArrayPropertyName(astTypeArray, parser)
}
if _, ok := field.Type.(*ast.MapType); ok { // if map
if _, ok := expr.(*ast.MapType); ok { // if map
//TODO: support map
return propertyName{SchemaType: "object", ArrayType: "object"}
}
if _, ok := field.Type.(*ast.StructType); ok { // if struct
if _, ok := expr.(*ast.StructType); ok { // if struct
return propertyName{SchemaType: "object", ArrayType: "object"}
}
if _, ok := field.Type.(*ast.InterfaceType); ok { // if interface{}
if _, ok := expr.(*ast.InterfaceType); ok { // if interface{}
return propertyName{SchemaType: "object", ArrayType: "object"}
}
panic("not supported" + fmt.Sprint(field.Type))
panic("not supported" + fmt.Sprint(expr))
}
func getArrayPropertyName(astTypeArray *ast.ArrayType, parser *Parser) propertyName {
if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.SelectorExpr); ok {
return parseFieldSelectorExpr(astTypeArrayExpr, parser, newArrayProperty)
}
if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.StarExpr); ok {
if astTypeArraySel, ok := astTypeArrayExpr.X.(*ast.SelectorExpr); ok {
return parseFieldSelectorExpr(astTypeArraySel, parser, newArrayProperty)
}
if astTypeArrayIdent, ok := astTypeArrayExpr.X.(*ast.Ident); ok {
name := TransToValidSchemeType(astTypeArrayIdent.Name)
return propertyName{SchemaType: "array", ArrayType: name}
}
}
itemTypeName := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArray.Elt))
if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[itemTypeName]; isCustomType {
itemTypeName = actualPrimitiveType
}
return propertyName{SchemaType: "array", ArrayType: itemTypeName}
}

View File

@ -2,7 +2,7 @@ package swag
import "fmt"
// CheckSchemaType TODO: NEEDS COMMENT INFO
// CheckSchemaType begins panicking if typeName is not a name of primitive type
func CheckSchemaType(typeName string) {
if !IsPrimitiveType(typeName) {
panic(fmt.Errorf("%s is not basic types", typeName))

View File

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