diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4abd3fc28..ef9d51a7e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,7 +3,7 @@ jobs:
test:
strategy:
matrix:
- go: [ '1.11.x', '1.12.x' ]
+ go: [ '1.13.x', '1.14.x' ]
platform: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
@@ -37,4 +37,4 @@ jobs:
working-directory: ./src/github.com/${{ github.repository }}
run: make test
env:
- GOPATH: ${{ runner.workspace }}
\ No newline at end of file
+ GOPATH: ${{ runner.workspace }}
diff --git a/.gitignore b/.gitignore
index bea8db681..a77cb497c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,5 +16,5 @@ cover.out
# Etc
.DS_Store
-swag
-swag.exe
+/swag
+/swag.exe
diff --git a/.goreleaser.yml b/.goreleaser.yml
index d0b576ab1..fcae4cd4a 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,5 +1,15 @@
build:
main: cmd/swag/main.go
+ goos:
+ - linux
+ - darwin
+ goarch:
+ - amd64
+ - arm64
+ - 386
+ ignore:
+ - goos: darwin
+ goarch: arm64
archive:
replacements:
darwin: Darwin
@@ -7,6 +17,7 @@ archive:
windows: Windows
386: i386
amd64: x86_64
+ arm64: aarch64
checksum:
name_template: 'checksums.txt'
snapshot:
diff --git a/.travis.yml b/.travis.yml
index 825e2482f..daadd9cdd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,9 @@
language: go
go:
- - 1.11.x
- - 1.12.x
- 1.13.x
+ - 1.14.x
+ - 1.15.x
install:
- make deps
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..0a5d9b8db
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,29 @@
+# Dockerfile References: https://docs.docker.com/engine/reference/builder/
+
+# Start from the latest golang base image
+FROM golang:1.14-alpine as builder
+
+# Set the Current Working Directory inside the container
+WORKDIR /app
+
+# Copy go mod and sum files
+COPY go.mod go.sum ./
+
+# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
+RUN go mod download
+
+# Copy the source from the current directory to the Working Directory inside the container
+COPY . .
+
+# Build the Go app
+RUN CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o swag cmd/swag/main.go
+
+
+######## Start a new stage from scratch #######
+FROM scratch
+
+WORKDIR /root/
+
+# Copy the Pre-built binary file from the previous stage
+COPY --from=builder /app/swag .
+
diff --git a/Makefile b/Makefile
index f78299612..2731f56d8 100644
--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,6 @@ clean:
deps:
$(GOGET) github.com/swaggo/cli
$(GOGET) github.com/ghodss/yaml
- $(GOGET) github.com/gin-gonic/gin
$(GOGET) github.com/KyleBanks/depth
$(GOGET) github.com/go-openapi/jsonreference
$(GOGET) github.com/go-openapi/spec
diff --git a/README.md b/README.md
index 9d987c265..1ddc6a78c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# swag
+🌍 *[English](README.md) ∙ [简体中文](README_zh-CN.md)*
+
[![Travis Status](https://img.shields.io/travis/swaggo/swag/master.svg)](https://travis-ci.org/swaggo/swag)
@@ -26,12 +28,15 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie
- [Examples](#examples)
- [Descriptions over multiple lines](#descriptions-over-multiple-lines)
- [User defined structure with an array type](#user-defined-structure-with-an-array-type)
+ - [Model composition in response](#model-composition-in-response)
- [Add a headers in response](#add-a-headers-in-response)
- [Use multiple path params](#use-multiple-path-params)
- [Example value of struct](#example-value-of-struct)
- [Description of struct](#description-of-struct)
- [Use swaggertype tag to supported custom type](#use-swaggertype-tag-to-supported-custom-type)
+ - [Use swaggerignore tag to exclude a field](#use-swaggerignore-tag-to-exclude-a-field)
- [Add extension info to struct field](#add-extension-info-to-struct-field)
+ - [Rename model to display](#rename-model-to-display)
- [How to using security annotations](#how-to-using-security-annotations)
- [About the Project](#about-the-project)
@@ -68,12 +73,19 @@ 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 all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs")
- --parseVendor Parse go files in 'vendor' folder, disabled by default
- --parseDependency Parse go files in outside dependency folder, disabled by default
+ --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: "./")
+ --exclude value Exclude directories and files when searching, comma separated
+ --propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase")
+ --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs")
+ --parseVendor Parse go files in 'vendor' folder, disabled by default (default: false)
+ --parseDependency Parse go files in outside dependency folder, disabled by default (default: false)
+ --markdownFiles value, --md value Parse folder containing markdown files to use as description, disabled by default
+ --codeExampleFiles value, --cef value Parse folder containing code example files to use for the x-codeSamples extension, disabled by default
+ --parseInternal Parse go files in internal packages, disabled by default (default: false)
+ --generatedTime Generate timestamp at the top of docs.go, disabled by default (default: false)
+ --parseDepth value Dependency parse depth (default: 100)
+ --help, -h show help (default: false)
```
## Supported Web Frameworks
@@ -82,6 +94,9 @@ OPTIONS:
- [echo](http://github.com/swaggo/echo-swagger)
- [buffalo](https://github.com/swaggo/buffalo-swagger)
- [net/http](https://github.com/swaggo/http-swagger)
+- [flamingo](https://github.com/i-love-flamingo/swagger)
+- [fiber](https://github.com/arsmn/fiber-swagger)
+- [atreugo](https://github.com/Nerzal/atreugo-swagger)
## How to use it with Gin
@@ -110,6 +125,7 @@ import "github.com/swaggo/files" // swagger embed files
// @host localhost:8080
// @BasePath /api/v1
+// @query.collection.format multi
// @securityDefinitions.basic BasicAuth
@@ -164,7 +180,7 @@ func main() {
//...
```
-Additionally some general API info can be set dynamically. The generated code package `docs` exports `SwaggerInfo` variable which we can use to set the title, description, version, host and base path programatically. Example using Gin:
+Additionally some general API info can be set dynamically. The generated code package `docs` exports `SwaggerInfo` variable which we can use to set the title, description, version, host and base path programmatically. Example using Gin:
```go
package main
@@ -188,7 +204,7 @@ import (
func main() {
- // programatically set swagger info
+ // programmatically set swagger info
docs.SwaggerInfo.Title = "Swagger Example API"
docs.SwaggerInfo.Description = "This is a sample server Petstore server."
docs.SwaggerInfo.Version = "1.0"
@@ -229,9 +245,9 @@ import (
// @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 400,404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
+// @Failure default {object} httputil.DefaultError
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
id := ctx.Param("id")
@@ -256,9 +272,9 @@ func (c *Controller) ShowAccount(ctx *gin.Context) {
// @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 400,404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
+// @Failure default {object} httputil.DefaultError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
q := ctx.Request.URL.Query().Get("q")
@@ -325,6 +341,7 @@ $ swag init
| 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 |
+| query.collection.format | The default collection(array) param format in query,enums:csv,multi,pipes,tsv,ssv. If not set, csv is the default.| // @query.collection.format multi
| schemes | The transfer protocol for the operation that separated by spaces. | // @schemes http https |
| x-name | The extension key, must be start by x- and take only json value | // @x-example-key {"key": "value"} |
@@ -341,7 +358,6 @@ When a short string in your documentation is insufficient, or you need images, c
| tag.description.markdown | Description of the tag this is an alternative to tag.description. The description will be read from a file named like tagname.md | // @tag.description.markdown |
-
## API Operation
**Example**
@@ -351,6 +367,7 @@ When a short string in your documentation is insufficient, or you need images, c
| annotation | description |
|-------------|----------------------------------------------------------------------------------------------------------------------------|
| description | A verbose explanation of the operation behavior. |
+| description.markdown | A short description of the application. The description will be read from a file named like endpointname.md| // @description.file endpoint.description.markdown |
| id | A unique string used to identify the operation. Must be unique among all API operations. |
| tags | A list of tags to each API operation that separated by commas. |
| summary | A short summary of what the operation does. |
@@ -358,11 +375,16 @@ When a short string in your documentation is insufficient, or you need images, c
| produce | A list of MIME types the APIs can produce. Value MUST be as described under [Mime Types](#mime-types). |
| param | Parameters that separated by spaces. `param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
| 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` |
+| success | Success response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
+| failure | Failure response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
+| response | As same as `success` and `failure` |
| header | Header in response that separated by spaces. `return code`,`{param type}`,`data type`,`comment` |
| router | Path definition that separated by spaces. `path`,`[httpMethod]` |
| x-name | The extension key, must be start by x- and take only json value. |
+| x-codeSample | Optional Markdown usage. take `file` as parameter. This will then search for a file named like the summary in the given folder. |
+| deprecated | Mark endpoint as deprecated. |
+
+
## Mime Types
@@ -429,8 +451,9 @@ Besides that, `swag` also accepts aliases for some MIME Types as follows:
// @Param enumint query int false "int enums" Enums(1, 2, 3)
// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)
// @Param string query string false "string valid" minlength(5) maxlength(10)
-// @Param int query int false "int valid" mininum(1) maxinum(10)
+// @Param int query int false "int valid" minimum(1) maximum(10)
// @Param default query string false "string default" default(A)
+// @Param collection query []string false "string collection" collectionFormat(multi)
```
It also works for the struct fields:
@@ -447,6 +470,7 @@ type Foo struct {
Field Name | Type | Description
---|:---:|---
+validate | `string` | Determines the validation for the parameter. Possible values are: `required`.
default | * | Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request. (Note: "default" has no meaning for required parameters.) See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Unlike JSON Schema this value MUST conform to the defined [`type`](#parameterType) for this parameter.
maximum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2.
minimum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3.
@@ -454,6 +478,7 @@ Field Name | Type | Description
minLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2.
enums | [\*] | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1.
format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](https://swagger.io/specification/v2/#dataTypeFormat) for further details.
+collectionFormat | `string` |Determines the format of the array if type array is used. Possible values are:
- `csv` - comma separated values `foo,bar`.
- `ssv` - space separated values `foo bar`.
- `tsv` - tab separated values `foo\tbar`.
- `pipes` - pipe separated values
foo|bar
. - `multi` - corresponds to multiple parameter instances instead of multiple values for a single instance `foo=bar&foo=baz`. This is valid only for parameters [`in`](#parameterIn) "query" or "formData".
Default value is `csv`.
### Future
@@ -464,7 +489,6 @@ Field Name | Type | Description
maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2.
minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3.
uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4.
-collectionFormat | `string` | Determines the format of the array if type array is used. Possible values are: - `csv` - comma separated values `foo,bar`.
- `ssv` - space separated values `foo bar`.
- `tsv` - tab separated values `foo\tbar`.
- `pipes` - pipe separated values
foo|bar
. - `multi` - corresponds to multiple parameter instances instead of multiple values for a single instance `foo=bar&foo=baz`. This is valid only for parameters [`in`](#parameterIn) "query" or "formData".
Default value is `csv`.
## Examples
@@ -492,12 +516,53 @@ type Account struct {
Name string `json:"name" example:"account name"`
}
```
+
+### Model composition in response
+```go
+// JSONResult's data field will be overridden by the specific type proto.Order
+@success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc"
+```
+
+```go
+type JSONResult struct {
+ Code int `json:"code" `
+ Message string `json:"message"`
+ Data interface{} `json:"data"`
+}
+
+type Order struct { //in `proto` package
+ Id uint `json:"id"`
+ Data interface{} `json:"data"`
+}
+```
+
+- also support array of objects and primitive types as nested response
+```go
+@success 200 {object} jsonresult.JSONResult{data=[]proto.Order} "desc"
+@success 200 {object} jsonresult.JSONResult{data=string} "desc"
+@success 200 {object} jsonresult.JSONResult{data=[]string} "desc"
+```
+
+- overriding multiple fields. field will be added if not exists
+```go
+@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc"
+```
+- overriding deep-level fields
+```go
+type DeepObject struct { //in `proto` package
+ ...
+}
+@success 200 {object} jsonresult.JSONResult{data1=proto.Order{data=proto.DeepObject},data2=[]proto.Order{data=[]proto.DeepObject}} "desc"
+```
### Add a headers in response
```go
// @Success 200 {string} string "ok"
+// @failure 400 {string} string "error"
+// @response default {string} string "other error"
// @Header 200 {string} Location "/entity/1"
-// @Header 200 {string} Token "qwerty"
+// @Header 200,400,default {string} Token "token"
+// @Header all {string} Token2 "token2"
```
### Use multiple path params
@@ -594,6 +659,17 @@ generated swagger doc as follows:
```
+
+### Use swaggerignore tag to exclude a field
+
+```go
+type Account struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Ignored int `swaggerignore:"true"`
+}
+```
+
### Add extension info to struct field
```go
@@ -616,6 +692,13 @@ generate swagger doc as follows:
}
}
```
+### Rename model to display
+
+```golang
+type Resp struct {
+ Code int
+}//@name Response
+```
### How to using security annotations
diff --git a/README_zh-CN.md b/README_zh-CN.md
new file mode 100644
index 000000000..fc8edac0e
--- /dev/null
+++ b/README_zh-CN.md
@@ -0,0 +1,744 @@
+# swag
+
+🌍 *[English](README.md) ∙ [简体中文](README_zh-CN.md)*
+
+
+
+[![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.aaakk.us.kg-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)
+[![Release](https://img.shields.io/github/release/swaggo/swag.svg?style=flat-square)](https://github.com/swaggo/swag/releases)
+
+Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framework](#支持的Web框架) 创建了各种插件,这样可以与现有Go项目快速集成(使用Swagger UI)。
+
+## 目录
+
+- [快速开始](#快速开始)
+- [支持的Web框架](#支持的web框架)
+- [如何与Gin集成](#如何与gin集成)
+- [开发现状](#开发现状)
+- [声明式注释格式](#声明式注释格式)
+ - [通用API信息](#通用api信息)
+ - [API操作](#api操作)
+ - [安全性](#安全性)
+- [样例](#样例)
+ - [多行的描述](#多行的描述)
+ - [用户自定义的具有数组类型的结构](#用户自定义的具有数组类型的结构)
+ - [响应对象中的模型组合](#响应对象中的模型组合)
+ - [在响应中增加头字段](#在响应中增加头字段)
+ - [使用多路径参数](#使用多路径参数)
+ - [结构体的示例值](#结构体的示例值)
+ - [结构体描述](#结构体描述)
+ - [使用`swaggertype`标签更改字段类型](#使用`swaggertype`标签更改字段类型)
+ - [使用`swaggerignore`标签排除字段](#使用swaggerignore标签排除字段)
+ - [将扩展信息添加到结构字段](#将扩展信息添加到结构字段)
+ - [对展示的模型重命名](#对展示的模型重命名)
+ - [如何使用安全性注释](#如何使用安全性注释)
+- [项目相关](#项目相关)
+
+## 快速开始
+
+1. 将注释添加到API源代码中,请参阅声明性注释格式。
+2. 使用如下命令下载swag:
+
+```bash
+go get -u github.com/swaggo/swag/cmd/swag
+```
+
+从源码开始构建的话,需要有Go环境(1.9及以上版本)。
+
+或者从github的release页面下载预编译好的二进制文件。
+
+3. 在包含`main.go`文件的项目根目录运行`swag init`。这将会解析注释并生成需要的文件(`docs`文件夹和`docs/docs.go`)。
+
+```bash
+swag init
+```
+
+确保导入了生成的`docs/docs.go`文件,这样特定的配置文件才会被初始化。如果通用API指数没有写在`main.go`中,可以使用`-g`标识符来告知swag。
+
+```bash
+swag init -g http/api.go
+```
+
+## swag cli
+
+```bash
+swag init -h
+NAME:
+ swag init - Create docs.go
+
+USAGE:
+ swag init [command options] [arguments...]
+
+OPTIONS:
+ --generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go")
+ --dir value, -d value API解析目录 (默认: "./")
+ --propertyStrategy value, -p value 结构体字段命名规则,三种:snakecase,camelcase,pascalcase (默认: "camelcase")
+ --output value, -o value 文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs")
+ --parseVendor 是否解析vendor目录里的go源文件,默认不
+ --parseDependency 是否解析依赖目录中的go源文件,默认不
+ --markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录
+ --generatedTime 是否输出时间到输出文件docs.go的顶部,默认是
+```
+
+## 支持的Web框架
+
+- [gin](http://github.com/swaggo/gin-swagger)
+- [echo](http://github.com/swaggo/echo-swagger)
+- [buffalo](https://github.com/swaggo/buffalo-swagger)
+- [net/http](https://github.com/swaggo/http-swagger)
+
+## 如何与Gin集成
+
+[点击此处](https://github.com/swaggo/swag/tree/master/example/celler)查看示例源代码。
+
+1. 使用`swag init`生成Swagger2.0文档后,导入如下代码包:
+
+```go
+import "github.com/swaggo/gin-swagger" // gin-swagger middleware
+import "github.com/swaggo/files" // swagger embed files
+```
+
+2. 在`main.go`源代码中添加通用的API注释:
+
+```go
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server celler server.
+// @termsOfService http://swagger.io/terms/
+
+// @contact.name API Support
+// @contact.url http://www.swagger.io/support
+// @contact.email support@swagger.io
+
+// @license.name Apache 2.0
+// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
+
+// @host localhost:8080
+// @BasePath /api/v1
+// @query.collection.format multi
+
+// @securityDefinitions.basic BasicAuth
+
+// @securityDefinitions.apikey ApiKeyAuth
+// @in header
+// @name Authorization
+
+// @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
+
+// @securitydefinitions.oauth2.implicit OAuth2Implicit
+// @authorizationurl https://example.com/oauth/authorize
+// @scope.write Grants write access
+// @scope.admin Grants read and write access to administrative information
+
+// @securitydefinitions.oauth2.password OAuth2Password
+// @tokenUrl https://example.com/oauth/token
+// @scope.read Grants read access
+// @scope.write Grants write access
+// @scope.admin Grants read and write access to administrative information
+
+// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
+// @tokenUrl https://example.com/oauth/token
+// @authorizationurl https://example.com/oauth/authorize
+// @scope.admin Grants read and write access to administrative information
+
+// @x-extension-openapi {"example": "value on a json format"}
+
+func main() {
+ r := gin.Default()
+
+ c := controller.NewController()
+
+ v1 := r.Group("/api/v1")
+ {
+ accounts := v1.Group("/accounts")
+ {
+ accounts.GET(":id", c.ShowAccount)
+ accounts.GET("", c.ListAccounts)
+ accounts.POST("", c.AddAccount)
+ accounts.DELETE(":id", c.DeleteAccount)
+ accounts.PATCH(":id", c.UpdateAccount)
+ accounts.POST(":id/images", c.UploadAccountImage)
+ }
+ //...
+ }
+ r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
+ r.Run(":8080")
+}
+//...
+```
+
+此外,可以动态设置一些通用的API信息。生成的代码包`docs`导出`SwaggerInfo`变量,使用该变量可以通过编码的方式设置标题、描述、版本、主机和基础路径。使用Gin的示例:
+
+```go
+package main
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/swaggo/files"
+ "github.com/swaggo/gin-swagger"
+
+ "./docs" // docs is generated by Swag CLI, you have to import it.
+)
+
+// @contact.name API Support
+// @contact.url http://www.swagger.io/support
+// @contact.email support@swagger.io
+
+// @license.name Apache 2.0
+// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
+
+// @termsOfService http://swagger.io/terms/
+
+func main() {
+
+ // programatically set swagger info
+ docs.SwaggerInfo.Title = "Swagger Example API"
+ docs.SwaggerInfo.Description = "This is a sample server Petstore server."
+ docs.SwaggerInfo.Version = "1.0"
+ docs.SwaggerInfo.Host = "petstore.swagger.io"
+ docs.SwaggerInfo.BasePath = "/v2"
+ docs.SwaggerInfo.Schemes = []string{"http", "https"}
+
+ r := gin.New()
+
+ // use ginSwagger middleware to serve the API docs
+ r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
+
+ r.Run()
+}
+```
+
+3. 在`controller`代码中添加API操作注释:
+
+```go
+package controller
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/swaggo/swag/example/celler/httputil"
+ "github.com/swaggo/swag/example/celler/model"
+)
+
+// ShowAccount godoc
+// @Summary Show a account
+// @Description get string by ID
+// @ID get-string-by-int
+// @Accept json
+// @Produce json
+// @Param id path int true "Account ID"
+// @Success 200 {object} model.Account
+// @Header 200 {string} Token "qwerty"
+// @Failure 400,404 {object} httputil.HTTPError
+// @Failure 500 {object} httputil.HTTPError
+// @Failure default {object} httputil.DefaultError
+// @Router /accounts/{id} [get]
+func (c *Controller) ShowAccount(ctx *gin.Context) {
+ id := ctx.Param("id")
+ aid, err := strconv.Atoi(id)
+ if err != nil {
+ httputil.NewError(ctx, http.StatusBadRequest, err)
+ return
+ }
+ account, err := model.AccountOne(aid)
+ if err != nil {
+ httputil.NewError(ctx, http.StatusNotFound, err)
+ return
+ }
+ ctx.JSON(http.StatusOK, account)
+}
+
+// ListAccounts godoc
+// @Summary List accounts
+// @Description get accounts
+// @Accept json
+// @Produce json
+// @Param q query string false "name search by q"
+// @Success 200 {array} model.Account
+// @Header 200 {string} Token "qwerty"
+// @Failure 400,404 {object} httputil.HTTPError
+// @Failure 500 {object} httputil.HTTPError
+// @Failure default {object} httputil.DefaultError
+// @Router /accounts [get]
+func (c *Controller) ListAccounts(ctx *gin.Context) {
+ q := ctx.Request.URL.Query().Get("q")
+ accounts, err := model.AccountsAll(q)
+ if err != nil {
+ httputil.NewError(ctx, http.StatusNotFound, err)
+ return
+ }
+ ctx.JSON(http.StatusOK, accounts)
+}
+
+//...
+```
+
+```bash
+swag init
+```
+
+4. 运行程序,然后在浏览器中访问 http://localhost:8080/swagger/index.html。将看到Swagger 2.0 Api文档,如下所示:
+
+![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png)
+
+## 开发现状
+
+[Swagger 2.0 文档](https://swagger.io/docs/specification/2-0/basic-structure/)
+
+- [x] Basic Structure
+- [x] API Host and Base Path
+- [x] Paths and Operations
+- [x] Describing Parameters
+- [x] Describing Request Body
+- [x] Describing Responses
+- [x] MIME Types
+- [x] Authentication
+ - [x] Basic Authentication
+ - [x] API Keys
+- [x] Adding Examples
+- [x] File Upload
+- [x] Enums
+- [x] Grouping Operations With Tags
+- [ ] Swagger Extensions
+
+## 声明式注释格式
+
+## 通用API信息
+
+**示例** [`celler/main.go`](https://github.com/swaggo/swag/blob/master/example/celler/main.go)
+
+| 注释 | 说明 | 示例 |
+| ----------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
+| title | **必填** 应用程序的名称。 | // @title Swagger Example API |
+| version | **必填** 提供应用程序API的版本。 | // @version 1.0 |
+| description | 应用程序的简短描述。 | // @description This is a sample server celler server. |
+| tag.name | 标签的名称。 | // @tag.name This is the name of the tag |
+| tag.description | 标签的描述。 | // @tag.description Cool Description |
+| tag.docs.url | 标签的外部文档的URL。 | // @tag.docs.url https://example.com |
+| tag.docs.description | 标签的外部文档说明。 | // @tag.docs.description Best example documentation |
+| termsOfService | API的服务条款。 | // @termsOfService http://swagger.io/terms/ |
+| contact.name | 公开的API的联系信息。 | // @contact.name API Support |
+| contact.url | 联系信息的URL。 必须采用网址格式。 | // @contact.url http://www.swagger.io/support |
+| contact.email | 联系人/组织的电子邮件地址。 必须采用电子邮件地址的格式。 | // @contact.email support@swagger.io |
+| license.name | **必填** 用于API的许可证名称。 | // @license.name Apache 2.0 |
+| license.url | 用于API的许可证的URL。 必须采用网址格式。 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html |
+| host | 运行API的主机(主机名或IP地址)。 | // @host localhost:8080 |
+| BasePath | 运行API的基本路径。 | // @BasePath /api/v1 |
+| query.collection.format | 请求URI query里数组参数的默认格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。 | // @query.collection.format multi |
+| schemes | 用空格分隔的请求的传输协议。 | // @schemes http https |
+| x-name | 扩展的键必须以x-开头,并且只能使用json值 | // @x-example-key {"key": "value"} |
+
+### 使用Markdown描述
+
+如果文档中的短字符串不足以完整表达,或者需要展示图片,代码示例等类似的内容,则可能需要使用Markdown描述。要使用Markdown描述,请使用一下注释。
+
+| 注释 | 说明 | 示例 |
+| ------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
+| title | **必填** 应用程序的名称。 | // @title Swagger Example API |
+| version | **必填** 提供应用程序API的版本。 | // @version 1.0 |
+| description.markdown | 应用程序的简短描述。 从`api.md`文件中解析。 这是`@description`的替代用法。 | // @description.markdown No value needed, this parses the description from api.md |
+| tag.name | 标签的名称。 | // @tag.name This is the name of the tag |
+| tag.description.markdown | 标签说明,这是`tag.description`的替代用法。 该描述将从名为`tagname.md的`文件中读取。 | // @tag.description.markdown |
+
+## API操作
+
+Example [celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller)
+
+| 注释 | 描述 |
+| -------------------- | ------------------------------------------------------------------------------------------------------- |
+| description | 操作行为的详细说明。 |
+| description.markdown | 应用程序的简短描述。该描述将从名为`endpointname.md`的文件中读取。 |
+| id | 用于标识操作的唯一字符串。在所有API操作中必须唯一。 |
+| tags | 每个API操作的标签列表,以逗号分隔。 |
+| summary | 该操作的简短摘要。 |
+| accept | API可以使用的MIME类型的列表。值必须如“[Mime类型](#mime-types)”中所述。 |
+| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime-types)”中所述。 |
+| param | 用空格分隔的参数。`param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
+| security | 每个API操作的[安全性](#security)。 |
+| success | 以空格分隔的成功响应。`return code`,`{param type}`,`data type`,`comment` |
+| failure | 以空格分隔的故障响应。`return code`,`{param type}`,`data type`,`comment` |
+| response | 与success、failure作用相同 |
+| header | 以空格分隔的头字段。 `return code`,`{param type}`,`data type`,`comment` |
+| router | 以空格分隔的路径定义。 `path`,`[httpMethod]` |
+| x-name | 扩展字段必须以`x-`开头,并且只能使用json值。 |
+
+## Mime类型
+
+`swag` 接受所有格式正确的MIME类型, 即使匹配 `*/*`。除此之外,`swag`还接受某些MIME类型的别名,如下所示:
+
+| 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 |
+
+## 参数类型
+
+- query
+- path
+- header
+- body
+- formData
+
+## 数据类型
+
+- string (string)
+- integer (int, uint, uint32, uint64)
+- number (float32)
+- boolean (bool)
+- user defined struct
+
+## 安全性
+
+| 注释 | 描述 | 参数 | 示例 |
+| -------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------ |
+| 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 |
+
+| 参数注释 | 示例 |
+| ---------------- | -------------------------------------------------------- |
+| 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 |
+
+## 属性
+
+```go
+// @Param enumstring query string false "string enums" Enums(A, B, C)
+// @Param enumint query int false "int enums" Enums(1, 2, 3)
+// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)
+// @Param string query string false "string valid" minlength(5) maxlength(10)
+// @Param int query int false "int valid" minimum(1) maximum(10)
+// @Param default query string false "string default" default(A)
+// @Param collection query []string false "string collection" collectionFormat(multi)
+```
+
+也适用于结构体字段:
+
+```go
+type Foo struct {
+ Bar string `minLength:"4" maxLength:"16"`
+ Baz int `minimum:"10" maximum:"20" default:"15"`
+ Qux []string `enums:"foo,bar,baz"`
+}
+```
+
+### 当前可用的
+
+| 字段名 | 类型 | 描述 |
+| ---------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| default | * | 声明如果未提供任何参数,则服务器将使用的默认参数值,例如,如果请求中的客户端未提供该参数,则用于控制每页结果数的“计数”可能默认为100。 (注意:“default”对于必需的参数没有意义)。参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2。 与JSON模式不同,此值务必符合此参数的定义[类型](#parameterType)。 |
+| maximum | `number` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2. |
+| minimum | `number` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3. |
+| maxLength | `integer` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1. |
+| minLength | `integer` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2. |
+| enums | [\*] | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1. |
+| format | `string` | 上面提到的[类型](#parameterType)的扩展格式。有关更多详细信息,请参见[数据类型格式](https://swagger.io/specification/v2/#dataTypeFormat)。 |
+| collectionFormat | `string` | 指定query数组参数的格式。 可能的值为: - `csv` - 逗号分隔值 `foo,bar`.
- `ssv` - 空格分隔值 `foo bar`.
- `tsv` - 制表符分隔值 `foo\tbar`.
- `pipes` - 管道符分隔值
foo|bar
. - `multi` - 对应于多个参数实例,而不是单个实例 `foo=bar&foo=baz` 的多个值。这仅对“`query`”或“`formData`”中的参数有效。
默认值是 `csv`。 |
+
+### 进一步的
+
+| 字段名 | 类型 | 描述 |
+| ----------- | :-------: | ---------------------------------------------------------------------------------- |
+| multipleOf | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1. |
+| pattern | `string` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3. |
+| maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2. |
+| minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3. |
+| uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4. |
+
+## 样例
+
+### 多行的描述
+
+可以在常规api描述或路由定义中添加跨越多行的描述,如下所示:
+
+```go
+// @description This is the first line
+// @description This is the second line
+// @description And so forth.
+```
+
+### 用户自定义的具有数组类型的结构
+
+```go
+// @Success 200 {array} model.Account <-- This is a user defined struct.
+```
+
+```go
+package model
+
+type Account struct {
+ ID int `json:"id" example:"1"`
+ Name string `json:"name" example:"account name"`
+}
+```
+
+### 响应对象中的模型组合
+
+```go
+// JSONResult的data字段类型将被proto.Order类型替换
+@success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc"
+```
+
+```go
+type JSONResult struct {
+ Code int `json:"code" `
+ Message string `json:"message"`
+ Data interface{} `json:"data"`
+}
+
+type Order struct { //in `proto` package
+ ...
+}
+```
+
+- 还支持对象数组和原始类型作为嵌套响应
+
+```go
+@success 200 {object} jsonresult.JSONResult{data=[]proto.Order} "desc"
+@success 200 {object} jsonresult.JSONResult{data=string} "desc"
+@success 200 {object} jsonresult.JSONResult{data=[]string} "desc"
+```
+
+- 替换多个字段的类型。如果某字段不存在,将添加该字段。
+
+```go
+@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc"
+```
+
+### 在响应中增加头字段
+
+```go
+// @Success 200 {string} string "ok"
+// @failure 400 {string} string "error"
+// @response default {string} string "other error"
+// @Header 200 {string} Location "/entity/1"
+// @Header 200,400,default {string} Token "token"
+// @Header all {string} Token2 "token2"
+```
+
+### 使用多路径参数
+
+```go
+/// ...
+// @Param group_id path int true "Group ID"
+// @Param account_id path int true "Account ID"
+// ...
+// @Router /examples/groups/{group_id}/accounts/{account_id} [get]
+```
+
+### 结构体的示例值
+
+```go
+type Account struct {
+ ID int `json:"id" example:"1"`
+ Name string `json:"name" example:"account name"`
+ PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
+}
+```
+
+### 结构体描述
+
+```go
+type Account struct {
+ // ID this is userid
+ ID int `json:"id"`
+ Name string `json:"name"` // This is Name
+}
+```
+
+### 使用`swaggertype`标签更改字段类型
+
+[#201](https://github.com/swaggo/swag/issues/201#issuecomment-475479409)
+
+```go
+type TimestampTime struct {
+ time.Time
+}
+
+///实现encoding.JSON.Marshaler接口
+func (t *TimestampTime) MarshalJSON() ([]byte, error) {
+ bin := make([]byte, 16)
+ bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10)
+ return bin, nil
+}
+
+///实现encoding.JSON.Unmarshaler接口
+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 {
+ // 使用`swaggertype`标签将别名类型更改为内置类型integer
+ ID sql.NullInt64 `json:"id" swaggertype:"integer"`
+
+ // 使用`swaggertype`标签更改struct类型为内置类型integer
+ RegisterTime TimestampTime `json:"register_time" swaggertype:"primitive,integer"`
+
+ // Array types can be overridden using "array," format
+ Coeffs []big.Float `json:"coeffs" swaggertype:"array,number"`
+}
+```
+
+[#379](https://github.com/swaggo/swag/issues/379)
+
+```go
+type CerticateKeyPair struct {
+ Crt []byte `json:"crt" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
+ Key []byte `json:"key" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
+}
+```
+
+生成的swagger文档如下:
+
+```go
+"api.MyBinding": {
+ "type":"object",
+ "properties":{
+ "crt":{
+ "type":"string",
+ "format":"base64",
+ "example":"U3dhZ2dlciByb2Nrcw=="
+ },
+ "key":{
+ "type":"string",
+ "format":"base64",
+ "example":"U3dhZ2dlciByb2Nrcw=="
+ }
+ }
+}
+```
+
+### 使用`swaggerignore`标签排除字段
+
+```go
+type Account struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Ignored int `swaggerignore:"true"`
+}
+```
+
+### 将扩展信息添加到结构字段
+
+```go
+type Account struct {
+ ID string `json:"id" extensions:"x-nullable,x-abc=def"` // 扩展字段必须以"x-"开头
+}
+```
+
+生成swagger文档,如下所示:
+
+```go
+"Account": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "x-nullable": true,
+ "x-abc": "def"
+ }
+ }
+}
+```
+
+### 对展示的模型重命名
+
+```go
+type Resp struct {
+ Code int
+}//@name Response
+```
+
+### 如何使用安全性注释
+
+通用API信息。
+
+```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
+```
+
+每个API操作。
+
+```go
+// @Security ApiKeyAuth
+```
+
+使用AND条件。
+
+```go
+// @Security ApiKeyAuth
+// @Security OAuth2Application[write, admin]
+```
+
+## 项目相关
+
+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).
+
+## 贡献者
+
+This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+
+
+## 支持者
+
+Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/swag#backer)]
+
+
+
+## 赞助商
+
+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)]
+
+
+
+
+
+
+
+
+
+
+
+
+## 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)
diff --git a/cmd/swag/main.go b/cmd/swag/main.go
index 59dd0d4a8..75c175617 100644
--- a/cmd/swag/main.go
+++ b/cmd/swag/main.go
@@ -12,35 +12,47 @@ import (
const (
searchDirFlag = "dir"
+ excludeFlag = "exclude"
generalInfoFlag = "generalInfo"
propertyStrategyFlag = "propertyStrategy"
outputFlag = "output"
parseVendorFlag = "parseVendor"
parseDependencyFlag = "parseDependency"
markdownFilesFlag = "markdownFiles"
+ codeExampleFilesFlag = "codeExampleFiles"
+ parseInternalFlag = "parseInternal"
generatedTimeFlag = "generatedTime"
+ parseDepthFlag = "parseDepth"
)
var initFlags = []cli.Flag{
&cli.StringFlag{
- Name: generalInfoFlag + ", g",
- Value: "main.go",
- Usage: "Go file path in which 'swagger general API Info' is written",
+ Name: generalInfoFlag,
+ Aliases: []string{"g"},
+ Value: "main.go",
+ Usage: "Go file path in which 'swagger general API Info' is written",
},
&cli.StringFlag{
- Name: searchDirFlag + ", d",
- Value: "./",
- Usage: "Directory you want to parse",
+ Name: searchDirFlag,
+ Aliases: []string{"d"},
+ Value: "./",
+ Usage: "Directory you want to parse",
},
&cli.StringFlag{
- Name: propertyStrategyFlag + ", p",
- Value: "camelcase",
- Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase",
+ Name: excludeFlag,
+ Usage: "Exclude directories and files when searching, comma separated",
},
&cli.StringFlag{
- Name: outputFlag + ", o",
- Value: "./docs",
- Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)",
+ Name: propertyStrategyFlag,
+ Aliases: []string{"p"},
+ Value: "camelcase",
+ Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase",
+ },
+ &cli.StringFlag{
+ Name: outputFlag,
+ Aliases: []string{"o"},
+ Value: "./docs",
+ Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)",
},
&cli.BoolFlag{
Name: parseVendorFlag,
@@ -51,13 +63,29 @@ var initFlags = []cli.Flag{
Usage: "Parse go files in outside dependency folder, disabled by default",
},
&cli.StringFlag{
- Name: markdownFilesFlag + ", md",
- Value: "",
- Usage: "Parse folder containing markdown files to use as description, disabled by default",
+ Name: markdownFilesFlag,
+ Aliases: []string{"md"},
+ Value: "",
+ Usage: "Parse folder containing markdown files to use as description, disabled by default",
+ },
+ &cli.StringFlag{
+ Name: codeExampleFilesFlag,
+ Aliases: []string{"cef"},
+ Value: "",
+ Usage: "Parse folder containing code example files to use for the x-codeSamples extension, disabled by default",
},
&cli.BoolFlag{
- Name: "generatedTime",
- Usage: "Generate timestamp at the top of docs.go, true by default",
+ Name: parseInternalFlag,
+ Usage: "Parse go files in internal packages, disabled by default",
+ },
+ &cli.BoolFlag{
+ Name: generatedTimeFlag,
+ Usage: "Generate timestamp at the top of docs.go, disabled by default",
+ },
+ &cli.IntFlag{
+ Name: parseDepthFlag,
+ Value: 100,
+ Usage: "Dependency parse depth",
},
}
@@ -71,14 +99,18 @@ func initAction(c *cli.Context) error {
}
return gen.New().Build(&gen.Config{
- SearchDir: c.String(searchDirFlag),
- MainAPIFile: c.String(generalInfoFlag),
- PropNamingStrategy: strategy,
- OutputDir: c.String(outputFlag),
- ParseVendor: c.Bool(parseVendorFlag),
- ParseDependency: c.Bool(parseDependencyFlag),
- MarkdownFilesDir: c.String(markdownFilesFlag),
- GeneratedTime: c.Bool(generatedTimeFlag),
+ SearchDir: c.String(searchDirFlag),
+ Excludes: c.String(excludeFlag),
+ MainAPIFile: c.String(generalInfoFlag),
+ PropNamingStrategy: strategy,
+ OutputDir: c.String(outputFlag),
+ ParseVendor: c.Bool(parseVendorFlag),
+ ParseDependency: c.Bool(parseDependencyFlag),
+ MarkdownFilesDir: c.String(markdownFilesFlag),
+ ParseInternal: c.Bool(parseInternalFlag),
+ GeneratedTime: c.Bool(generatedTimeFlag),
+ CodeExampleFilesDir: c.String(codeExampleFilesFlag),
+ ParseDepth: c.Int(parseDepthFlag),
})
}
diff --git a/example/basic/api/api.go b/example/basic/api/api.go
index 3709ff5d3..52be29cd5 100644
--- a/example/basic/api/api.go
+++ b/example/basic/api/api.go
@@ -1,7 +1,9 @@
package api
import (
- "github.com/gin-gonic/gin"
+ "encoding/json"
+ "net/http"
+
"github.com/swaggo/swag/example/basic/web"
)
@@ -17,9 +19,9 @@ import (
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
-func GetStringByInt(c *gin.Context) {
+func GetStringByInt(w http.ResponseWriter, r *http.Request) {
var pet web.Pet
- if err := c.ShouldBindJSON(&pet); err != nil {
+ if err := json.NewDecoder(r.Body).Decode(&pet); err != nil {
// write your code
return
}
@@ -39,7 +41,7 @@ func GetStringByInt(c *gin.Context) {
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
-func GetStructArrayByString(c *gin.Context) {
+func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
// write your code
}
@@ -54,7 +56,7 @@ func GetStructArrayByString(c *gin.Context) {
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
-func Upload(ctx *gin.Context) {
+func Upload(w http.ResponseWriter, r *http.Request) {
// write your code
}
diff --git a/example/basic/main.go b/example/basic/main.go
index a93ea32c3..9f22eb586 100644
--- a/example/basic/main.go
+++ b/example/basic/main.go
@@ -1,7 +1,8 @@
package main
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/example/basic/api"
)
@@ -20,10 +21,8 @@ import (
// @host petstore.swagger.io
// @BasePath /v2
func main() {
- r := gin.New()
- r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt)
- r.GET("//testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString)
- r.POST("/testapi/upload", api.Upload)
- r.Run()
-
+ http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
+ http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
+ http.HandleFunc("/testapi/upload", api.Upload)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/example/celler/controller/accounts.go b/example/celler/controller/accounts.go
index 72d4d46dc..0c0b0709d 100644
--- a/example/celler/controller/accounts.go
+++ b/example/celler/controller/accounts.go
@@ -11,7 +11,7 @@ import (
)
// ShowAccount godoc
-// @Summary Show a account
+// @Summary Show an account
// @Description get string by ID
// @Tags accounts
// @Accept json
@@ -60,7 +60,7 @@ func (c *Controller) ListAccounts(ctx *gin.Context) {
}
// AddAccount godoc
-// @Summary Add a account
+// @Summary Add an account
// @Description add by json account
// @Tags accounts
// @Accept json
@@ -94,7 +94,7 @@ func (c *Controller) AddAccount(ctx *gin.Context) {
}
// UpdateAccount godoc
-// @Summary Update a account
+// @Summary Update an account
// @Description Update by json account
// @Tags accounts
// @Accept json
@@ -131,7 +131,7 @@ func (c *Controller) UpdateAccount(ctx *gin.Context) {
}
// DeleteAccount godoc
-// @Summary Update a account
+// @Summary Delete an account
// @Description Delete by account ID
// @Tags accounts
// @Accept json
diff --git a/example/celler/controller/examples.go b/example/celler/controller/examples.go
index 5670ec8d0..d3838f88d 100644
--- a/example/celler/controller/examples.go
+++ b/example/celler/controller/examples.go
@@ -22,7 +22,6 @@ import (
// @Router /examples/ping [get]
func (c *Controller) PingExample(ctx *gin.Context) {
ctx.String(http.StatusOK, "pong")
- return
}
// CalcExample godoc
@@ -33,7 +32,7 @@ func (c *Controller) PingExample(ctx *gin.Context) {
// @Produce json
// @Param val1 query int true "used for calc"
// @Param val2 query int true "used for calc"
-// @Success 200 {integer} integer "answer"
+// @Success 200 {integer} string "answer"
// @Failure 400 {string} string "ok"
// @Failure 404 {string} string "ok"
// @Failure 500 {string} string "ok"
@@ -123,7 +122,7 @@ func (c *Controller) SecuritiesExample(ctx *gin.Context) {
// @Param enumint query int false "int enums" Enums(1, 2, 3)
// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)
// @Param string query string false "string valid" minlength(5) maxlength(10)
-// @Param int query int false "int valid" mininum(1) maxinum(10)
+// @Param int query int false "int valid" minimum(1) maximum(10)
// @Param default query string false "string default" default(A)
// @Success 200 {string} string "answer"
// @Failure 400 {string} string "ok"
@@ -140,3 +139,15 @@ func (c *Controller) AttributeExample(ctx *gin.Context) {
ctx.Query("default"),
))
}
+
+// PostExample godoc
+// @Summary post request example
+// @Description post request example
+// @Accept json
+// @Produce plain
+// @Param message body model.Account true "Account Info"
+// @Success 200 {string} string "success"
+// @Failure 500 {string} string "fail"
+// @Router /examples/post [post]
+func (c *Controller) PostExample(ctx *gin.Context) {
+}
diff --git a/example/celler/go.mod b/example/celler/go.mod
new file mode 100644
index 000000000..72c8d24e9
--- /dev/null
+++ b/example/celler/go.mod
@@ -0,0 +1,11 @@
+module github.com/swaggo/swag/example/celler
+
+go 1.13
+
+require (
+ github.com/gin-gonic/gin v1.6.3
+ github.com/gofrs/uuid v3.3.0+incompatible
+ github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
+ github.com/swaggo/gin-swagger v1.3.0
+ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
+)
diff --git a/example/celler/go.sum b/example/celler/go.sum
new file mode 100644
index 000000000..dc2785804
--- /dev/null
+++ b/example/celler/go.sum
@@ -0,0 +1,163 @@
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
+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/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/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
+github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
+github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+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.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
+github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
+github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
+github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
+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.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/gofrs/uuid v1.2.0 h1:coDhrjgyJaglxSjxuJdqQSSdUpG3w6p1OwN2od6frBU=
+github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
+github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+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 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
+github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
+github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
+github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZtPsI=
+github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0=
+github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM=
+github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
+github.com/swaggo/swag v1.6.2 h1:WQMAtT/FmMBb7g0rAuHDhG3vvdtHKJ3WZ+Ssb0p4Y6E=
+github.com/swaggo/swag v1.6.2/go.mod h1:YyZstMc22WYm6GEDx/CYWxq+faBbjQ5EqwQcrjREDBo=
+github.com/swaggo/swag v1.6.9 h1:BukKRwZjnEcUxQt7Xgfrt9fpav0hiWw9YimdNO9wssw=
+github.com/swaggo/swag v1.6.9/go.mod h1:a0IpNeMfGidNOcm2TsqODUh9JHdHu3kxDA0UlGbBKjI=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go v1.1.13 h1:nB3O5kBSQGjEQAcfe1aLUYuxmXdFKmYgBZhY32rQb6Q=
+github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
+github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
+github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4=
+github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0=
+golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+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/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/example/celler/model/account.go b/example/celler/model/account.go
index a3c7427fe..54fb9b5d6 100644
--- a/example/celler/model/account.go
+++ b/example/celler/model/account.go
@@ -4,7 +4,7 @@ import (
"errors"
"fmt"
- uuid "github.com/satori/go.uuid"
+ uuid "github.com/gofrs/uuid"
)
// Account example
diff --git a/example/markdown/api/api.go b/example/markdown/api/api.go
index 2bc900e27..db115f7d3 100644
--- a/example/markdown/api/api.go
+++ b/example/markdown/api/api.go
@@ -7,7 +7,7 @@ import (
// User example
type User struct {
- Id int64
+ ID int64
Email string
Password string
}
@@ -15,8 +15,8 @@ type User struct {
// UsersCollection example
type UsersCollection []User
-// APIError example
-type APIError struct {
+// Error example
+type Error struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
diff --git a/example/object-map-example/controller/api.go b/example/object-map-example/controller/api.go
new file mode 100644
index 000000000..83b5e99fe
--- /dev/null
+++ b/example/object-map-example/controller/api.go
@@ -0,0 +1,25 @@
+package controller
+
+import "github.com/gin-gonic/gin"
+
+// GetMap godoc
+// @Summary Get Map Example
+// @Description get map
+// @ID get-map
+// @Accept json
+// @Produce json
+// @Success 200 {object} Response
+// @Router /test [get]
+func (c *Controller) GetMap(ctx *gin.Context) {
+ ctx.JSON(200, Response{
+ Title: map[string]string{
+ "en": "Map",
+ },
+ CustomType: map[string]interface{}{
+ "key": "value",
+ },
+ Object: Data{
+ Text: "object text",
+ },
+ })
+}
diff --git a/example/object-map-example/controller/controller.go b/example/object-map-example/controller/controller.go
new file mode 100644
index 000000000..735d745de
--- /dev/null
+++ b/example/object-map-example/controller/controller.go
@@ -0,0 +1,10 @@
+package controller
+
+// Controller example
+type Controller struct {
+}
+
+// NewController example
+func NewController() *Controller {
+ return &Controller{}
+}
diff --git a/example/object-map-example/controller/response.go b/example/object-map-example/controller/response.go
new file mode 100644
index 000000000..d0692f08c
--- /dev/null
+++ b/example/object-map-example/controller/response.go
@@ -0,0 +1,11 @@
+package controller
+
+type Response struct {
+ Title map[string]string `json:"title" example:"en:Map,ru:Карта,kk:Карталар"`
+ CustomType map[string]interface{} `json:"map_data" swaggertype:"object,string" example:"key:value,key2:value2"`
+ Object Data `json:"object"`
+}
+
+type Data struct {
+ Text string `json:"title" example:"Object data"`
+}
diff --git a/example/object-map-example/docs/docs.go b/example/object-map-example/docs/docs.go
new file mode 100644
index 000000000..f73e708a4
--- /dev/null
+++ b/example/object-map-example/docs/docs.go
@@ -0,0 +1,141 @@
+// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
+// This file was generated by swaggo/swag
+
+package docs
+
+import (
+ "bytes"
+ "encoding/json"
+ "strings"
+
+ "github.com/alecthomas/template"
+ "github.com/swaggo/swag"
+)
+
+var doc = `{
+ "schemes": {{ marshal .Schemes }},
+ "swagger": "2.0",
+ "info": {
+ "description": "{{.Description}}",
+ "title": "{{.Title}}",
+ "termsOfService": "http://swagger.io/terms/",
+ "contact": {},
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ },
+ "version": "{{.Version}}"
+ },
+ "host": "{{.Host}}",
+ "basePath": "{{.BasePath}}",
+ "paths": {
+ "/test": {
+ "get": {
+ "description": "get map",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "summary": "Get Map Example",
+ "operationId": "get-map",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/controller.Response"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "controller.Data": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string",
+ "example": "Object data"
+ }
+ }
+ },
+ "controller.Response": {
+ "type": "object",
+ "properties": {
+ "map_data": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "example": {
+ "key": "value",
+ "key2": "value2"
+ }
+ },
+ "object": {
+ "$ref": "#/definitions/controller.Data"
+ },
+ "title": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "example": {
+ "en": "Map",
+ "kk": "Карталар",
+ "ru": "Карта"
+ }
+ }
+ }
+ }
+ }
+}`
+
+type swaggerInfo struct {
+ Version string
+ Host string
+ BasePath string
+ Schemes []string
+ Title string
+ Description string
+}
+
+// SwaggerInfo holds exported Swagger Info so clients can modify it
+var SwaggerInfo = swaggerInfo{
+ Version: "1.0",
+ Host: "localhost:8080",
+ BasePath: "/api/v1",
+ Schemes: []string{},
+ Title: "Swagger Map Example API",
+ Description: "",
+}
+
+type s struct{}
+
+func (s *s) ReadDoc() string {
+ sInfo := SwaggerInfo
+ sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
+
+ t, err := template.New("swagger_info").Funcs(template.FuncMap{
+ "marshal": func(v interface{}) string {
+ a, _ := json.Marshal(v)
+ return string(a)
+ },
+ }).Parse(doc)
+ if err != nil {
+ return doc
+ }
+
+ var tpl bytes.Buffer
+ if err := t.Execute(&tpl, sInfo); err != nil {
+ return doc
+ }
+
+ return tpl.String()
+}
+
+func init() {
+ swag.Register(swag.Name, &s{})
+}
diff --git a/example/object-map-example/docs/swagger.json b/example/object-map-example/docs/swagger.json
new file mode 100644
index 000000000..e20754c9d
--- /dev/null
+++ b/example/object-map-example/docs/swagger.json
@@ -0,0 +1,78 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "title": "Swagger Map Example API",
+ "termsOfService": "http://swagger.io/terms/",
+ "contact": {},
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ },
+ "version": "1.0"
+ },
+ "host": "localhost:8080",
+ "basePath": "/api/v1",
+ "paths": {
+ "/test": {
+ "get": {
+ "description": "get map",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "summary": "Get Map Example",
+ "operationId": "get-map",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/controller.Response"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "controller.Data": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string",
+ "example": "Object data"
+ }
+ }
+ },
+ "controller.Response": {
+ "type": "object",
+ "properties": {
+ "map_data": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "example": {
+ "key": "value",
+ "key2": "value2"
+ }
+ },
+ "object": {
+ "$ref": "#/definitions/controller.Data"
+ },
+ "title": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "example": {
+ "en": "Map",
+ "kk": "Карталар",
+ "ru": "Карта"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/example/object-map-example/docs/swagger.yaml b/example/object-map-example/docs/swagger.yaml
new file mode 100644
index 000000000..d7a65e140
--- /dev/null
+++ b/example/object-map-example/docs/swagger.yaml
@@ -0,0 +1,53 @@
+basePath: /api/v1
+definitions:
+ controller.Data:
+ properties:
+ title:
+ example: Object data
+ type: string
+ type: object
+ controller.Response:
+ properties:
+ map_data:
+ additionalProperties:
+ type: string
+ example:
+ key: value
+ key2: value2
+ type: object
+ object:
+ $ref: '#/definitions/controller.Data'
+ title:
+ additionalProperties:
+ type: string
+ example:
+ en: Map
+ kk: Карталар
+ ru: Карта
+ type: object
+ type: object
+host: localhost:8080
+info:
+ contact: {}
+ license:
+ name: Apache 2.0
+ url: http://www.apache.org/licenses/LICENSE-2.0.html
+ termsOfService: http://swagger.io/terms/
+ title: Swagger Map Example API
+ version: "1.0"
+paths:
+ /test:
+ get:
+ consumes:
+ - application/json
+ description: get map
+ operationId: get-map
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/controller.Response'
+ summary: Get Map Example
+swagger: "2.0"
diff --git a/example/object-map-example/go.mod b/example/object-map-example/go.mod
new file mode 100644
index 000000000..c4b5c151f
--- /dev/null
+++ b/example/object-map-example/go.mod
@@ -0,0 +1,11 @@
+module github.com/swaggo/swag/example/object-map-example
+
+go 1.14
+
+require (
+ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
+ github.com/gin-gonic/gin v1.6.3
+ github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
+ github.com/swaggo/gin-swagger v1.3.0
+ github.com/swaggo/swag v1.5.1
+)
diff --git a/example/object-map-example/main.go b/example/object-map-example/main.go
new file mode 100644
index 000000000..0108f2c9f
--- /dev/null
+++ b/example/object-map-example/main.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/swaggo/swag/example/object-map-example/controller"
+ _ "github.com/swaggo/swag/example/object-map-example/docs"
+
+ swaggerFiles "github.com/swaggo/files"
+ ginSwagger "github.com/swaggo/gin-swagger"
+)
+
+// @title Swagger Map Example API
+// @version 1.0
+// @termsOfService http://swagger.io/terms/
+
+// @license.name Apache 2.0
+// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
+
+// @host localhost:8080
+// @BasePath /api/v1
+func main() {
+ r := gin.Default()
+
+ c := controller.NewController()
+
+ v1 := r.Group("/api/v1")
+ {
+ test := v1.Group("/map")
+ {
+ test.GET("", c.GetMap)
+ }
+ }
+ r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
+ r.Run(":8080")
+}
diff --git a/gen/gen.go b/gen/gen.go
index 0edd211c2..291517485 100644
--- a/gen/gen.go
+++ b/gen/gen.go
@@ -8,7 +8,7 @@ import (
"io"
"log"
"os"
- "path"
+ "path/filepath"
"strings"
"text/template"
"time"
@@ -39,6 +39,9 @@ type Config struct {
// SearchDir the swag would be parse
SearchDir string
+ // excludes dirs and files in SearchDir,comma separated
+ Excludes string
+
// OutputDir represents the output directory for all the generated files
OutputDir string
@@ -54,11 +57,20 @@ type Config struct {
// ParseDependencies whether swag should be parse outside dependency folder
ParseDependency bool
+ // ParseInternal whether swag should parse internal packages
+ ParseInternal bool
+
// MarkdownFilesDir used to find markdownfiles, which can be used for tag descriptions
MarkdownFilesDir string
// GeneratedTime whether swag should generate the timestamp at the top of docs.go
GeneratedTime bool
+
+ // CodeExampleFilesDir used to find code example files, which can be used for x-codeSamples
+ CodeExampleFilesDir string
+
+ // ParseDepth dependency parse depth
+ ParseDepth int
}
// Build builds swagger json file for given searchDir and mainAPIFile. Returns json
@@ -68,12 +80,15 @@ func (g *Gen) Build(config *Config) error {
}
log.Println("Generate swagger docs....")
- p := swag.New(swag.SetMarkdownFileDirectory(config.MarkdownFilesDir))
+ p := swag.New(swag.SetMarkdownFileDirectory(config.MarkdownFilesDir),
+ swag.SetExcludedDirsAndFiles(config.Excludes),
+ swag.SetCodeExamplesDirectory(config.CodeExampleFilesDir))
p.PropNamingStrategy = config.PropNamingStrategy
p.ParseVendor = config.ParseVendor
p.ParseDependency = config.ParseDependency
+ p.ParseInternal = config.ParseInternal
- if err := p.ParseAPI(config.SearchDir, config.MainAPIFile); err != nil {
+ if err := p.ParseAPI(config.SearchDir, config.MainAPIFile, config.ParseDepth); err != nil {
return err
}
swagger := p.GetSwagger()
@@ -87,10 +102,14 @@ func (g *Gen) Build(config *Config) error {
return err
}
- packageName := path.Base(config.OutputDir)
- docFileName := path.Join(config.OutputDir, "docs.go")
- jsonFileName := path.Join(config.OutputDir, "swagger.json")
- yamlFileName := path.Join(config.OutputDir, "swagger.yaml")
+ absOutputDir, err := filepath.Abs(config.OutputDir)
+ if err != nil {
+ return err
+ }
+ packageName := filepath.Base(absOutputDir)
+ docFileName := filepath.Join(config.OutputDir, "docs.go")
+ jsonFileName := filepath.Join(config.OutputDir, "swagger.json")
+ yamlFileName := filepath.Join(config.OutputDir, "swagger.yaml")
docs, err := os.Create(docFileName)
if err != nil {
diff --git a/gen/gen_test.go b/gen/gen_test.go
index 075917b58..a61ccdec0 100644
--- a/gen/gen_test.go
+++ b/gen/gen_test.go
@@ -4,7 +4,6 @@ import (
"errors"
"os"
"os/exec"
- "path"
"path/filepath"
"testing"
@@ -24,9 +23,9 @@ func TestGen_Build(t *testing.T) {
assert.NoError(t, New().Build(config))
expectedFiles := []string{
- path.Join(config.OutputDir, "docs.go"),
- path.Join(config.OutputDir, "swagger.json"),
- path.Join(config.OutputDir, "swagger.yaml"),
+ filepath.Join(config.OutputDir, "docs.go"),
+ filepath.Join(config.OutputDir, "swagger.json"),
+ filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
@@ -48,9 +47,9 @@ func TestGen_BuildSnakecase(t *testing.T) {
assert.NoError(t, New().Build(config))
expectedFiles := []string{
- path.Join(config.OutputDir, "docs.go"),
- path.Join(config.OutputDir, "swagger.json"),
- path.Join(config.OutputDir, "swagger.yaml"),
+ filepath.Join(config.OutputDir, "docs.go"),
+ filepath.Join(config.OutputDir, "swagger.json"),
+ filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
@@ -72,9 +71,9 @@ func TestGen_BuildLowerCamelcase(t *testing.T) {
assert.NoError(t, New().Build(config))
expectedFiles := []string{
- path.Join(config.OutputDir, "docs.go"),
- path.Join(config.OutputDir, "swagger.json"),
- path.Join(config.OutputDir, "swagger.yaml"),
+ filepath.Join(config.OutputDir, "docs.go"),
+ filepath.Join(config.OutputDir, "swagger.json"),
+ filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
@@ -116,8 +115,8 @@ func TestGen_jsonToYAML(t *testing.T) {
assert.Error(t, gen.Build(config))
expectedFiles := []string{
- path.Join(config.OutputDir, "docs.go"),
- path.Join(config.OutputDir, "swagger.json"),
+ filepath.Join(config.OutputDir, "docs.go"),
+ filepath.Join(config.OutputDir, "swagger.json"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
@@ -226,9 +225,9 @@ func TestGen_configWithOutputDir(t *testing.T) {
assert.NoError(t, New().Build(config))
expectedFiles := []string{
- path.Join(config.OutputDir, "docs.go"),
- path.Join(config.OutputDir, "swagger.json"),
- path.Join(config.OutputDir, "swagger.yaml"),
+ filepath.Join(config.OutputDir, "docs.go"),
+ filepath.Join(config.OutputDir, "swagger.json"),
+ filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
@@ -334,9 +333,35 @@ func TestGen_GeneratedDoc(t *testing.T) {
assert.NoError(t, cmd.Run())
expectedFiles := []string{
- path.Join(config.OutputDir, "docs.go"),
- path.Join(config.OutputDir, "swagger.json"),
- path.Join(config.OutputDir, "swagger.yaml"),
+ filepath.Join(config.OutputDir, "docs.go"),
+ filepath.Join(config.OutputDir, "swagger.json"),
+ filepath.Join(config.OutputDir, "swagger.yaml"),
+ }
+ for _, expectedFile := range expectedFiles {
+ if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
+ t.Fatal(err)
+ }
+ os.Remove(expectedFile)
+ }
+}
+
+func TestGen_cgoImports(t *testing.T) {
+ searchDir := "../testdata/simple_cgo"
+
+ config := &Config{
+ SearchDir: searchDir,
+ MainAPIFile: "./main.go",
+ OutputDir: "../testdata/simple_cgo/docs",
+ PropNamingStrategy: "",
+ ParseDependency: true,
+ }
+
+ assert.NoError(t, New().Build(config))
+
+ expectedFiles := []string{
+ filepath.Join(config.OutputDir, "docs.go"),
+ filepath.Join(config.OutputDir, "swagger.json"),
+ filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
diff --git a/go.mod b/go.mod
index f02834645..72d4a5f29 100644
--- a/go.mod
+++ b/go.mod
@@ -4,15 +4,12 @@ require (
github.com/KyleBanks/depth v1.2.1
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/ghodss/yaml v1.0.0
- github.com/gin-gonic/gin v1.4.0
- github.com/go-openapi/jsonreference v0.19.3
- github.com/go-openapi/spec v0.19.4
- github.com/satori/go.uuid v1.2.0
- github.com/stretchr/testify v1.4.0
- github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
- github.com/swaggo/gin-swagger v1.2.0
- github.com/urfave/cli/v2 v2.1.1
- golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59
+ github.com/go-openapi/spec v0.20.0
+ github.com/stretchr/testify v1.7.0
+ github.com/urfave/cli/v2 v2.3.0
+ golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
+ golang.org/x/text v0.3.5 // indirect
+ golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064
)
-go 1.13
+go 1.15
diff --git a/go.sum b/go.sum
index ab7e7c11c..8e5559667 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,6 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
-github.com/PuerkitoBio/purell v1.1.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=
@@ -11,134 +9,101 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
-github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
-github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
-github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
-github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
-github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
-github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
-github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
-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.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
-github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
-github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
-github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
-github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
-github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
-github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
-github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
-github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
-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.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
+github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
+github.com/go-openapi/spec v0.20.0 h1:HGLc8AJ7ynOxwv0Lq4TsnwLsWMawHAYiJIFzbcML86I=
+github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-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.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-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/go-openapi/swag v0.19.12 h1:Bc0bnY2c3AoF7Gc+IMIAQQsD8fLHjHpc19wXvYuayQI=
+github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/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/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
-github.com/mattn/go-isatty v0.0.8/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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-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/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
-github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
-github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
-github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
-github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/ugorji/go v1.1.5-pre h1:jyJKFOSEbdOc2HODrf2qcCkYOdq7zzXqA9bhW5oV4fM=
-github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
-github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
-github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs=
-github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
-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=
-github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
-github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY=
-golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
-golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
-golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0=
-golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
-golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064 h1:BmCFkEH4nJrYcAc2L08yX5RhYGD4j58PTMkEUDkpz2I=
+golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/operation.go b/operation.go
index b3ee04379..6e357aa17 100644
--- a/operation.go
+++ b/operation.go
@@ -6,13 +6,14 @@ import (
"go/ast"
goparser "go/parser"
"go/token"
+ "io/ioutil"
"net/http"
"os"
+ "path/filepath"
"regexp"
"strconv"
"strings"
- "github.com/go-openapi/jsonreference"
"github.com/go-openapi/spec"
"golang.org/x/tools/go/loader"
)
@@ -24,7 +25,8 @@ type Operation struct {
Path string
spec.Operation
- parser *Parser
+ parser *Parser
+ codeExampleFilesDir string
}
var mimeTypeAliases = map[string]string{
@@ -46,13 +48,34 @@ var mimeTypePattern = regexp.MustCompile("^[^/]+/[^/]+$")
// NewOperation creates a new Operation with default properties.
// map[int]Response
-func NewOperation() *Operation {
- return &Operation{
+func NewOperation(parser *Parser, options ...func(*Operation)) *Operation {
+ if parser == nil {
+ parser = New()
+ }
+
+ result := &Operation{
+ parser: parser,
HTTPMethod: "get",
Operation: spec.Operation{
OperationProps: spec.OperationProps{},
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{},
+ },
},
}
+
+ for _, option := range options {
+ option(result)
+ }
+
+ return result
+}
+
+// SetCodeExampleFilesDirectory sets the directory to search for codeExamples
+func SetCodeExampleFilesDirectory(directoryPath string) func(*Operation) {
+ return func(o *Operation) {
+ o.codeExampleFilesDir = directoryPath
+ }
}
// ParseComment parses comment for given comment string and returns error if error occurs.
@@ -69,6 +92,12 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
switch lowerAttribute {
case "@description":
operation.ParseDescriptionComment(lineRemainder)
+ case "@description.markdown":
+ commentInfo, err := getMarkdownForTag(lineRemainder, operation.parser.markdownFileDir)
+ if err != nil {
+ return err
+ }
+ operation.ParseDescriptionComment(string(commentInfo))
case "@summary":
operation.Summary = lineRemainder
case "@id":
@@ -81,7 +110,7 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
err = operation.ParseProduceComment(lineRemainder)
case "@param":
err = operation.ParseParamComment(lineRemainder, astFile)
- case "@success", "@failure":
+ case "@success", "@failure", "@response":
err = operation.ParseResponseComment(lineRemainder, astFile)
case "@header":
err = operation.ParseResponseHeaderComment(lineRemainder, astFile)
@@ -91,13 +120,36 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro
err = operation.ParseSecurityComment(lineRemainder)
case "@deprecated":
operation.Deprecate()
+ case "@x-codesamples":
+ err = operation.ParseCodeSample(attribute, commentLine, lineRemainder)
default:
err = operation.ParseMetadata(attribute, lowerAttribute, lineRemainder)
}
-
return err
}
+// ParseCodeSample godoc
+func (operation *Operation) ParseCodeSample(attribute, commentLine, lineRemainder string) error {
+ if lineRemainder == "file" {
+ data, err := getCodeExampleForSummary(operation.Summary, operation.codeExampleFilesDir)
+ if err != nil {
+ return err
+ }
+
+ var valueJSON interface{}
+ if err := json.Unmarshal(data, &valueJSON); err != nil {
+ return fmt.Errorf("annotation %s need a valid json value", attribute)
+ }
+
+ operation.Extensions[attribute[1:]] = valueJSON // don't use the method provided by spec lib, cause it will call toLower() on attribute names, which is wrongy
+
+ return nil
+ }
+
+ // Fallback into existing logic
+ return operation.ParseMetadata(attribute, strings.ToLower(attribute), lineRemainder)
+}
+
// ParseDescriptionComment godoc
func (operation *Operation) ParseDescriptionComment(lineRemainder string) {
if operation.Description == "" {
@@ -119,7 +171,8 @@ func (operation *Operation) ParseMetadata(attribute, lowerAttribute, lineRemaind
if err := json.Unmarshal([]byte(lineRemainder), &valueJSON); err != nil {
return fmt.Errorf("annotation %s need a valid json value", attribute)
}
- operation.Operation.AddExtension(attribute[1:], valueJSON) // Trim "@" at head
+
+ operation.Extensions[attribute[1:]] = valueJSON // don't use the method provided by spec lib, cause it will call toLower() on attribute names, which is wrongy
}
return nil
}
@@ -140,14 +193,14 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
refType := TransToValidSchemeType(matches[3])
// Detect refType
- objectType := "object"
- if strings.HasPrefix(refType, "[]") == true {
- objectType = "array"
+ objectType := OBJECT
+ if strings.HasPrefix(refType, "[]") {
+ objectType = ARRAY
refType = strings.TrimPrefix(refType, "[]")
refType = TransToValidSchemeType(refType)
} else if IsPrimitiveType(refType) ||
paramType == "formData" && refType == "file" {
- objectType = "primitive"
+ objectType = PRIMITIVE
}
requiredText := strings.ToLower(matches[4])
@@ -157,34 +210,28 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
param := createParameter(paramType, description, name, refType, required)
switch paramType {
- case "path", "header", "formData":
+ case "path", "header":
switch objectType {
- case "array", "object":
+ case ARRAY, OBJECT:
return fmt.Errorf("%s is not supported type for %s", refType, paramType)
}
- case "query":
+ case "query", "formData":
switch objectType {
- case "array":
+ case ARRAY:
if !IsPrimitiveType(refType) {
return fmt.Errorf("%s is not supported array type for %s", refType, paramType)
}
- param.SimpleSchema.Type = "array"
+ param.SimpleSchema.Type = objectType
+ if operation.parser != nil {
+ param.CollectionFormat = TransToValidCollectionFormat(operation.parser.collectionFormatInQuery)
+ }
param.SimpleSchema.Items = &spec.Items{
SimpleSchema: spec.SimpleSchema{
Type: refType,
},
}
- case "object":
- refType, typeSpec, err := operation.registerSchemaType(refType, astFile)
- if err != nil {
- return err
- }
- structType, ok := typeSpec.Type.(*ast.StructType)
- if !ok {
- return fmt.Errorf("%s is not supported type for %s", refType, paramType)
- }
- refSplit := strings.Split(refType, ".")
- schema, err := operation.parser.parseStruct(refSplit[0], structType.Fields)
+ case OBJECT:
+ schema, err := operation.parser.getTypeSchema(refType, astFile, false)
if err != nil {
return err
}
@@ -199,16 +246,22 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
}
return false
}
- for name, prop := range schema.Properties {
+ items := schema.Properties.ToOrderedSchemaItems()
+ for _, item := range items {
+ name := item.Name
+ prop := item.Schema
if len(prop.Type) == 0 {
continue
}
- if prop.Type[0] == "array" &&
+ if prop.Type[0] == ARRAY &&
prop.Items.Schema != nil &&
len(prop.Items.Schema.Type) > 0 &&
IsSimplePrimitiveType(prop.Items.Schema.Type[0]) {
param = createParameter(paramType, prop.Description, name, prop.Type[0], find(schema.Required, name))
param.SimpleSchema.Type = prop.Type[0]
+ if operation.parser != nil && operation.parser.collectionFormatInQuery != "" && param.CollectionFormat == "" {
+ param.CollectionFormat = TransToValidCollectionFormat(operation.parser.collectionFormatInQuery)
+ }
param.SimpleSchema.Items = &spec.Items{
SimpleSchema: spec.SimpleSchema{
Type: prop.Items.Schema.Type[0],
@@ -220,6 +273,11 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
Println(fmt.Sprintf("skip field [%s] in %s is not supported type for %s", name, refType, paramType))
continue
}
+ param.Nullable = prop.Nullable
+ param.Format = prop.Format
+ param.Default = prop.Default
+ param.Example = prop.Example
+ param.Extensions = prop.Extensions
param.CommonValidations.Maximum = prop.Maximum
param.CommonValidations.Minimum = prop.Minimum
param.CommonValidations.ExclusiveMaximum = prop.ExclusiveMaximum
@@ -237,113 +295,42 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
return nil
}
case "body":
- switch objectType {
- case "primitive":
- param.Schema.Type = spec.StringOrArray{refType}
- case "array":
- param.Schema.Type = spec.StringOrArray{objectType}
- param.Schema.Items = &spec.SchemaOrArray{
- Schema: &spec.Schema{
- SchemaProps: spec.SchemaProps{},
- },
- }
- // Array of Primitive or Object
- if IsPrimitiveType(refType) {
- param.Schema.Items.Schema.Type = spec.StringOrArray{refType}
- } else {
- var err error
- refType, _, err = operation.registerSchemaType(refType, astFile)
- if err != nil {
- return err
- }
- param.Schema.Items.Schema.Ref = spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)}
- }
- case "object":
- var err error
- refType, _, err = operation.registerSchemaType(refType, astFile)
- if err != nil {
- return err
- }
- param.Schema.Type = []string{}
- param.Schema.Ref = spec.Ref{
- Ref: jsonreference.MustCreateRef("#/definitions/" + refType),
- }
+ schema, err := operation.parseAPIObjectSchema(objectType, refType, astFile)
+ if err != nil {
+ return err
}
+ param.Schema = schema
default:
return fmt.Errorf("%s is not supported paramType", paramType)
}
- if err := operation.parseAndExtractionParamAttribute(commentLine, refType, ¶m); err != nil {
+ if err := operation.parseAndExtractionParamAttribute(commentLine, objectType, refType, ¶m); err != nil {
return err
}
operation.Operation.Parameters = append(operation.Operation.Parameters, param)
return nil
}
-func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.File) (string, *ast.TypeSpec, error) {
- if !strings.ContainsRune(schemaType, '.') {
- if astFile == nil {
- return schemaType, nil, fmt.Errorf("no package name for type %s", schemaType)
- }
- schemaType = astFile.Name.String() + "." + schemaType
- }
- refSplit := strings.Split(schemaType, ".")
- pkgName := refSplit[0]
- typeName := refSplit[1]
- if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok {
- operation.parser.registerTypes[schemaType] = typeSpec
- return schemaType, typeSpec, nil
- }
- var typeSpec *ast.TypeSpec
- if astFile == nil {
- return schemaType, nil, 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 schemaType, nil, fmt.Errorf("can not find type def: %q error: %s", schemaType, err)
- }
- break
- }
- }
-
- if typeSpec == nil {
- return schemaType, nil, 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 schemaType, typeSpec, nil
-}
-
var regexAttributes = map[string]*regexp.Regexp{
// for Enums(A, B)
- "enums": regexp.MustCompile(`(?i)enums\(.*\)`),
- // for Minimum(0)
- "maxinum": regexp.MustCompile(`(?i)maxinum\(.*\)`),
- // for Maximum(0)
- "mininum": regexp.MustCompile(`(?i)mininum\(.*\)`),
- // for Maximum(0)
- "default": regexp.MustCompile(`(?i)default\(.*\)`),
+ "enums": regexp.MustCompile(`(?i)\s+enums\(.*\)`),
+ // for maximum(0)
+ "maximum": regexp.MustCompile(`(?i)\s+maxinum|maximum\(.*\)`),
+ // for minimum(0)
+ "minimum": regexp.MustCompile(`(?i)\s+mininum|minimum\(.*\)`),
+ // for default(0)
+ "default": regexp.MustCompile(`(?i)\s+default\(.*\)`),
// for minlength(0)
- "minlength": regexp.MustCompile(`(?i)minlength\(.*\)`),
+ "minlength": regexp.MustCompile(`(?i)\s+minlength\(.*\)`),
// for maxlength(0)
- "maxlength": regexp.MustCompile(`(?i)maxlength\(.*\)`),
+ "maxlength": regexp.MustCompile(`(?i)\s+maxlength\(.*\)`),
// for format(email)
- "format": regexp.MustCompile(`(?i)format\(.*\)`),
+ "format": regexp.MustCompile(`(?i)\s+format\(.*\)`),
+ // for collectionFormat(csv)
+ "collectionFormat": regexp.MustCompile(`(?i)\s+collectionFormat\(.*\)`),
}
-func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schemaType string, param *spec.Parameter) error {
+func (operation *Operation) parseAndExtractionParamAttribute(commentLine, objectType, schemaType string, param *spec.Parameter) error {
schemaType = TransToValidSchemeType(schemaType)
for attrKey, re := range regexAttributes {
attr, err := findAttr(re, commentLine)
@@ -356,13 +343,13 @@ func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schema
if err != nil {
return err
}
- case "maxinum":
+ case "maximum":
n, err := setNumberParam(attrKey, schemaType, attr, commentLine)
if err != nil {
return err
}
param.Maximum = &n
- case "mininum":
+ case "minimum":
n, err := setNumberParam(attrKey, schemaType, attr, commentLine)
if err != nil {
return err
@@ -388,8 +375,13 @@ func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schema
param.MinLength = &n
case "format":
param.Format = attr
+ case "collectionFormat":
+ n, err := setCollectionFormatParam(attrKey, objectType, attr, commentLine)
+ if err != nil {
+ return err
+ }
+ param.CollectionFormat = n
}
-
}
return nil
}
@@ -405,7 +397,7 @@ func findAttr(re *regexp.Regexp, commentLine string) (string, error) {
}
func setStringParam(name, schemaType, attr, commentLine string) (int64, error) {
- if schemaType != "string" {
+ if schemaType != STRING {
return 0, fmt.Errorf("%s is attribute to set to a number. comment=%s got=%s", name, commentLine, schemaType)
}
n, err := strconv.ParseInt(attr, 10, 64)
@@ -416,7 +408,7 @@ func setStringParam(name, schemaType, attr, commentLine string) (int64, error) {
}
func setNumberParam(name, schemaType, attr, commentLine string) (float64, error) {
- if schemaType != "integer" && schemaType != "number" {
+ if schemaType != INTEGER && schemaType != NUMBER {
return 0, fmt.Errorf("%s is attribute to set to a number. comment=%s got=%s", name, commentLine, schemaType)
}
n, err := strconv.ParseFloat(attr, 64)
@@ -439,25 +431,32 @@ func setEnumParam(attr, schemaType string, param *spec.Parameter) error {
return nil
}
+func setCollectionFormatParam(name, schemaType, attr, commentLine string) (string, error) {
+ if schemaType != ARRAY {
+ return "", fmt.Errorf("%s is attribute to set to an array. comment=%s got=%s", name, commentLine, schemaType)
+ }
+ return TransToValidCollectionFormat(attr), nil
+}
+
// defineType enum value define the type (object and array unsupported)
func defineType(schemaType string, value string) (interface{}, error) {
schemaType = TransToValidSchemeType(schemaType)
switch schemaType {
- case "string":
+ case STRING:
return value, nil
- case "number":
+ case NUMBER:
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err)
}
return v, nil
- case "integer":
+ case INTEGER:
v, err := strconv.Atoi(value)
if err != nil {
return nil, fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err)
}
return v, nil
- case "boolean":
+ case BOOLEAN:
v, err := strconv.ParseBool(value)
if err != nil {
return nil, fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err)
@@ -604,82 +603,162 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) {
return nil, fmt.Errorf("type spec not found")
}
-var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/]+)[^"]*(.*)?`)
+var responsePattern = regexp.MustCompile(`^([\w,]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=,\[\]]+)[^"]*(.*)?`)
-// ParseResponseComment parses comment for given `response` comment string.
-func (operation *Operation) ParseResponseComment(commentLine string, astFile *ast.File) error {
- var matches []string
+//ResponseType{data1=Type1,data2=Type2}
+var combinedPattern = regexp.MustCompile(`^([\w\-\.\/\[\]]+)\{(.*)\}$`)
- if matches = responsePattern.FindStringSubmatch(commentLine); len(matches) != 5 {
- err := operation.ParseEmptyResponseComment(commentLine)
+func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) {
+ switch {
+ case refType == "interface{}":
+ return PrimitiveSchema(OBJECT), nil
+ case IsGolangPrimitiveType(refType):
+ refType = TransToValidSchemeType(refType)
+ return PrimitiveSchema(refType), nil
+ case IsPrimitiveType(refType):
+ return PrimitiveSchema(refType), nil
+ case strings.HasPrefix(refType, "[]"):
+ schema, err := operation.parseObjectSchema(refType[2:], astFile)
if err != nil {
- return operation.ParseEmptyResponseOnly(commentLine)
+ return nil, err
}
- return err
- }
+ return spec.ArrayProperty(schema), nil
+ case strings.HasPrefix(refType, "map["):
+ //ignore key type
+ idx := strings.Index(refType, "]")
+ if idx < 0 {
+ return nil, fmt.Errorf("invalid type: %s", refType)
+ }
+ refType = refType[idx+1:]
+ if refType == "interface{}" {
+ return spec.MapProperty(nil), nil
- response := spec.Response{}
+ }
+ schema, err := operation.parseObjectSchema(refType, astFile)
+ if err != nil {
+ return nil, err
+ }
+ return spec.MapProperty(schema), nil
+ case strings.Contains(refType, "{"):
+ return operation.parseCombinedObjectSchema(refType, astFile)
+ default:
+ if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
+ schema, err := operation.parser.getTypeSchema(refType, astFile, true)
+ if err != nil {
+ return nil, err
+ }
+ return schema, nil
+ }
- code, _ := strconv.Atoi(matches[1])
+ return RefSchema(refType), nil
+ }
+}
- responseDescription := strings.Trim(matches[4], "\"")
- if responseDescription == "" {
- responseDescription = http.StatusText(code)
+func (operation *Operation) parseCombinedObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) {
+ matches := combinedPattern.FindStringSubmatch(refType)
+ if len(matches) != 3 {
+ return nil, fmt.Errorf("invalid type: %s", refType)
+ }
+ refType = matches[1]
+ schema, err := operation.parseObjectSchema(refType, astFile)
+ if err != nil {
+ return nil, err
}
- response.Description = responseDescription
- schemaType := strings.Trim(matches[2], "{}")
- refType := matches[3]
+ parseFields := func(s string) []string {
+ n := 0
+ return strings.FieldsFunc(s, func(r rune) bool {
+ if r == '{' {
+ n++
+ return false
+ } else if r == '}' {
+ n--
+ return false
+ }
+ return r == ',' && n == 0
+ })
+ }
- if !IsGolangPrimitiveType(refType) {
- if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
- var err error
- if refType, _, err = operation.registerSchemaType(refType, astFile); err != nil {
- return err
+ fields := parseFields(matches[2])
+ props := map[string]spec.Schema{}
+ for _, field := range fields {
+ if matches := strings.SplitN(field, "=", 2); len(matches) == 2 {
+ schema, err := operation.parseObjectSchema(matches[1], astFile)
+ if err != nil {
+ return nil, err
}
+ props[matches[0]] = *schema
}
}
- // so we have to know all type in app
- response.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{schemaType}}}
+ if len(props) == 0 {
+ return schema, nil
+ }
+ return spec.ComposedSchema(*schema, spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{OBJECT},
+ Properties: props,
+ },
+ }), nil
+}
- if schemaType == "object" {
- response.Schema.SchemaProps = spec.SchemaProps{}
- response.Schema.Ref = spec.Ref{
- Ref: jsonreference.MustCreateRef("#/definitions/" + refType),
+func (operation *Operation) parseAPIObjectSchema(schemaType, refType string, astFile *ast.File) (*spec.Schema, error) {
+ switch schemaType {
+ case OBJECT:
+ if !strings.HasPrefix(refType, "[]") {
+ return operation.parseObjectSchema(refType, astFile)
}
+ refType = refType[2:]
+ fallthrough
+ case ARRAY:
+ schema, err := operation.parseObjectSchema(refType, astFile)
+ if err != nil {
+ return nil, err
+ }
+ return spec.ArrayProperty(schema), nil
+ case PRIMITIVE:
+ return PrimitiveSchema(refType), nil
+ default:
+ return PrimitiveSchema(schemaType), nil
}
+}
- if schemaType == "array" {
- refType = TransToValidSchemeType(refType)
- if IsPrimitiveType(refType) {
- response.Schema.Items = &spec.SchemaOrArray{
- Schema: &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: spec.StringOrArray{refType},
- },
- },
- }
- } else {
- response.Schema.Items = &spec.SchemaOrArray{
- Schema: &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Ref: spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)},
- },
- },
- }
+// ParseResponseComment parses comment for given `response` comment string.
+func (operation *Operation) ParseResponseComment(commentLine string, astFile *ast.File) error {
+ var matches []string
+
+ if matches = responsePattern.FindStringSubmatch(commentLine); len(matches) != 5 {
+ err := operation.ParseEmptyResponseComment(commentLine)
+ if err != nil {
+ return operation.ParseEmptyResponseOnly(commentLine)
}
+ return err
}
- if operation.Responses == nil {
- operation.Responses = &spec.Responses{
- ResponsesProps: spec.ResponsesProps{
- StatusCodeResponses: make(map[int]spec.Response),
- },
- }
+ responseDescription := strings.Trim(matches[4], "\"")
+ schemaType := strings.Trim(matches[2], "{}")
+ refType := matches[3]
+ schema, err := operation.parseAPIObjectSchema(schemaType, refType, astFile)
+ if err != nil {
+ return err
}
- operation.Responses.StatusCodeResponses[code] = response
+ for _, codeStr := range strings.Split(matches[1], ",") {
+ if strings.EqualFold(codeStr, "default") {
+ operation.DefaultResponse().Schema = schema
+ operation.DefaultResponse().Description = responseDescription
+ } else if code, err := strconv.Atoi(codeStr); err == nil {
+ resp := &spec.Response{
+ ResponseProps: spec.ResponseProps{Schema: schema, Description: responseDescription},
+ }
+ if resp.Description == "" {
+ resp.Description = http.StatusText(code)
+ }
+ operation.AddResponse(code, resp)
+ } else {
+ return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
+ }
+ }
return nil
}
@@ -692,45 +771,60 @@ func (operation *Operation) ParseResponseHeaderComment(commentLine string, astFi
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),
- },
+ headerKey := matches[3]
+ description := strings.Trim(matches[4], "\"")
+ header := spec.Header{}
+ header.Description = description
+ header.Type = schemaType
+
+ if strings.EqualFold(matches[1], "all") {
+ if operation.Responses.Default != nil {
+ if operation.Responses.Default.Headers == nil {
+ operation.Responses.Default.Headers = make(map[string]spec.Header)
+ }
+ operation.Responses.Default.Headers[headerKey] = header
}
+ if operation.Responses != nil && operation.Responses.StatusCodeResponses != nil {
+ for code, response := range operation.Responses.StatusCodeResponses {
+ if response.Headers == nil {
+ response.Headers = make(map[string]spec.Header)
+ }
+ response.Headers[headerKey] = header
+ operation.Responses.StatusCodeResponses[code] = response
+ }
+ }
+ return nil
}
- response, responseExist := operation.Responses.StatusCodeResponses[code]
- if responseExist {
- header := spec.Header{}
- header.Description = responseDescription
- header.Type = schemaType
+ for _, codeStr := range strings.Split(matches[1], ",") {
+ if strings.EqualFold(codeStr, "default") {
+ if operation.Responses.Default != nil {
+ if operation.Responses.Default.Headers == nil {
+ operation.Responses.Default.Headers = make(map[string]spec.Header)
+ }
+ operation.Responses.Default.Headers[headerKey] = header
+ }
+ } else if code, err := strconv.Atoi(codeStr); err == nil {
+ if operation.Responses != nil && operation.Responses.StatusCodeResponses != nil {
+ if response, responseExist := operation.Responses.StatusCodeResponses[code]; responseExist {
+ if response.Headers == nil {
+ response.Headers = make(map[string]spec.Header)
+ }
+ response.Headers[headerKey] = header
- if response.Headers == nil {
- response.Headers = make(map[string]spec.Header)
+ operation.Responses.StatusCodeResponses[code] = response
+ }
+ }
+ } else {
+ return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
- response.Headers[refType] = header
-
- operation.Responses.StatusCodeResponses[code] = response
}
return nil
}
-var emptyResponsePattern = regexp.MustCompile(`([\d]+)[\s]+"(.*)"`)
+var emptyResponsePattern = regexp.MustCompile(`([\w,]+)[\s]+"(.*)"`)
// ParseEmptyResponseComment parse only comment out status code and description,eg: @Success 200 "it's ok"
func (operation *Operation) ParseEmptyResponseComment(commentLine string) error {
@@ -740,33 +834,49 @@ func (operation *Operation) ParseEmptyResponseComment(commentLine string) error
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
- response := spec.Response{}
-
- code, _ := strconv.Atoi(matches[1])
-
- response.Description = strings.Trim(matches[2], "")
-
- if operation.Responses == nil {
- operation.Responses = &spec.Responses{
- ResponsesProps: spec.ResponsesProps{
- StatusCodeResponses: make(map[int]spec.Response),
- },
+ responseDescription := strings.Trim(matches[2], "\"")
+ for _, codeStr := range strings.Split(matches[1], ",") {
+ if strings.EqualFold(codeStr, "default") {
+ operation.DefaultResponse().Description = responseDescription
+ } else if code, err := strconv.Atoi(codeStr); err == nil {
+ var response spec.Response
+ response.Description = responseDescription
+ operation.AddResponse(code, &response)
+ } else {
+ return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
}
- operation.Responses.StatusCodeResponses[code] = response
-
return nil
}
//ParseEmptyResponseOnly parse only comment out status code ,eg: @Success 200
func (operation *Operation) ParseEmptyResponseOnly(commentLine string) error {
- response := spec.Response{}
+ for _, codeStr := range strings.Split(commentLine, ",") {
+ if strings.EqualFold(codeStr, "default") {
+ _ = operation.DefaultResponse()
+ } else if code, err := strconv.Atoi(codeStr); err == nil {
+ var response spec.Response
+ //response.Description = http.StatusText(code)
+ operation.AddResponse(code, &response)
+ } else {
+ return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
+ }
+ }
- code, err := strconv.Atoi(commentLine)
- if err != nil {
- return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
+ return nil
+}
+
+//DefaultResponse return the default response member pointer
+func (operation *Operation) DefaultResponse() *spec.Response {
+ if operation.Responses.Default == nil {
+ operation.Responses.Default = &spec.Response{}
}
+ return operation.Responses.Default
+}
+
+//AddResponse add a response for a code
+func (operation *Operation) AddResponse(code int, response *spec.Response) {
if operation.Responses == nil {
operation.Responses = &spec.Responses{
ResponsesProps: spec.ResponsesProps{
@@ -774,10 +884,7 @@ func (operation *Operation) ParseEmptyResponseOnly(commentLine string) error {
},
}
}
-
- operation.Responses.StatusCodeResponses[code] = response
-
- return nil
+ operation.Responses.StatusCodeResponses[code] = *response
}
// createParameter returns swagger spec.Parameter for gived paramType, description, paramName, schemaType, required
@@ -808,3 +915,31 @@ func createParameter(paramType, description, paramName, schemaType string, requi
}
return parameter
}
+
+func getCodeExampleForSummary(summaryName string, dirPath string) ([]byte, error) {
+ filesInfos, err := ioutil.ReadDir(dirPath)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, fileInfo := range filesInfos {
+ if fileInfo.IsDir() {
+ continue
+ }
+ fileName := fileInfo.Name()
+
+ if !strings.Contains(fileName, ".json") {
+ continue
+ }
+
+ if strings.Contains(fileName, summaryName) {
+ fullPath := filepath.Join(dirPath, fileName)
+ commentInfo, err := ioutil.ReadFile(fullPath)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to read code example file %s error: %s ", fullPath, err)
+ }
+ return commentInfo, nil
+ }
+ }
+ return nil, fmt.Errorf("Unable to find code example file for tag %s in the given directory", summaryName)
+}
diff --git a/operation_test.go b/operation_test.go
index ee58db0f6..1e982d593 100644
--- a/operation_test.go
+++ b/operation_test.go
@@ -2,7 +2,6 @@ package swag
import (
"encoding/json"
- "go/ast"
goparser "go/parser"
"go/token"
"testing"
@@ -12,7 +11,7 @@ import (
)
func TestParseEmptyComment(t *testing.T) {
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment("//", nil)
assert.NoError(t, err)
@@ -27,7 +26,7 @@ func TestParseTagsComment(t *testing.T) {
]
}`
comment := `/@Tags pet, store,user`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation, "", " ")
@@ -54,7 +53,7 @@ func TestParseAcceptComment(t *testing.T) {
]
}`
comment := `/@Accept json,xml,plain,html,mpfd,x-www-form-urlencoded,json-api,json-stream,octet-stream,png,jpeg,gif,application/xhtml+xml,application/health+json`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation, "", " ")
@@ -64,7 +63,7 @@ func TestParseAcceptComment(t *testing.T) {
func TestParseAcceptCommentErr(t *testing.T) {
comment := `/@Accept unknown`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
@@ -104,7 +103,7 @@ func TestParseProduceCommentErr(t *testing.T) {
func TestParseRouterComment(t *testing.T) {
comment := `/@Router /customer/get-wishlist/{wishlist_id} [get]`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, "/customer/get-wishlist/{wishlist_id}", operation.Path)
@@ -113,7 +112,7 @@ func TestParseRouterComment(t *testing.T) {
func TestParseRouterOnlySlash(t *testing.T) {
comment := `// @Router / [get]`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, "/", operation.Path)
@@ -122,7 +121,7 @@ func TestParseRouterOnlySlash(t *testing.T) {
func TestParseRouterCommentWithPlusSign(t *testing.T) {
comment := `/@Router /customer/get-wishlist/{proxy+} [post]`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, "/customer/get-wishlist/{proxy+}", operation.Path)
@@ -131,7 +130,7 @@ func TestParseRouterCommentWithPlusSign(t *testing.T) {
func TestParseRouterCommentWithColonSign(t *testing.T) {
comment := `/@Router /customer/get-wishlist/{wishlist_id}:move [post]`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, "/customer/get-wishlist/{wishlist_id}:move", operation.Path)
@@ -140,32 +139,29 @@ func TestParseRouterCommentWithColonSign(t *testing.T) {
func TestParseRouterCommentNoColonSignAtPathStartErr(t *testing.T) {
comment := `/@Router :customer/get-wishlist/{wishlist_id}:move [post]`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseRouterCommentMethodSeparationErr(t *testing.T) {
comment := `/@Router /api/{id}|,*[get`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseRouterCommentMethodMissingErr(t *testing.T) {
comment := `/@Router /customer/get-wishlist/{wishlist_id}`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseResponseCommentWithObjectType(t *testing.T) {
comment := `@Success 200 {object} model.OrderRow "Error message, if code != 200`
- operation := NewOperation()
- operation.parser = New()
-
- operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec)
- operation.parser.TypeDefinitions["model"]["OrderRow"] = &ast.TypeSpec{}
+ operation := NewOperation(nil)
+ operation.parser.addTestType("model.OrderRow")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -188,18 +184,405 @@ func TestParseResponseCommentWithObjectType(t *testing.T) {
assert.Equal(t, expected, string(b))
}
+func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) {
+ comment := `@Success 200 {object} model.CommonHeader{data=string,data2=int} "Error message, if code != 200`
+ operation := NewOperation(nil)
+
+ operation.parser.addTestType("model.CommonHeader")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ response := operation.Responses.StatusCodeResponses[200]
+ assert.Equal(t, `Error message, if code != 200`, response.Description)
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "Error message, if code != 200",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "string"
+ },
+ "data2": {
+ "type": "integer"
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) {
+ comment := `@Success 200 {object} model.CommonHeader{data=[]string,data2=[]int} "Error message, if code != 200`
+ operation := NewOperation(nil)
+
+ operation.parser.addTestType("model.CommonHeader")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ response := operation.Responses.StatusCodeResponses[200]
+ assert.Equal(t, `Error message, if code != 200`, response.Description)
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "Error message, if code != 200",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "data2": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseResponseCommentWithNestedObjectType(t *testing.T) {
+ comment := `@Success 200 {object} model.CommonHeader{data=model.Payload,data2=model.Payload2} "Error message, if code != 200`
+ operation := NewOperation(nil)
+ operation.parser.addTestType("model.CommonHeader")
+ operation.parser.addTestType("model.Payload")
+ operation.parser.addTestType("model.Payload2")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ response := operation.Responses.StatusCodeResponses[200]
+ assert.Equal(t, `Error message, if code != 200`, response.Description)
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "Error message, if code != 200",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/definitions/model.Payload"
+ },
+ "data2": {
+ "$ref": "#/definitions/model.Payload2"
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) {
+ comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload,data2=[]model.Payload2} "Error message, if code != 200`
+ operation := NewOperation(nil)
+
+ operation.parser.addTestType("model.CommonHeader")
+ operation.parser.addTestType("model.Payload")
+ operation.parser.addTestType("model.Payload2")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ response := operation.Responses.StatusCodeResponses[200]
+ assert.Equal(t, `Error message, if code != 200`, response.Description)
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "Error message, if code != 200",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/model.Payload"
+ }
+ },
+ "data2": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/model.Payload2"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseResponseCommentWithNestedFields(t *testing.T) {
+ comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload,data4=[]model.Payload} "Error message, if code != 200`
+ operation := NewOperation(nil)
+
+ operation.parser.addTestType("model.CommonHeader")
+ operation.parser.addTestType("model.Payload")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ response := operation.Responses.StatusCodeResponses[200]
+ assert.Equal(t, `Error message, if code != 200`, response.Description)
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "Error message, if code != 200",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data1": {
+ "type": "integer"
+ },
+ "data2": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "data3": {
+ "$ref": "#/definitions/model.Payload"
+ },
+ "data4": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/model.Payload"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseResponseCommentWithDeepNestedFields(t *testing.T) {
+ comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload{data1=int,data2=model.DeepPayload},data4=[]model.Payload{data1=[]int,data2=[]model.DeepPayload}} "Error message, if code != 200`
+ operation := NewOperation(nil)
+
+ operation.parser.addTestType("model.CommonHeader")
+ operation.parser.addTestType("model.Payload")
+ operation.parser.addTestType("model.DeepPayload")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ response := operation.Responses.StatusCodeResponses[200]
+ assert.Equal(t, `Error message, if code != 200`, response.Description)
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "Error message, if code != 200",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data1": {
+ "type": "integer"
+ },
+ "data2": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "data3": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.Payload"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data1": {
+ "type": "integer"
+ },
+ "data2": {
+ "$ref": "#/definitions/model.DeepPayload"
+ }
+ }
+ }
+ ]
+ },
+ "data4": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.Payload"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data1": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "data2": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/model.DeepPayload"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseResponseCommentWithNestedArrayMapFields(t *testing.T) {
+ comment := `@Success 200 {object} []map[string]model.CommonHeader{data1=[]map[string]model.Payload,data2=map[string][]int} "Error message, if code != 200`
+ operation := NewOperation(nil)
+
+ operation.parser.addTestType("model.CommonHeader")
+ operation.parser.addTestType("model.Payload")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ response := operation.Responses.StatusCodeResponses[200]
+ assert.Equal(t, `Error message, if code != 200`, response.Description)
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "Error message, if code != 200",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data1": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/model.Payload"
+ }
+ }
+ },
+ "data2": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
func TestParseResponseCommentWithObjectTypeInSameFile(t *testing.T) {
comment := `@Success 200 {object} testOwner "Error message, if code != 200"`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
- operation.parser.TypeDefinitions["swag"] = make(map[string]*ast.TypeSpec)
- operation.parser.TypeDefinitions["swag"]["testOwner"] = &ast.TypeSpec{}
+ operation.parser.addTestType("swag.testOwner")
fset := token.NewFileSet()
astFile, err := goparser.ParseFile(fset, "operation_test.go", `package swag
type testOwner struct {
-
+
}
`, goparser.ParseComments)
assert.NoError(t, err)
@@ -231,11 +614,9 @@ func TestParseResponseCommentWithObjectTypeAnonymousField(t *testing.T) {
func TestParseResponseCommentWithObjectTypeErr(t *testing.T) {
comment := `@Success 200 {object} model.OrderRow "Error message, if code != 200"`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
- operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec)
- operation.parser.TypeDefinitions["model"]["notexist"] = &ast.TypeSpec{}
+ operation.parser.addTestType("model.notexist")
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
@@ -243,7 +624,8 @@ func TestParseResponseCommentWithObjectTypeErr(t *testing.T) {
func TestParseResponseCommentWithArrayType(t *testing.T) {
comment := `@Success 200 {array} model.OrderRow "Error message, if code != 200`
- operation := NewOperation()
+ operation := NewOperation(nil)
+ operation.parser.addTestType("model.OrderRow")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
@@ -271,7 +653,7 @@ func TestParseResponseCommentWithArrayType(t *testing.T) {
func TestParseResponseCommentWithBasicType(t *testing.T) {
comment := `@Success 200 {string} string "it's ok'"`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
@@ -289,9 +671,59 @@ func TestParseResponseCommentWithBasicType(t *testing.T) {
assert.Equal(t, expected, string(b))
}
+func TestParseResponseCommentWithBasicTypeAndCodes(t *testing.T) {
+ comment := `@Success 200,201,default {string} string "it's ok"`
+ operation := NewOperation(nil)
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err, "ParseComment should not fail")
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "it's ok",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "201": {
+ "description": "it's ok",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "default": {
+ "description": "it's ok",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
func TestParseEmptyResponseComment(t *testing.T) {
- comment := `@Success 200 "it's ok"`
- operation := NewOperation()
+ comment := `@Success 200 "it is ok"`
+ operation := NewOperation(nil)
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err, "ParseComment should not fail")
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "it is ok"
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseEmptyResponseCommentWithCodes(t *testing.T) {
+ comment := `@Success 200,201,default "it is ok"`
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
@@ -300,7 +732,13 @@ func TestParseEmptyResponseComment(t *testing.T) {
expected := `{
"responses": {
"200": {
- "description": "it's ok"
+ "description": "it is ok"
+ },
+ "201": {
+ "description": "it is ok"
+ },
+ "default": {
+ "description": "it is ok"
}
}
}`
@@ -309,7 +747,7 @@ func TestParseEmptyResponseComment(t *testing.T) {
func TestParseResponseCommentWithHeader(t *testing.T) {
comment := `@Success 200 "it's ok"`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
@@ -338,12 +776,97 @@ func TestParseResponseCommentWithHeader(t *testing.T) {
comment = `@Header 200 "Mallformed"`
err = operation.ParseComment(comment, nil)
assert.Error(t, err, "ParseComment should not fail")
+}
+
+func TestParseResponseCommentWithHeaderForCodes(t *testing.T) {
+ operation := NewOperation(nil)
+
+ comment := `@Success 200,201,default "it's ok"`
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err, "ParseComment should not fail")
+
+ comment = `@Header 200,201,default {string} Token "qwerty"`
+ err = operation.ParseComment(comment, nil)
+ assert.NoError(t, err, "ParseComment should not fail")
+
+ comment = `@Header all {string} Token2 "qwerty"`
+ err = operation.ParseComment(comment, nil)
+ assert.NoError(t, err, "ParseComment should not fail")
+
+ b, err := json.MarshalIndent(operation, "", " ")
+ assert.NoError(t, err)
+ expected := `{
+ "responses": {
+ "200": {
+ "description": "it's ok",
+ "headers": {
+ "Token": {
+ "type": "string",
+ "description": "qwerty"
+ },
+ "Token2": {
+ "type": "string",
+ "description": "qwerty"
+ }
+ }
+ },
+ "201": {
+ "description": "it's ok",
+ "headers": {
+ "Token": {
+ "type": "string",
+ "description": "qwerty"
+ },
+ "Token2": {
+ "type": "string",
+ "description": "qwerty"
+ }
+ }
+ },
+ "default": {
+ "description": "it's ok",
+ "headers": {
+ "Token": {
+ "type": "string",
+ "description": "qwerty"
+ },
+ "Token2": {
+ "type": "string",
+ "description": "qwerty"
+ }
+ }
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+
+ comment = `@Header 200 "Mallformed"`
+ err = operation.ParseComment(comment, nil)
+ assert.Error(t, err, "ParseComment should not fail")
}
func TestParseEmptyResponseOnlyCode(t *testing.T) {
comment := `@Success 200`
- operation := NewOperation()
+ operation := NewOperation(nil)
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err, "ParseComment should not fail")
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "responses": {
+ "200": {
+ "description": ""
+ }
+ }
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseEmptyResponseOnlyCodes(t *testing.T) {
+ comment := `@Success 200,201,default`
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
@@ -351,24 +874,40 @@ func TestParseEmptyResponseOnlyCode(t *testing.T) {
expected := `{
"responses": {
- "200": {}
+ "200": {
+ "description": ""
+ },
+ "201": {
+ "description": ""
+ },
+ "default": {
+ "description": ""
+ }
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentParamMissing(t *testing.T) {
- operation := NewOperation()
+ operation := NewOperation(nil)
- paramLenErrComment := `@Success notIntCode {string}`
+ paramLenErrComment := `@Success notIntCode`
paramLenErr := operation.ParseComment(paramLenErrComment, nil)
- assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode {string}"`)
+ assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode"`)
+
+ paramLenErrComment = `@Success notIntCode {string} string "it ok"`
+ paramLenErr = operation.ParseComment(paramLenErrComment, nil)
+ assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode {string} string "it ok""`)
+
+ paramLenErrComment = `@Success notIntCode "it ok"`
+ paramLenErr = operation.ParseComment(paramLenErrComment, nil)
+ assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode "it ok""`)
}
// Test ParseParamComment
func TestParseParamCommentByPathType(t *testing.T) {
comment := `@Param some_id path int true "Some ID"`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -390,7 +929,7 @@ func TestParseParamCommentByPathType(t *testing.T) {
// Test ParseParamComment Query Params
func TestParseParamCommentBodyArray(t *testing.T) {
comment := `@Param names body []string true "Users List"`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -417,7 +956,32 @@ func TestParseParamCommentBodyArray(t *testing.T) {
// Test ParseParamComment Query Params
func TestParseParamCommentQueryArray(t *testing.T) {
comment := `@Param names query []string true "Users List"`
- operation := NewOperation()
+ operation := NewOperation(nil)
+ err := operation.ParseComment(comment, nil)
+
+ assert.NoError(t, err)
+ b, _ := json.MarshalIndent(operation, "", " ")
+ expected := `{
+ "parameters": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Users List",
+ "name": "names",
+ "in": "query",
+ "required": true
+ }
+ ]
+}`
+ assert.Equal(t, expected, string(b))
+}
+
+// Test ParseParamComment Query Params
+func TestParseParamCommentQueryArrayFormat(t *testing.T) {
+ comment := `@Param names query []string true "Users List" collectionFormat(multi)`
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -429,6 +993,7 @@ func TestParseParamCommentQueryArray(t *testing.T) {
"items": {
"type": "string"
},
+ "collectionFormat": "multi",
"description": "Users List",
"name": "names",
"in": "query",
@@ -441,7 +1006,7 @@ func TestParseParamCommentQueryArray(t *testing.T) {
func TestParseParamCommentByID(t *testing.T) {
comment := `@Param unsafe_id[lte] query int true "Unsafe query param"`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -462,7 +1027,7 @@ func TestParseParamCommentByID(t *testing.T) {
func TestParseParamCommentByQueryType(t *testing.T) {
comment := `@Param some_id query int true "Some ID"`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -483,11 +1048,9 @@ func TestParseParamCommentByQueryType(t *testing.T) {
func TestParseParamCommentByBodyType(t *testing.T) {
comment := `@Param some_id body model.OrderRow true "Some ID"`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
- operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec)
- operation.parser.TypeDefinitions["model"]["OrderRow"] = &ast.TypeSpec{}
+ operation.parser.addTestType("model.OrderRow")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -508,10 +1071,54 @@ func TestParseParamCommentByBodyType(t *testing.T) {
assert.Equal(t, expected, string(b))
}
+func TestParseParamCommentByBodyTypeWithDeepNestedFields(t *testing.T) {
+ comment := `@Param body body model.CommonHeader{data=string,data2=int} true "test deep"`
+ operation := NewOperation(nil)
+
+ operation.parser.addTestType("model.CommonHeader")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+ assert.Len(t, operation.Parameters, 1)
+ assert.Equal(t, "test deep", operation.Parameters[0].Description)
+ assert.True(t, operation.Parameters[0].Required)
+
+ b, err := json.MarshalIndent(operation, "", " ")
+ assert.NoError(t, err)
+ expected := `{
+ "parameters": [
+ {
+ "description": "test deep",
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "string"
+ },
+ "data2": {
+ "type": "integer"
+ }
+ }
+ }
+ ]
+ }
+ }
+ ]
+}`
+ assert.Equal(t, expected, string(b))
+}
+
func TestParseParamCommentByBodyTypeArrayOfPrimitiveGo(t *testing.T) {
comment := `@Param some_id body []int true "Some ID"`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -535,13 +1142,57 @@ func TestParseParamCommentByBodyTypeArrayOfPrimitiveGo(t *testing.T) {
assert.Equal(t, expected, string(b))
}
+func TestParseParamCommentByBodyTypeArrayOfPrimitiveGoWithDeepNestedFields(t *testing.T) {
+ comment := `@Param body body []model.CommonHeader{data=string,data2=int} true "test deep"`
+ operation := NewOperation(nil)
+ operation.parser.addTestType("model.CommonHeader")
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+ assert.Len(t, operation.Parameters, 1)
+ assert.Equal(t, "test deep", operation.Parameters[0].Description)
+ assert.True(t, operation.Parameters[0].Required)
+
+ b, err := json.MarshalIndent(operation, "", " ")
+ assert.NoError(t, err)
+ expected := `{
+ "parameters": [
+ {
+ "description": "test deep",
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/model.CommonHeader"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "string"
+ },
+ "data2": {
+ "type": "integer"
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+}`
+ assert.Equal(t, expected, string(b))
+}
+
func TestParseParamCommentByBodyTypeErr(t *testing.T) {
comment := `@Param some_id body model.OrderRow true "Some ID"`
- operation := NewOperation()
- operation.parser = New()
-
- operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec)
- operation.parser.TypeDefinitions["model"]["notexist"] = &ast.TypeSpec{}
+ operation := NewOperation(nil)
+ operation.parser.addTestType("model.notexist")
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
@@ -549,8 +1200,7 @@ func TestParseParamCommentByBodyTypeErr(t *testing.T) {
func TestParseParamCommentByFormDataType(t *testing.T) {
comment := `@Param file formData file true "this is a test file"`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -572,8 +1222,7 @@ func TestParseParamCommentByFormDataType(t *testing.T) {
func TestParseParamCommentByFormDataTypeUint64(t *testing.T) {
comment := `@Param file formData uint64 true "this is a test file"`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -595,7 +1244,7 @@ func TestParseParamCommentByFormDataTypeUint64(t *testing.T) {
func TestParseParamCommentByNotSupportedType(t *testing.T) {
comment := `@Param some_id not_supported int true "Some ID"`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
@@ -603,7 +1252,7 @@ func TestParseParamCommentByNotSupportedType(t *testing.T) {
func TestParseParamCommentNotMatch(t *testing.T) {
comment := `@Param some_id body mock true`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
@@ -611,7 +1260,7 @@ func TestParseParamCommentNotMatch(t *testing.T) {
func TestParseParamCommentByEnums(t *testing.T) {
comment := `@Param some_id query string true "Some ID" Enums(A, B, C)`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -635,7 +1284,7 @@ func TestParseParamCommentByEnums(t *testing.T) {
assert.Equal(t, expected, string(b))
comment = `@Param some_id query int true "Some ID" Enums(1, 2, 3)`
- operation = NewOperation()
+ operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -659,7 +1308,7 @@ func TestParseParamCommentByEnums(t *testing.T) {
assert.Equal(t, expected, string(b))
comment = `@Param some_id query number true "Some ID" Enums(1.1, 2.2, 3.3)`
- operation = NewOperation()
+ operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -683,7 +1332,7 @@ func TestParseParamCommentByEnums(t *testing.T) {
assert.Equal(t, expected, string(b))
comment = `@Param some_id query bool true "Some ID" Enums(true, false)`
- operation = NewOperation()
+ operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -705,7 +1354,7 @@ func TestParseParamCommentByEnums(t *testing.T) {
}`
assert.Equal(t, expected, string(b))
- operation = NewOperation()
+ operation = NewOperation(nil)
comment = `@Param some_id query int true "Some ID" Enums(A, B, C)`
assert.Error(t, operation.ParseComment(comment, nil))
@@ -722,7 +1371,7 @@ func TestParseParamCommentByEnums(t *testing.T) {
func TestParseParamCommentByMaxLength(t *testing.T) {
comment := `@Param some_id query string true "Some ID" MaxLength(10)`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -750,7 +1399,7 @@ func TestParseParamCommentByMaxLength(t *testing.T) {
func TestParseParamCommentByMinLength(t *testing.T) {
comment := `@Param some_id query string true "Some ID" MinLength(10)`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -776,9 +1425,9 @@ func TestParseParamCommentByMinLength(t *testing.T) {
assert.Error(t, operation.ParseComment(comment, nil))
}
-func TestParseParamCommentByMininum(t *testing.T) {
- comment := `@Param some_id query int true "Some ID" Mininum(10)`
- operation := NewOperation()
+func TestParseParamCommentByMinimum(t *testing.T) {
+ comment := `@Param some_id query int true "Some ID" Minimum(10)`
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -797,16 +1446,19 @@ func TestParseParamCommentByMininum(t *testing.T) {
}`
assert.Equal(t, expected, string(b))
- comment = `@Param some_id query string true "Some ID" Mininum(10)`
+ comment = `@Param some_id query int true "Some ID" Mininum(10)`
+ assert.NoError(t, operation.ParseComment(comment, nil))
+
+ comment = `@Param some_id query string true "Some ID" Minimum(10)`
assert.Error(t, operation.ParseComment(comment, nil))
- comment = `@Param some_id query integer true "Some ID" Mininum(Goopher)`
+ comment = `@Param some_id query integer true "Some ID" Minimum(Goopher)`
assert.Error(t, operation.ParseComment(comment, nil))
}
-func TestParseParamCommentByMaxinum(t *testing.T) {
- comment := `@Param some_id query int true "Some ID" Maxinum(10)`
- operation := NewOperation()
+func TestParseParamCommentByMaximum(t *testing.T) {
+ comment := `@Param some_id query int true "Some ID" Maximum(10)`
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -825,17 +1477,20 @@ func TestParseParamCommentByMaxinum(t *testing.T) {
}`
assert.Equal(t, expected, string(b))
- comment = `@Param some_id query string true "Some ID" Maxinum(10)`
+ comment = `@Param some_id query int true "Some ID" Maxinum(10)`
+ assert.NoError(t, operation.ParseComment(comment, nil))
+
+ comment = `@Param some_id query string true "Some ID" Maximum(10)`
assert.Error(t, operation.ParseComment(comment, nil))
- comment = `@Param some_id query integer true "Some ID" Maxinum(Goopher)`
+ comment = `@Param some_id query integer true "Some ID" Maximum(Goopher)`
assert.Error(t, operation.ParseComment(comment, nil))
}
func TestParseParamCommentByDefault(t *testing.T) {
comment := `@Param some_id query int true "Some ID" Default(10)`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -857,7 +1512,7 @@ func TestParseParamCommentByDefault(t *testing.T) {
func TestParseIdComment(t *testing.T) {
comment := `@Id myOperationId`
- operation := NewOperation()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -884,8 +1539,8 @@ func TestFindTypeDefInvalidPkg(t *testing.T) {
func TestParseSecurityComment(t *testing.T) {
comment := `@Security OAuth2Implicit[read, write]`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
+
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -905,8 +1560,7 @@ func TestParseSecurityComment(t *testing.T) {
func TestParseMultiDescription(t *testing.T) {
comment := `@Description line one`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -927,8 +1581,7 @@ func TestParseMultiDescription(t *testing.T) {
func TestParseSummary(t *testing.T) {
comment := `@summary line one`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -940,8 +1593,7 @@ func TestParseSummary(t *testing.T) {
func TestParseDeprecationDescription(t *testing.T) {
comment := `@Deprecated`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -951,27 +1603,11 @@ func TestParseDeprecationDescription(t *testing.T) {
}
}
-func TestRegisterSchemaType(t *testing.T) {
- operation := NewOperation()
-
- fset := token.NewFileSet()
- astFile, err := goparser.ParseFile(fset, "main.go", `package main
- import "timer"
-`, goparser.ParseComments)
-
- assert.NoError(t, err)
-
- operation.parser = New()
- _, _, err = operation.registerSchemaType("timer.Location", astFile)
- assert.Error(t, err)
-}
-
func TestParseExtentions(t *testing.T) {
// Fail if there are no args for attributes.
{
comment := `@x-amazon-apigateway-integration`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.EqualError(t, err, "annotation @x-amazon-apigateway-integration need a value")
@@ -980,8 +1616,7 @@ func TestParseExtentions(t *testing.T) {
// Fail if args of attributes are broken.
{
comment := `@x-amazon-apigateway-integration ["broken"}]`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.EqualError(t, err, "annotation @x-amazon-apigateway-integration need a valid json value")
@@ -990,8 +1625,7 @@ func TestParseExtentions(t *testing.T) {
// OK
{
comment := `@x-amazon-apigateway-integration {"uri": "${some_arn}", "passthroughBehavior": "when_no_match", "httpMethod": "POST", "type": "aws_proxy"}`
- operation := NewOperation()
- operation.parser = New()
+ operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
@@ -1007,4 +1641,60 @@ func TestParseExtentions(t *testing.T) {
b, _ := json.MarshalIndent(operation, "", " ")
assert.Equal(t, expected, string(b))
}
+
+ // Test x-tagGroups
+ {
+ comment := `@x-tagGroups [{"name":"Natural Persons","tags":["Person","PersonRisk","PersonDocuments"]}]`
+ operation := NewOperation(nil)
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err)
+
+ expected := `{
+ "x-tagGroups": [
+ {
+ "name": "Natural Persons",
+ "tags": [
+ "Person",
+ "PersonRisk",
+ "PersonDocuments"
+ ]
+ }
+ ]
+}`
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+ assert.Equal(t, expected, string(b))
+ }
+}
+
+func TestParseCodeSamples(t *testing.T) {
+ t.Run("Find sample by file", func(t *testing.T) {
+ comment := `@x-codeSamples file`
+ operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples"))
+ operation.Summary = "example"
+
+ err := operation.ParseComment(comment, nil)
+ assert.NoError(t, err, "no error should be thrown")
+
+ b, _ := json.MarshalIndent(operation, "", " ")
+
+ expected := `{
+ "summary": "example",
+ "x-codeSamples": {
+ "lang": "JavaScript",
+ "source": "console.log('Hello World');"
+ }
+}`
+ assert.Equal(t, expected, string(b))
+ })
+
+ t.Run("Example file not found", func(t *testing.T) {
+ comment := `@x-codeSamples file`
+ operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples"))
+ operation.Summary = "exampel"
+
+ err := operation.ParseComment(comment, nil)
+ assert.Error(t, err, "error was expected, as file does not exist")
+ })
}
diff --git a/packages.go b/packages.go
new file mode 100644
index 000000000..29fceb9e2
--- /dev/null
+++ b/packages.go
@@ -0,0 +1,234 @@
+package swag
+
+import (
+ "go/ast"
+ "go/token"
+ "strings"
+)
+
+//PackagesDefinitions map[package import path]*PackageDefinitions
+type PackagesDefinitions struct {
+ files map[*ast.File]*AstFileInfo
+ packages map[string]*PackageDefinitions
+ uniqueDefinitions map[string]*TypeSpecDef
+}
+
+//NewPackagesDefinitions create object PackagesDefinitions
+func NewPackagesDefinitions() *PackagesDefinitions {
+ return &PackagesDefinitions{
+ files: make(map[*ast.File]*AstFileInfo),
+ packages: make(map[string]*PackageDefinitions),
+ uniqueDefinitions: make(map[string]*TypeSpecDef),
+ }
+}
+
+//CollectAstFile collect ast.file
+func (pkgs *PackagesDefinitions) CollectAstFile(packageDir, path string, astFile *ast.File) {
+ if pkgs.files == nil {
+ pkgs.files = make(map[*ast.File]*AstFileInfo)
+ }
+
+ pkgs.files[astFile] = &AstFileInfo{
+ File: astFile,
+ Path: path,
+ PackagePath: packageDir,
+ }
+
+ if len(packageDir) == 0 {
+ return
+ }
+
+ if pkgs.packages == nil {
+ pkgs.packages = make(map[string]*PackageDefinitions)
+ }
+
+ if pd, ok := pkgs.packages[packageDir]; ok {
+ pd.Files[path] = astFile
+ } else {
+ pkgs.packages[packageDir] = &PackageDefinitions{
+ Name: astFile.Name.Name,
+ Files: map[string]*ast.File{path: astFile},
+ TypeDefinitions: make(map[string]*TypeSpecDef),
+ }
+ }
+}
+
+//RangeFiles for range the collection of ast.File
+func (pkgs *PackagesDefinitions) RangeFiles(handle func(filename string, file *ast.File) error) error {
+ for file, info := range pkgs.files {
+ if err := handle(info.Path, file); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+//ParseTypes parse types
+//@Return parsed definitions
+func (pkgs *PackagesDefinitions) ParseTypes() (map[*TypeSpecDef]*Schema, error) {
+ parsedSchemas := make(map[*TypeSpecDef]*Schema)
+ for astFile, info := range pkgs.files {
+ for _, astDeclaration := range astFile.Decls {
+ if generalDeclaration, ok := astDeclaration.(*ast.GenDecl); ok && generalDeclaration.Tok == token.TYPE {
+ for _, astSpec := range generalDeclaration.Specs {
+ if typeSpec, ok := astSpec.(*ast.TypeSpec); ok {
+ typeSpecDef := &TypeSpecDef{
+ PkgPath: info.PackagePath,
+ File: astFile,
+ TypeSpec: typeSpec,
+ }
+
+ if idt, ok := typeSpec.Type.(*ast.Ident); ok && IsGolangPrimitiveType(idt.Name) {
+ parsedSchemas[typeSpecDef] = &Schema{
+ PkgPath: typeSpecDef.PkgPath,
+ Name: astFile.Name.Name,
+ Schema: PrimitiveSchema(TransToValidSchemeType(idt.Name)),
+ }
+ }
+
+ if pkgs.uniqueDefinitions == nil {
+ pkgs.uniqueDefinitions = make(map[string]*TypeSpecDef)
+ }
+
+ fullName := typeSpecDef.FullName()
+ anotherTypeDef, ok := pkgs.uniqueDefinitions[fullName]
+ if ok {
+ if typeSpecDef.PkgPath == anotherTypeDef.PkgPath {
+ continue
+ } else {
+ delete(pkgs.uniqueDefinitions, fullName)
+ }
+ } else {
+ pkgs.uniqueDefinitions[fullName] = typeSpecDef
+ }
+
+ pkgs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()] = typeSpecDef
+ }
+ }
+ }
+ }
+ }
+ return parsedSchemas, nil
+}
+
+func (pkgs *PackagesDefinitions) findTypeSpec(pkgPath string, typeName string) *TypeSpecDef {
+ if pkgs.packages != nil {
+ if pd, ok := pkgs.packages[pkgPath]; ok {
+ if typeSpec, ok := pd.TypeDefinitions[typeName]; ok {
+ return typeSpec
+ }
+ }
+ }
+ return nil
+}
+
+// findPackagePathFromImports finds out the package path of a package via ranging imports of a ast.File
+// @pkg the name of the target package
+// @file current ast.File in which to search imports
+// @return the package path of a package of @pkg
+func (pkgs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *ast.File) string {
+ if file == nil {
+ return ""
+ }
+
+ if strings.ContainsRune(pkg, '.') {
+ pkg = strings.Split(pkg, ".")[0]
+ }
+
+ hasAnonymousPkg := false
+
+ // prior to match named package
+ for _, imp := range file.Imports {
+ if imp.Name != nil {
+ if imp.Name.Name == pkg {
+ return strings.Trim(imp.Path.Value, `"`)
+ } else if imp.Name.Name == "_" {
+ hasAnonymousPkg = true
+ }
+ } else if pkgs.packages != nil {
+ path := strings.Trim(imp.Path.Value, `"`)
+ if pd, ok := pkgs.packages[path]; ok {
+ if pd.Name == pkg {
+ return path
+ }
+ }
+ }
+ }
+
+ //match unnamed package
+ if hasAnonymousPkg && pkgs.packages != nil {
+ for _, imp := range file.Imports {
+ if imp.Name == nil {
+ continue
+ }
+ if imp.Name.Name == "_" {
+ path := strings.Trim(imp.Path.Value, `"`)
+ if pd, ok := pkgs.packages[path]; ok {
+ if pd.Name == pkg {
+ return path
+ }
+ }
+ }
+ }
+ }
+ return ""
+}
+
+// FindTypeSpec finds out TypeSpecDef of a type by typeName
+// @typeName the name of the target type, if it starts with a package name, find its own package path from imports on top of @file
+// @file the ast.file in which @typeName is used
+// @pkgPath the package path of @file
+func (pkgs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File) *TypeSpecDef {
+ if IsGolangPrimitiveType(typeName) {
+ return nil
+ } else if file == nil { // for test
+ return pkgs.uniqueDefinitions[typeName]
+ }
+
+ if strings.ContainsRune(typeName, '.') {
+ parts := strings.Split(typeName, ".")
+
+ isAliasPkgName := func(file *ast.File, pkgName string) bool {
+ if file != nil && file.Imports != nil {
+ for _, pkg := range file.Imports {
+ if pkg.Name != nil && pkg.Name.Name == pkgName {
+ return true
+ }
+ }
+ }
+
+ return false
+ }
+
+ if !isAliasPkgName(file, parts[0]) {
+ if typeDef, ok := pkgs.uniqueDefinitions[typeName]; ok {
+ return typeDef
+ }
+ }
+
+ pkgPath := pkgs.findPackagePathFromImports(parts[0], file)
+ if len(pkgPath) == 0 && parts[0] == file.Name.Name {
+ pkgPath = pkgs.files[file].PackagePath
+ }
+ return pkgs.findTypeSpec(pkgPath, parts[1])
+ }
+
+ if typeDef, ok := pkgs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)]; ok {
+ return typeDef
+ }
+
+ if typeDef := pkgs.findTypeSpec(pkgs.files[file].PackagePath, typeName); typeDef != nil {
+ return typeDef
+ }
+
+ for _, imp := range file.Imports {
+ if imp.Name != nil && imp.Name.Name == "." {
+ pkgPath := strings.Trim(imp.Path.Value, `"`)
+ if typeDef := pkgs.findTypeSpec(pkgPath, typeName); typeDef != nil {
+ return typeDef
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/parser.go b/parser.go
index 16f4fb96c..856cdf800 100644
--- a/parser.go
+++ b/parser.go
@@ -2,6 +2,7 @@ package swag
import (
"encoding/json"
+ "errors"
"fmt"
"go/ast"
"go/build"
@@ -9,9 +10,9 @@ import (
"go/token"
"io/ioutil"
"net/http"
+ "net/url"
"os"
"os/exec"
- "path"
"path/filepath"
"reflect"
"sort"
@@ -20,7 +21,6 @@ import (
"unicode"
"github.com/KyleBanks/depth"
- "github.com/go-openapi/jsonreference"
"github.com/go-openapi/spec"
)
@@ -35,25 +35,39 @@ const (
SnakeCase = "snakecase"
)
+var (
+ //ErrRecursiveParseStruct recursively parsing struct
+ ErrRecursiveParseStruct = errors.New("recursively parsing struct")
+
+ //ErrFuncTypeField field type is func
+ ErrFuncTypeField = errors.New("field type is func")
+
+ // ErrFailedConvertPrimitiveType Failed to convert for swag to interpretable type
+ ErrFailedConvertPrimitiveType = errors.New("swag property: failed convert primitive type")
+)
+
// Parser implements a parser for Go source files.
type Parser struct {
// swagger represents the root document object for the API specification
swagger *spec.Swagger
- // files is a map that stores map[real_go_file_path][astFile]
- files map[string]*ast.File
+ //packages store entities of APIs, definitions, file, package path etc. and their relations
+ packages *PackagesDefinitions
+
+ //parsedSchemas store schemas which have been parsed from ast.TypeSpec
+ parsedSchemas map[*TypeSpecDef]*Schema
- // TypeDefinitions is a map that stores [package name][type name][*ast.TypeSpec]
- TypeDefinitions map[string]map[string]*ast.TypeSpec
+ //outputSchemas store schemas which will be export to swagger
+ outputSchemas map[*TypeSpecDef]*Schema
- // ImportAliases is map that stores [import name][import package name][*ast.ImportSpec]
- ImportAliases map[string]map[string]*ast.ImportSpec
+ //existSchemaNames store names of models for conflict determination
+ existSchemaNames map[string]*Schema
- // CustomPrimitiveTypes is a map that stores custom primitive types to actual golang types [type name][string]
- CustomPrimitiveTypes map[string]string
+ //toBeRenamedSchemas names of models to be renamed
+ toBeRenamedSchemas map[string]string
- // registerTypes is a map that stores [refTypeName][*ast.TypeSpec]
- registerTypes map[string]*ast.TypeSpec
+ //toBeRenamedSchemas URLs of ref models to be renamed
+ toBeRenamedRefURLs []*url.URL
PropNamingStrategy string
@@ -62,11 +76,23 @@ type Parser struct {
// ParseDependencies whether swag should be parse outside dependency folder
ParseDependency bool
+ // ParseInternal whether swag should parse internal packages
+ ParseInternal bool
+
// structStack stores full names of the structures that were already parsed or are being parsed now
- structStack []string
+ structStack []*TypeSpecDef
// markdownFileDir holds the path to the folder, where markdown files are stored
markdownFileDir string
+
+ // codeExampleFilesDir holds path to the folder, where code example files are stored
+ codeExampleFilesDir string
+
+ // collectionFormatInQuery set the default collectionFormat otherwise then 'csv' for array in query params
+ collectionFormatInQuery string
+
+ // excludes excludes dirs and files in SearchDir
+ excludes map[string]bool
}
// New creates a new Parser with default properties.
@@ -77,7 +103,10 @@ func New(options ...func(*Parser)) *Parser {
Info: &spec.Info{
InfoProps: spec.InfoProps{
Contact: &spec.ContactInfo{},
- License: &spec.License{},
+ License: nil,
+ },
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{},
},
},
Paths: &spec.Paths{
@@ -86,11 +115,12 @@ func New(options ...func(*Parser)) *Parser {
Definitions: make(map[string]spec.Schema),
},
},
- files: make(map[string]*ast.File),
- TypeDefinitions: make(map[string]map[string]*ast.TypeSpec),
- ImportAliases: make(map[string]map[string]*ast.ImportSpec),
- CustomPrimitiveTypes: make(map[string]string),
- registerTypes: make(map[string]*ast.TypeSpec),
+ packages: NewPackagesDefinitions(),
+ parsedSchemas: make(map[*TypeSpecDef]*Schema),
+ outputSchemas: make(map[*TypeSpecDef]*Schema),
+ existSchemaNames: make(map[string]*Schema),
+ toBeRenamedSchemas: make(map[string]string),
+ excludes: make(map[string]bool),
}
for _, option := range options {
@@ -107,15 +137,38 @@ func SetMarkdownFileDirectory(directoryPath string) func(*Parser) {
}
}
+// SetCodeExamplesDirectory sets the directory to search for code example files
+func SetCodeExamplesDirectory(directoryPath string) func(*Parser) {
+ return func(p *Parser) {
+ p.codeExampleFilesDir = directoryPath
+ }
+}
+
+// SetExcludedDirsAndFiles sets directories and files to be excluded when searching
+func SetExcludedDirsAndFiles(excludes string) func(*Parser) {
+ return func(p *Parser) {
+ for _, f := range strings.Split(excludes, ",") {
+ f = strings.TrimSpace(f)
+ if f != "" {
+ f = filepath.Clean(f)
+ p.excludes[f] = true
+ }
+ }
+ }
+}
+
// ParseAPI parses general api info for given searchDir and mainAPIFile
-func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error {
+func (parser *Parser) ParseAPI(searchDir, mainAPIFile string, parseDepth int) error {
Printf("Generate general API Info, search dir:%s", searchDir)
- if err := parser.getAllGoFileInfo(searchDir); err != nil {
- return err
+ packageDir, err := getPkgName(searchDir)
+ if err != nil {
+ Printf("warning: failed to get package name in dir: %s, error: %s", searchDir, err.Error())
}
- var t depth.Tree
+ if err = parser.getAllGoFileInfo(packageDir, searchDir); err != nil {
+ return err
+ }
absMainAPIFilePath, err := filepath.Abs(filepath.Join(searchDir, mainAPIFile))
if err != nil {
@@ -123,13 +176,19 @@ func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error {
}
if parser.ParseDependency {
- pkgName, err := getPkgName(path.Dir(absMainAPIFilePath))
+ var t depth.Tree
+ t.ResolveInternal = true
+ t.MaxDepth = parseDepth
+
+ pkgName, err := getPkgName(filepath.Dir(absMainAPIFilePath))
if err != nil {
return err
}
+
if err := t.Resolve(pkgName); err != nil {
return fmt.Errorf("pkg %s cannot find all dependencies, %s", pkgName, err)
}
+
for i := 0; i < len(t.Root.Deps); i++ {
if err := parser.getAllGoFileInfoFromDeps(&t.Root.Deps[i]); err != nil {
return err
@@ -137,21 +196,22 @@ func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error {
}
}
- if err := parser.ParseGeneralAPIInfo(absMainAPIFilePath); err != nil {
+ if err = parser.ParseGeneralAPIInfo(absMainAPIFilePath); err != nil {
return err
}
- for _, astFile := range parser.files {
- parser.ParseType(astFile)
+ parser.parsedSchemas, err = parser.packages.ParseTypes()
+ if err != nil {
+ return err
}
- for fileName, astFile := range parser.files {
- if err := parser.ParseRouterAPIInfo(fileName, astFile); err != nil {
- return err
- }
+ if err = parser.packages.RangeFiles(parser.ParseRouterAPIInfo); err != nil {
+ return err
}
- return parser.parseDefinitions()
+ parser.renameRefSchemas()
+
+ return parser.checkOperationIDUniqueness()
}
func getPkgName(searchDir string) (string, error) {
@@ -176,6 +236,14 @@ func getPkgName(searchDir string) (string, error) {
return outStr, nil
}
+func initIfEmpty(license *spec.License) *spec.License {
+ if license == nil {
+ return new(spec.License)
+ }
+
+ return license
+}
+
// ParseGeneralAPIInfo parses general api info for given mainAPIFile path
func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
fileSet := token.NewFileSet()
@@ -227,8 +295,10 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
case "@contact.url":
parser.swagger.Info.Contact.URL = value
case "@license.name":
+ parser.swagger.Info.License = initIfEmpty(parser.swagger.Info.License)
parser.swagger.Info.License.Name = value
case "@license.url":
+ parser.swagger.Info.License = initIfEmpty(parser.swagger.Info.License)
parser.swagger.Info.License.URL = value
case "@host":
parser.swagger.Host = value
@@ -270,36 +340,52 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
case "@securitydefinitions.basic":
securityMap[value] = spec.BasicAuth()
case "@securitydefinitions.apikey":
- attrMap, _, err := extractSecurityAttribute(attribute, []string{"@in", "@name"}, comments[i+1:])
+ attrMap, _, _, err := extractSecurityAttribute(attribute, []string{"@in", "@name"}, comments[i+1:])
if err != nil {
return err
}
securityMap[value] = spec.APIKeyAuth(attrMap["@name"], attrMap["@in"])
case "@securitydefinitions.oauth2.application":
- attrMap, scopes, err := extractSecurityAttribute(attribute, []string{"@tokenurl"}, comments[i+1:])
+ attrMap, scopes, extensions, err := extractSecurityAttribute(attribute, []string{"@tokenurl"}, comments[i+1:])
if err != nil {
return err
}
- securityMap[value] = securitySchemeOAuth2Application(attrMap["@tokenurl"], scopes)
+ securityMap[value] = securitySchemeOAuth2Application(attrMap["@tokenurl"], scopes, extensions)
case "@securitydefinitions.oauth2.implicit":
- attrMap, scopes, err := extractSecurityAttribute(attribute, []string{"@authorizationurl"}, comments[i+1:])
+ attrMap, scopes, extensions, err := extractSecurityAttribute(attribute, []string{"@authorizationurl"}, comments[i+1:])
if err != nil {
return err
}
- securityMap[value] = securitySchemeOAuth2Implicit(attrMap["@authorizationurl"], scopes)
+ securityMap[value] = securitySchemeOAuth2Implicit(attrMap["@authorizationurl"], scopes, extensions)
case "@securitydefinitions.oauth2.password":
- attrMap, scopes, err := extractSecurityAttribute(attribute, []string{"@tokenurl"}, comments[i+1:])
+ attrMap, scopes, extensions, err := extractSecurityAttribute(attribute, []string{"@tokenurl"}, comments[i+1:])
if err != nil {
return err
}
- securityMap[value] = securitySchemeOAuth2Password(attrMap["@tokenurl"], scopes)
+ securityMap[value] = securitySchemeOAuth2Password(attrMap["@tokenurl"], scopes, extensions)
case "@securitydefinitions.oauth2.accesscode":
- attrMap, scopes, err := extractSecurityAttribute(attribute, []string{"@tokenurl", "@authorizationurl"}, comments[i+1:])
+ attrMap, scopes, extensions, err := extractSecurityAttribute(attribute, []string{"@tokenurl", "@authorizationurl"}, comments[i+1:])
if err != nil {
return err
}
- securityMap[value] = securitySchemeOAuth2AccessToken(attrMap["@authorizationurl"], attrMap["@tokenurl"], scopes)
+ securityMap[value] = securitySchemeOAuth2AccessToken(attrMap["@authorizationurl"], attrMap["@tokenurl"], scopes, extensions)
+ case "@x-tokenname":
+ // ignore this
+ break
+ case "@query.collection.format":
+ parser.collectionFormatInQuery = value
+ case "@x-taggroups":
+ originalAttribute := strings.Split(commentLine, " ")[0]
+ if len(value) == 0 {
+ return fmt.Errorf("annotation %s need a value", attribute)
+ }
+
+ var valueJSON interface{}
+ if err := json.Unmarshal([]byte(value), &valueJSON); err != nil {
+ return fmt.Errorf("annotation %s need a valid json value", originalAttribute)
+ }
+ parser.swagger.Extensions[originalAttribute[1:]] = valueJSON // don't use the method provided by spec lib, cause it will call toLower() on attribute names, which is wrongy
default:
prefixExtension := "@x-"
if len(attribute) > 5 { // Prefix extension + 1 char + 1 space + 1 char
@@ -313,7 +399,12 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
if err := json.Unmarshal([]byte(split[1]), &valueJSON); err != nil {
return fmt.Errorf("annotation %s need a valid json value", attribute)
}
- parser.swagger.AddExtension(extensionName, valueJSON)
+
+ if strings.Contains(extensionName, "logo") {
+ parser.swagger.Info.Extensions.Add(extensionName, valueJSON)
+ } else {
+ parser.swagger.AddExtension(extensionName, valueJSON)
+ }
}
}
}
@@ -333,16 +424,17 @@ func isGeneralAPIComment(comment *ast.CommentGroup) bool {
attribute := strings.ToLower(strings.Split(commentLine, " ")[0])
switch attribute {
// The @summary, @router, @success,@failure annotation belongs to Operation
- case "@summary", "@router", "@success", "@failure":
+ case "@summary", "@router", "@success", "@failure", "@response":
return false
}
}
return true
}
-func extractSecurityAttribute(context string, search []string, lines []string) (map[string]string, map[string]string, error) {
+func extractSecurityAttribute(context string, search []string, lines []string) (map[string]string, map[string]string, map[string]interface{}, error) {
attrMap := map[string]string{}
scopes := map[string]string{}
+ extensions := map[string]interface{}{}
for _, v := range lines {
securityAttr := strings.ToLower(strings.Split(v, " ")[0])
for _, findterm := range search {
@@ -353,58 +445,76 @@ func extractSecurityAttribute(context string, search []string, lines []string) (
}
isExists, err := isExistsScope(securityAttr)
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
if isExists {
scopScheme, err := getScopeScheme(securityAttr)
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
scopes[scopScheme] = v[len(securityAttr):]
}
+ if securityAttr == "@x-tokenname" {
+ extensions["x-tokenName"] = strings.TrimSpace(v[len(securityAttr):])
+ }
// next securityDefinitions
if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
break
}
}
if len(attrMap) != len(search) {
- return nil, nil, fmt.Errorf("%s is %v required", context, search)
+ return nil, nil, nil, fmt.Errorf("%s is %v required", context, search)
}
- return attrMap, scopes, nil
+ return attrMap, scopes, extensions, nil
}
-func securitySchemeOAuth2Application(tokenurl string, scopes map[string]string) *spec.SecurityScheme {
+func securitySchemeOAuth2Application(tokenurl string, scopes map[string]string, extensions map[string]interface{}) *spec.SecurityScheme {
securityScheme := spec.OAuth2Application(tokenurl)
+ securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions)
for scope, description := range scopes {
securityScheme.AddScope(scope, description)
}
return securityScheme
}
-func securitySchemeOAuth2Implicit(authorizationurl string, scopes map[string]string) *spec.SecurityScheme {
+func securitySchemeOAuth2Implicit(authorizationurl string, scopes map[string]string, extensions map[string]interface{}) *spec.SecurityScheme {
securityScheme := spec.OAuth2Implicit(authorizationurl)
+ securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions)
for scope, description := range scopes {
securityScheme.AddScope(scope, description)
}
return securityScheme
}
-func securitySchemeOAuth2Password(tokenurl string, scopes map[string]string) *spec.SecurityScheme {
+func securitySchemeOAuth2Password(tokenurl string, scopes map[string]string, extensions map[string]interface{}) *spec.SecurityScheme {
securityScheme := spec.OAuth2Password(tokenurl)
+ securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions)
for scope, description := range scopes {
securityScheme.AddScope(scope, description)
}
return securityScheme
}
-func securitySchemeOAuth2AccessToken(authorizationurl, tokenurl string, scopes map[string]string) *spec.SecurityScheme {
+func securitySchemeOAuth2AccessToken(authorizationurl, tokenurl string, scopes map[string]string, extensions map[string]interface{}) *spec.SecurityScheme {
securityScheme := spec.OAuth2AccessToken(authorizationurl, tokenurl)
+ securityScheme.VendorExtensible.Extensions = handleSecuritySchemaExtensions(extensions)
for scope, description := range scopes {
securityScheme.AddScope(scope, description)
}
return securityScheme
}
+func handleSecuritySchemaExtensions(providedExtensions map[string]interface{}) spec.Extensions {
+ var extensions spec.Extensions
+ if len(providedExtensions) > 0 {
+ extensions = make(map[string]interface{}, len(providedExtensions))
+ for key, value := range providedExtensions {
+ extensions[key] = value
+ }
+ }
+ return extensions
+}
+
func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) {
filesInfos, err := ioutil.ReadDir(dirPath)
if err != nil {
@@ -465,8 +575,7 @@ func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) err
switch astDeclaration := astDescription.(type) {
case *ast.FuncDecl:
if astDeclaration.Doc != nil && astDeclaration.Doc.List != nil {
- operation := NewOperation() //for per 'function' comment, create a new 'Operation' object
- operation.parser = parser
+ operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir)) //for per 'function' comment, create a new 'Operation' object
for _, comment := range astDeclaration.Doc.List {
if err := operation.ParseComment(comment.Text, astFile); err != nil {
return fmt.Errorf("ParseComment error in file %s :%+v", fileName, err)
@@ -503,141 +612,158 @@ func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) err
return nil
}
-// ParseType parses type info for given astFile.
-func (parser *Parser) ParseType(astFile *ast.File) {
- if _, ok := parser.TypeDefinitions[astFile.Name.String()]; !ok {
- parser.TypeDefinitions[astFile.Name.String()] = make(map[string]*ast.TypeSpec)
- }
-
- for _, astDeclaration := range astFile.Decls {
- if generalDeclaration, ok := astDeclaration.(*ast.GenDecl); ok && generalDeclaration.Tok == token.TYPE {
- for _, astSpec := range generalDeclaration.Specs {
- if typeSpec, ok := astSpec.(*ast.TypeSpec); ok {
- typeName := fmt.Sprintf("%v", typeSpec.Type)
- // check if its a custom primitive type
- if IsGolangPrimitiveType(typeName) {
- var typeSpecFullName = fmt.Sprintf("%s.%s", astFile.Name.String(), typeSpec.Name.String())
- parser.CustomPrimitiveTypes[typeSpecFullName] = TransToValidSchemeType(typeName)
- } else {
- parser.TypeDefinitions[astFile.Name.String()][typeSpec.Name.String()] = typeSpec
- }
+func convertFromSpecificToPrimitive(typeName string) (string, error) {
+ name := typeName
+ if strings.ContainsRune(name, '.') {
+ name = strings.Split(name, ".")[1]
+ }
+ switch strings.ToUpper(name) {
+ case "TIME", "OBJECTID", "UUID":
+ return STRING, nil
+ case "DECIMAL":
+ return NUMBER, nil
+ }
+ return typeName, ErrFailedConvertPrimitiveType
+}
- }
+func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) (*spec.Schema, error) {
+ if IsGolangPrimitiveType(typeName) {
+ return PrimitiveSchema(TransToValidSchemeType(typeName)), nil
+ }
+
+ if schemaType, err := convertFromSpecificToPrimitive(typeName); err == nil {
+ return PrimitiveSchema(schemaType), nil
+ }
+
+ typeSpecDef := parser.packages.FindTypeSpec(typeName, file)
+ if typeSpecDef == nil {
+ return nil, fmt.Errorf("cannot find type definition: %s", typeName)
+ }
+
+ schema, ok := parser.parsedSchemas[typeSpecDef]
+ if !ok {
+ var err error
+ schema, err = parser.ParseDefinition(typeSpecDef)
+ if err == ErrRecursiveParseStruct {
+ if ref {
+ return parser.getRefTypeSchema(typeSpecDef, schema), nil
}
+
+ } else if err != nil {
+ return nil, err
}
}
- for _, importSpec := range astFile.Imports {
- if importSpec.Name == nil {
- continue
- }
+ if ref && len(schema.Schema.Type) > 0 && schema.Schema.Type[0] == OBJECT {
+ return parser.getRefTypeSchema(typeSpecDef, schema), nil
+ }
+ return schema.Schema, nil
+}
- alias := importSpec.Name.Name
+func (parser *Parser) renameRefSchemas() {
+ if len(parser.toBeRenamedSchemas) == 0 {
+ return
+ }
- if _, ok := parser.ImportAliases[alias]; !ok {
- parser.ImportAliases[alias] = make(map[string]*ast.ImportSpec)
+ //rename schemas in swagger.Definitions
+ for name, pkgPath := range parser.toBeRenamedSchemas {
+ if schema, ok := parser.swagger.Definitions[name]; ok {
+ delete(parser.swagger.Definitions, name)
+ name = parser.renameSchema(name, pkgPath)
+ parser.swagger.Definitions[name] = schema
}
+ }
- importParts := strings.Split(strings.Trim(importSpec.Path.Value, "\""), "/")
- importPkgName := importParts[len(importParts)-1]
-
- parser.ImportAliases[alias][importPkgName] = importSpec
+ //rename URLs if match
+ for _, url := range parser.toBeRenamedRefURLs {
+ parts := strings.Split(url.Fragment, "/")
+ name := parts[len(parts)-1]
+ if pkgPath, ok := parser.toBeRenamedSchemas[name]; ok {
+ parts[len(parts)-1] = parser.renameSchema(name, pkgPath)
+ url.Fragment = strings.Join(parts, "/")
+ }
}
}
-func (parser *Parser) isInStructStack(refTypeName string) bool {
- for _, structName := range parser.structStack {
- if refTypeName == structName {
- return true
+func (parser *Parser) renameSchema(name, pkgPath string) string {
+ parts := strings.Split(name, ".")
+ name = fullTypeName(pkgPath, parts[len(parts)-1])
+ name = strings.ReplaceAll(name, "/", "_")
+ return name
+}
+
+func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) *spec.Schema {
+ if _, ok := parser.outputSchemas[typeSpecDef]; !ok {
+ if existSchema, ok := parser.existSchemaNames[schema.Name]; ok {
+ //store the first one to be renamed after parsing over
+ if _, ok = parser.toBeRenamedSchemas[existSchema.Name]; !ok {
+ parser.toBeRenamedSchemas[existSchema.Name] = existSchema.PkgPath
+ }
+ //rename not the first one
+ schema.Name = parser.renameSchema(schema.Name, schema.PkgPath)
+ } else {
+ parser.existSchemaNames[schema.Name] = schema
+ }
+ if schema.Schema != nil {
+ parser.swagger.Definitions[schema.Name] = *schema.Schema
+ } else {
+ parser.swagger.Definitions[schema.Name] = spec.Schema{}
}
+ parser.outputSchemas[typeSpecDef] = schema
}
- return false
+
+ refSchema := RefSchema(schema.Name)
+ //store every URL
+ parser.toBeRenamedRefURLs = append(parser.toBeRenamedRefURLs, refSchema.Ref.Ref.GetURL())
+ return refSchema
}
-// parseDefinitions parses Swagger Api definitions.
-func (parser *Parser) parseDefinitions() error {
- // 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
- if err := parser.ParseDefinition(pkgName, typeSpec.Name.Name, typeSpec); err != nil {
- return err
+func (parser *Parser) isInStructStack(typeSpecDef *TypeSpecDef) bool {
+ for _, specDef := range parser.structStack {
+ if typeSpecDef == specDef {
+ return true
}
}
- return nil
+ return false
}
// 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) error {
- refTypeName := fullTypeName(pkgName, typeName)
+func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) {
+ typeName := typeSpecDef.FullName()
+ refTypeName := TypeDocName(typeName, typeSpecDef.TypeSpec)
- if typeSpec == nil {
- Println("Skipping '" + refTypeName + "', pkg '" + pkgName + "' not found, try add flag --parseDependency or --parseVendor.")
- return nil
+ if schema, ok := parser.parsedSchemas[typeSpecDef]; ok {
+ Println("Skipping '" + typeName + "', already parsed.")
+ return schema, nil
}
- if _, isParsed := parser.swagger.Definitions[refTypeName]; isParsed {
- Println("Skipping '" + refTypeName + "', already parsed.")
- return nil
+ if parser.isInStructStack(typeSpecDef) {
+ Println("Skipping '" + typeName + "', recursion detected.")
+ return &Schema{
+ Name: refTypeName,
+ PkgPath: typeSpecDef.PkgPath,
+ Schema: PrimitiveSchema(OBJECT)},
+ ErrRecursiveParseStruct
}
+ parser.structStack = append(parser.structStack, typeSpecDef)
- if parser.isInStructStack(refTypeName) {
- Println("Skipping '" + refTypeName + "', recursion detected.")
- return nil
- }
- parser.structStack = append(parser.structStack, refTypeName)
+ Println("Generating " + typeName)
- Println("Generating " + refTypeName)
-
- schema, err := parser.parseTypeExpr(pkgName, typeName, typeSpec.Type)
+ schema, err := parser.parseTypeExpr(typeSpecDef.File, typeSpecDef.TypeSpec.Type, false)
if err != nil {
- return err
- }
- parser.swagger.Definitions[refTypeName] = *schema
- return nil
-}
-
-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 {
- ks = append(ks, k)
- }
- sort.Strings(ks)
-
- requiredFields = make([]string, 0)
-
- // iterate over keys list instead of map to avoid the random shuffle of the order that go does for maps
- for _, k := range ks {
- prop := properties[k]
-
- // todo find the pkgName of the property type
- tname := prop.SchemaProps.Type[0]
- if _, ok := parser.TypeDefinitions[pkgName][tname]; ok {
- tspec := parser.TypeDefinitions[pkgName][tname]
- parser.ParseDefinition(pkgName, tname, tspec)
- }
- requiredFields = append(requiredFields, prop.SchemaProps.Required...)
- properties[k] = prop
+ return nil, err
}
+ s := &Schema{Name: refTypeName, PkgPath: typeSpecDef.PkgPath, Schema: schema}
+ parser.parsedSchemas[typeSpecDef] = s
- if extraRequired != nil {
- requiredFields = append(requiredFields, extraRequired...)
+ //update an empty schema as a result of recursion
+ if s2, ok := parser.outputSchemas[typeSpecDef]; ok {
+ parser.swagger.Definitions[s2.Name] = *schema
}
- sort.Strings(requiredFields)
-
- return
+ return s, nil
}
func fullTypeName(pkgName, typeName string) string {
@@ -649,123 +775,76 @@ 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) (*spec.Schema, error) {
-
+func (parser *Parser) parseTypeExpr(file *ast.File, typeExpr ast.Expr, ref bool) (*spec.Schema, error) {
switch expr := typeExpr.(type) {
// type Foo struct {...}
case *ast.StructType:
- refTypeName := fullTypeName(pkgName, typeName)
- if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed {
- return &schema, nil
- }
-
- return parser.parseStruct(pkgName, expr.Fields)
+ return parser.parseStruct(file, expr.Fields)
// type Foo Baz
case *ast.Ident:
- if IsGolangPrimitiveType(expr.Name) {
- return &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: spec.StringOrArray{TransToValidSchemeType(expr.Name)},
- },
- }, nil
- }
- refTypeName := fullTypeName(pkgName, expr.Name)
- if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed {
- if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok {
- parser.ParseDefinition(pkgName, expr.Name, typedef)
- }
- }
- return &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Ref: spec.Ref{
- Ref: jsonreference.MustCreateRef("#/definitions/" + refTypeName),
- },
- },
- }, nil
+ return parser.getTypeSchema(expr.Name, file, ref)
// type Foo *Baz
case *ast.StarExpr:
- return parser.parseTypeExpr(pkgName, typeName, expr.X)
-
- // type Foo []Baz
- case *ast.ArrayType:
- itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Elt)
- if err != nil {
- return &spec.Schema{}, err
- }
- return &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{"array"},
- Items: &spec.SchemaOrArray{
- Schema: itemSchema,
- },
- },
- }, nil
+ return parser.parseTypeExpr(file, expr.X, ref)
// type Foo pkg.Bar
case *ast.SelectorExpr:
if xIdent, ok := expr.X.(*ast.Ident); ok {
- return parser.parseTypeExpr(xIdent.Name, expr.Sel.Name, expr.Sel)
+ return parser.getTypeSchema(fullTypeName(xIdent.Name, expr.Sel.Name), file, ref)
}
-
+ // type Foo []Baz
+ case *ast.ArrayType:
+ itemSchema, err := parser.parseTypeExpr(file, expr.Elt, true)
+ if err != nil {
+ return nil, err
+ }
+ return spec.ArrayProperty(itemSchema), nil
// type Foo map[string]Bar
case *ast.MapType:
- var valueSchema spec.SchemaOrBool
if _, ok := expr.Value.(*ast.InterfaceType); ok {
- valueSchema.Allows = true
- } else {
- schema, err := parser.parseTypeExpr(pkgName, "", expr.Value)
- if err != nil {
- return &spec.Schema{}, err
- }
- valueSchema.Schema = schema
+ return spec.MapProperty(nil), nil
}
- return &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{"object"},
- AdditionalProperties: &valueSchema,
- },
- }, nil
+ schema, err := parser.parseTypeExpr(file, expr.Value, true)
+ if err != nil {
+ return nil, err
+ }
+ return spec.MapProperty(schema), nil
+ case *ast.FuncType:
+ return nil, ErrFuncTypeField
// ...
default:
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
+ return PrimitiveSchema(OBJECT), nil
}
-func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (*spec.Schema, error) {
+func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec.Schema, error) {
- extraRequired := make([]string, 0)
+ required := make([]string, 0)
properties := make(map[string]spec.Schema)
for _, field := range fields.List {
- fieldProps, requiredFromAnon, err := parser.parseStructField(pkgName, field)
- if err != nil {
- return &spec.Schema{}, err
+ fieldProps, requiredFromAnon, err := parser.parseStructField(file, field)
+ if err == ErrFuncTypeField {
+ continue
+ } else if err != nil {
+ return nil, err
+ } else if len(fieldProps) == 0 {
+ continue
}
- extraRequired = append(extraRequired, requiredFromAnon...)
+ required = append(required, requiredFromAnon...)
for k, v := range fieldProps {
properties[k] = v
}
}
- // collect requireds from our properties and anonymous fields
- required := parser.collectRequiredFields(pkgName, properties, extraRequired)
-
- // unset required from properties because we've collected them
- for k, prop := range properties {
- prop.SchemaProps.Required = make([]string, 0)
- properties[k] = prop
- }
+ sort.Strings(required)
return &spec.Schema{
SchemaProps: spec.SchemaProps{
- Type: []string{"object"},
+ Type: []string{OBJECT},
Properties: properties,
Required: required,
}}, nil
@@ -790,322 +869,106 @@ type structField struct {
extensions map[string]interface{}
}
-func (sf *structField) toStandardSchema() *spec.Schema {
- required := make([]string, 0)
- if sf.isRequired {
- required = append(required, sf.name)
- }
- return &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{sf.schemaType},
- Description: sf.desc,
- Format: sf.formatType,
- Required: required,
- Maximum: sf.maximum,
- Minimum: sf.minimum,
- MaxLength: sf.maxLength,
- MinLength: sf.minLength,
- Enum: sf.enums,
- Default: sf.defaultValue,
- },
- SwaggerSchemaProps: spec.SwaggerSchemaProps{
- Example: sf.exampleValue,
- ReadOnly: sf.readOnly,
- },
- VendorExtensible: spec.VendorExtensible{
- Extensions: sf.extensions,
- },
- }
-}
-
-func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[string]spec.Schema, []string, error) {
- properties := map[string]spec.Schema{}
-
+func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[string]spec.Schema, []string, error) {
if field.Names == nil {
- fullTypeName, err := getFieldType(field.Type)
- if err != nil {
- return properties, []string{}, nil
+ if field.Tag != nil {
+ skip, ok := reflect.StructTag(strings.ReplaceAll(field.Tag.Value, "`", "")).Lookup("swaggerignore")
+ if ok && strings.EqualFold(skip, "true") {
+ return nil, nil, nil
+ }
}
- typeName := fullTypeName
-
- if splits := strings.Split(fullTypeName, "."); len(splits) > 1 {
- pkgName = splits[0]
- typeName = splits[1]
+ typeName, err := getFieldType(field.Type)
+ if err != nil {
+ return nil, nil, err
}
-
- typeSpec := parser.TypeDefinitions[pkgName][typeName]
- if typeSpec != nil {
- 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]
+ schema, err := parser.getTypeSchema(typeName, file, false)
+ if err != nil {
+ return nil, nil, err
+ }
+ if len(schema.Type) > 0 && schema.Type[0] == OBJECT {
+ if len(schema.Properties) == 0 {
+ return nil, nil, nil
}
- switch schemaType {
- case "object":
- for k, v := range schema.SchemaProps.Properties {
- properties[k] = v
- }
- case "array":
- properties[typeName] = *schema
- default:
- Printf("Can't extract properties from a schema of type '%s'", schemaType)
+ properties := map[string]spec.Schema{}
+ for k, v := range schema.Properties {
+ properties[k] = v
}
return properties, schema.SchemaProps.Required, nil
}
-
- return properties, nil, nil
+ //for alias type of non-struct types ,such as array,map, etc. ignore field tag.
+ return map[string]spec.Schema{typeName: *schema}, nil, nil
}
- structField, err := parser.parseField(pkgName, field)
+ fieldName, schema, err := parser.getFieldName(field)
if err != nil {
- return properties, nil, err
- }
- if structField.name == "" {
- return properties, nil, nil
+ return nil, nil, err
}
-
- // TODO: find package of schemaType and/or arrayType
- if structField.crossPkg != "" {
- pkgName = structField.crossPkg
+ if fieldName == "" {
+ return nil, nil, nil
}
-
- fillObject := func(src, dest interface{}) error {
- bin, err := json.Marshal(src)
+ if schema == nil {
+ typeName, err := getFieldType(field.Type)
+ if err == nil {
+ //named type
+ schema, err = parser.getTypeSchema(typeName, file, true)
+ } else {
+ //unnamed type
+ schema, err = parser.parseTypeExpr(file, field.Type, false)
+ }
if err != nil {
- return err
+ return nil, nil, err
}
- return json.Unmarshal(bin, dest)
}
- //for spec.Schema have implemented json.Marshaler, here in another way to convert
- fillSchema := func(src, dest *spec.Schema) error {
- err = fillObject(&src.SchemaProps, &dest.SchemaProps)
- if err != nil {
- return err
- }
- err = fillObject(&src.SwaggerSchemaProps, &dest.SwaggerSchemaProps)
- if err != nil {
- return err
- }
- return fillObject(&src.VendorExtensible, &dest.VendorExtensible)
+ types := parser.GetSchemaTypePath(schema, 2)
+ if len(types) == 0 {
+ return nil, nil, fmt.Errorf("invalid type for field: %s", field.Names[0])
}
- if _, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field
- // write definition if not yet present
- parser.ParseDefinition(pkgName, structField.schemaType,
- parser.TypeDefinitions[pkgName][structField.schemaType])
- required := make([]string, 0)
- if structField.isRequired {
- required = append(required, structField.name)
- }
- properties[structField.name] = spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{"object"}, // to avoid swagger validation error
- Description: structField.desc,
- Required: required,
- Ref: spec.Ref{
- Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType),
- },
- },
- SwaggerSchemaProps: spec.SwaggerSchemaProps{
- ReadOnly: structField.readOnly,
- },
- }
- } else if structField.schemaType == "array" { // array field type
- // if defined -- ref it
- if _, ok := parser.TypeDefinitions[pkgName][structField.arrayType]; ok { // user type in array
- parser.ParseDefinition(pkgName, structField.arrayType,
- parser.TypeDefinitions[pkgName][structField.arrayType])
- required := make([]string, 0)
- if structField.isRequired {
- required = append(required, structField.name)
- }
- properties[structField.name] = spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{structField.schemaType},
- Description: structField.desc,
- Required: required,
- Items: &spec.SchemaOrArray{
- Schema: &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Ref: spec.Ref{
- Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.arrayType),
- },
- },
- },
- },
- },
- SwaggerSchemaProps: spec.SwaggerSchemaProps{
- ReadOnly: structField.readOnly,
- },
- }
- } else if structField.arrayType == "object" {
- // Anonymous struct
- if astTypeArray, ok := field.Type.(*ast.ArrayType); ok { // if array
- props := make(map[string]spec.Schema)
- if expr, ok := astTypeArray.Elt.(*ast.StructType); ok {
- for _, field := range expr.Fields.List {
- var fieldProps map[string]spec.Schema
- fieldProps, _, err = parser.parseStructField(pkgName, field)
- if err != nil {
- return properties, nil, err
- }
- for k, v := range fieldProps {
- props[k] = v
- }
- }
- properties[structField.name] = spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{structField.schemaType},
- Description: structField.desc,
- Items: &spec.SchemaOrArray{
- Schema: &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{"object"},
- Properties: props,
- },
- },
- },
- },
- SwaggerSchemaProps: spec.SwaggerSchemaProps{
- ReadOnly: structField.readOnly,
- },
- }
- } else {
- schema, _ := parser.parseTypeExpr(pkgName, "", astTypeArray.Elt)
- properties[structField.name] = spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{structField.schemaType},
- Description: structField.desc,
- Items: &spec.SchemaOrArray{
- Schema: schema,
- },
- },
- SwaggerSchemaProps: spec.SwaggerSchemaProps{
- ReadOnly: structField.readOnly,
- },
- }
- }
- }
- } else if structField.arrayType == "array" {
- if astTypeArray, ok := field.Type.(*ast.ArrayType); ok {
- schema, _ := parser.parseTypeExpr(pkgName, "", astTypeArray.Elt)
- properties[structField.name] = spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{structField.schemaType},
- Description: structField.desc,
- Items: &spec.SchemaOrArray{
- Schema: schema,
- },
- },
- SwaggerSchemaProps: spec.SwaggerSchemaProps{
- ReadOnly: structField.readOnly,
- },
- }
- }
- } else {
- // standard type in array
- required := make([]string, 0)
- if structField.isRequired {
- required = append(required, structField.name)
- }
+ structField, err := parser.parseFieldTag(field, types)
+ if err != nil {
+ return nil, nil, err
+ }
- properties[structField.name] = spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{structField.schemaType},
- Description: structField.desc,
- Format: structField.formatType,
- Required: required,
- Items: &spec.SchemaOrArray{
- Schema: &spec.Schema{
- SchemaProps: spec.SchemaProps{
- Type: []string{structField.arrayType},
- Maximum: structField.maximum,
- Minimum: structField.minimum,
- MaxLength: structField.maxLength,
- MinLength: structField.minLength,
- Enum: structField.enums,
- Default: structField.defaultValue,
- },
- },
- },
- },
- SwaggerSchemaProps: spec.SwaggerSchemaProps{
- Example: structField.exampleValue,
- ReadOnly: structField.readOnly,
- },
- }
- }
- } else if astTypeMap, ok := field.Type.(*ast.MapType); ok { // if map
- stdSchema := structField.toStandardSchema()
- mapValueSchema, err := parser.parseTypeExpr(pkgName, "", astTypeMap)
- if err != nil {
- return properties, nil, err
- }
- stdSchema.Type = mapValueSchema.Type
- stdSchema.AdditionalProperties = mapValueSchema.AdditionalProperties
- properties[structField.name] = *stdSchema
- } else {
- stdSchema := structField.toStandardSchema()
- properties[structField.name] = *stdSchema
-
- if nestStar, ok := field.Type.(*ast.StarExpr); ok {
- if !IsGolangPrimitiveType(structField.schemaType) {
- schema, err := parser.parseTypeExpr(pkgName, structField.schemaType, nestStar.X)
- if err != nil {
- return properties, nil, err
- }
+ if structField.schemaType == "string" && types[0] != structField.schemaType {
+ schema = PrimitiveSchema(structField.schemaType)
+ }
- if len(schema.SchemaProps.Type) > 0 {
- err = fillSchema(schema, stdSchema)
- if err != nil {
- return properties, nil, err
- }
- properties[structField.name] = *stdSchema
- return properties, nil, nil
- }
- }
- } else if nestStruct, ok := field.Type.(*ast.StructType); ok {
- props := map[string]spec.Schema{}
- nestRequired := make([]string, 0)
- for _, v := range nestStruct.Fields.List {
- p, _, err := parser.parseStructField(pkgName, v)
- if err != nil {
- return properties, nil, err
- }
- for k, v := range p {
- if v.SchemaProps.Type[0] != "object" {
- nestRequired = append(nestRequired, v.SchemaProps.Required...)
- v.SchemaProps.Required = make([]string, 0)
- }
- props[k] = v
- }
- }
- stdSchema.Properties = props
- stdSchema.Required = nestRequired
- properties[structField.name] = *stdSchema
- }
+ schema.Description = structField.desc
+ schema.ReadOnly = structField.readOnly
+ schema.Default = structField.defaultValue
+ schema.Example = structField.exampleValue
+ schema.Format = structField.formatType
+ schema.Extensions = structField.extensions
+ eleSchema := schema
+ if structField.schemaType == "array" {
+ eleSchema = schema.Items.Schema
}
- return properties, nil, nil
-}
+ eleSchema.Maximum = structField.maximum
+ eleSchema.Minimum = structField.minimum
+ eleSchema.MaxLength = structField.maxLength
+ eleSchema.MinLength = structField.minLength
+ eleSchema.Enum = structField.enums
-func getFieldType(field interface{}) (string, error) {
+ var tagRequired []string
+ if structField.isRequired {
+ tagRequired = append(tagRequired, fieldName)
+ }
+ return map[string]spec.Schema{fieldName: *schema}, tagRequired, nil
+}
+func getFieldType(field ast.Expr) (string, error) {
switch ftype := field.(type) {
case *ast.Ident:
return ftype.Name, nil
-
case *ast.SelectorExpr:
packageName, err := getFieldType(ftype.X)
if err != nil {
return "", err
}
- return fmt.Sprintf("%s.%s", packageName, ftype.Sel.Name), nil
+ return fullTypeName(packageName, ftype.Sel.Name), nil
case *ast.StarExpr:
fullName, err := getFieldType(ftype.X)
@@ -1113,48 +976,60 @@ func getFieldType(field interface{}) (string, error) {
return "", err
}
return fullName, nil
-
}
return "", fmt.Errorf("unknown field type %#v", field)
}
-func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField, error) {
- prop, err := getPropertyName(pkgName, field.Type, parser)
- if err != nil {
- return nil, err
+func (parser *Parser) getFieldName(field *ast.Field) (name string, schema *spec.Schema, err error) {
+ // Skip non-exported fields.
+ if !ast.IsExported(field.Names[0].Name) {
+ return "", nil, nil
}
- if len(prop.ArrayType) == 0 {
- if err := CheckSchemaType(prop.SchemaType); err != nil {
- return nil, err
+ if field.Tag != nil {
+ // `json:"tag"` -> json:"tag"
+ structTag := reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1))
+ if ignoreTag := structTag.Get("swaggerignore"); strings.EqualFold(ignoreTag, "true") {
+ return "", nil, nil
}
- } else {
- if err := CheckSchemaType("array"); err != nil {
- return nil, err
+
+ name = structTag.Get("json")
+ // json:"tag,hoge"
+ if name = strings.TrimSpace(strings.Split(name, ",")[0]); name == "-" {
+ return "", nil, nil
+ }
+
+ if typeTag := structTag.Get("swaggertype"); typeTag != "" {
+ parts := strings.Split(typeTag, ",")
+ schema, err = BuildCustomSchema(parts)
+ if err != nil {
+ return "", nil, err
+ }
}
}
- // Skip func fields.
- if prop.SchemaType == "func" {
- return &structField{name: ""}, nil
+ if name == "" {
+ switch parser.PropNamingStrategy {
+ case SnakeCase:
+ name = toSnakeCase(field.Names[0].Name)
+ case PascalCase:
+ name = field.Names[0].Name
+ case CamelCase:
+ name = toLowerCamelCase(field.Names[0].Name)
+ default:
+ name = toLowerCamelCase(field.Names[0].Name)
+ }
}
+ return name, schema, err
+}
+func (parser *Parser) parseFieldTag(field *ast.Field, types []string) (*structField, error) {
structField := &structField{
- name: field.Names[0].Name,
- schemaType: prop.SchemaType,
- arrayType: prop.ArrayType,
- crossPkg: prop.CrossPkg,
- }
-
- switch parser.PropNamingStrategy {
- case SnakeCase:
- structField.name = toSnakeCase(structField.name)
- case PascalCase:
- //use struct field name
- case CamelCase:
- structField.name = toLowerCamelCase(structField.name)
- default:
- structField.name = toLowerCamelCase(structField.name)
+ // name: field.Names[0].Name,
+ schemaType: types[0],
+ }
+ if len(types) > 1 && (types[0] == "array" || types[0] == "object") {
+ structField.arrayType = types[1]
}
if field.Doc != nil {
@@ -1169,54 +1044,23 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField
}
// `json:"tag"` -> json:"tag"
structTag := reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1))
- jsonTag := structTag.Get("json")
- // json:"tag,hoge"
- if strings.Contains(jsonTag, ",") {
- // json:",hoge"
- if strings.HasPrefix(jsonTag, ",") {
- jsonTag = ""
- } else {
- jsonTag = strings.SplitN(jsonTag, ",", 2)[0]
- }
- }
- if jsonTag == "-" {
- structField.name = ""
- } else if jsonTag != "" {
- structField.name = jsonTag
- }
- if typeTag := structTag.Get("swaggertype"); typeTag != "" {
- parts := strings.Split(typeTag, ",")
- if 0 < len(parts) && len(parts) <= 2 {
- newSchemaType := parts[0]
- newArrayType := structField.arrayType
- if len(parts) >= 2 {
- if newSchemaType == "array" {
- newArrayType = parts[1]
- if err := CheckSchemaType(newArrayType); err != nil {
- return nil, err
- }
- } else if newSchemaType == "primitive" {
- newSchemaType = parts[1]
- newArrayType = parts[1]
- }
- }
+ jsonTag := structTag.Get("json")
+ // json:"name,string" or json:",string"
+ hasStringTag := strings.Contains(jsonTag, ",string")
- if err := CheckSchemaType(newSchemaType); err != nil {
+ if exampleTag := structTag.Get("example"); exampleTag != "" {
+ if hasStringTag {
+ // then the example must be in string format
+ structField.exampleValue = exampleTag
+ } else {
+ example, err := defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag)
+ if err != nil {
return nil, err
}
-
- structField.schemaType = newSchemaType
- structField.arrayType = newArrayType
+ structField.exampleValue = example
}
}
- if exampleTag := structTag.Get("example"); 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
}
@@ -1249,7 +1093,7 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField
}
if enumsTag := structTag.Get("enums"); enumsTag != "" {
enumType := structField.schemaType
- if structField.schemaType == "array" {
+ if structField.schemaType == ARRAY {
enumType = structField.arrayType
}
@@ -1282,7 +1126,7 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField
}
structField.minimum = minimum
}
- if structField.schemaType == "string" || structField.arrayType == "string" {
+ if structField.schemaType == STRING || structField.arrayType == STRING {
maxLength, err := getIntTag(structTag, "maxLength")
if err != nil {
return nil, err
@@ -1299,9 +1143,63 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField
structField.readOnly = readOnly == "true"
}
+ // perform this after setting everything else (min, max, etc...)
+ if hasStringTag {
+
+ // @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types."
+ defaultValues := map[string]string{
+ // Zero Values as string
+ STRING: "",
+ INTEGER: "0",
+ BOOLEAN: "false",
+ NUMBER: "0",
+ }
+
+ if defaultValue, ok := defaultValues[structField.schemaType]; ok {
+ structField.schemaType = STRING
+
+ if structField.exampleValue == nil {
+ // if exampleValue is not defined by the user,
+ // we will force an example with a correct value
+ // (eg: int->"0", bool:"false")
+ structField.exampleValue = defaultValue
+ }
+ }
+ }
+
return structField, nil
}
+// GetSchemaTypePath get path of schema type
+func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string {
+ if schema == nil || depth == 0 {
+ return nil
+ }
+ if name := schema.Ref.String(); name != "" {
+ if pos := strings.LastIndexByte(name, '/'); pos >= 0 {
+ name = name[pos+1:]
+ if schema, ok := parser.swagger.Definitions[name]; ok {
+ return parser.GetSchemaTypePath(&schema, depth)
+ }
+ }
+ } else if len(schema.Type) > 0 {
+ if schema.Type[0] == "array" {
+ depth--
+ s := []string{schema.Type[0]}
+ return append(s, parser.GetSchemaTypePath(schema.Items.Schema, depth)...)
+ } else if schema.Type[0] == OBJECT {
+ if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
+ // for map
+ depth--
+ s := []string{schema.Type[0]}
+ return append(s, parser.GetSchemaTypePath(schema.AdditionalProperties.Schema, depth)...)
+ }
+ }
+ return []string{schema.Type[0]}
+ }
+ return nil
+}
+
func replaceLastTag(slice []spec.Tag, element spec.Tag) {
slice = slice[:len(slice)-1]
slice = append(slice, element)
@@ -1370,27 +1268,27 @@ func toLowerCamelCase(in string) string {
// defineTypeOfExample example value define the type (object and array unsupported)
func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{}, error) {
switch schemaType {
- case "string":
+ case STRING:
return exampleValue, nil
- case "number":
+ case NUMBER:
v, err := strconv.ParseFloat(exampleValue, 64)
if err != nil {
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v, nil
- case "integer":
+ case INTEGER:
v, err := strconv.Atoi(exampleValue)
if err != nil {
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v, nil
- case "boolean":
+ case BOOLEAN:
v, err := strconv.ParseBool(exampleValue)
if err != nil {
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v, nil
- case "array":
+ case ARRAY:
values := strings.Split(exampleValue, ",")
result := make([]interface{}, 0)
for _, value := range values {
@@ -1401,21 +1299,59 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{
result = append(result, v)
}
return result, nil
+ case OBJECT:
+ if arrayType == "" {
+ return nil, fmt.Errorf("%s is unsupported type in example value", schemaType)
+ }
+
+ values := strings.Split(exampleValue, ",")
+ result := map[string]interface{}{}
+ for _, value := range values {
+ mapData := strings.Split(value, ":")
+
+ if len(mapData) == 2 {
+ v, err := defineTypeOfExample(arrayType, "", mapData[1])
+ if err != nil {
+ return nil, err
+ }
+ result[mapData[0]] = v
+ } else {
+ return nil, fmt.Errorf("example value %s should format: key:value", exampleValue)
+ }
+ }
+ return result, nil
default:
return nil, fmt.Errorf("%s is unsupported type in example value", schemaType)
}
}
// GetAllGoFileInfo gets all Go source files information for given searchDir.
-func (parser *Parser) getAllGoFileInfo(searchDir string) error {
- return filepath.Walk(searchDir, parser.visit)
+func (parser *Parser) getAllGoFileInfo(packageDir, searchDir string) error {
+ return filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error {
+ if err := parser.Skip(path, f); err != nil {
+ return err
+ } else if f.IsDir() {
+ return nil
+ }
+
+ relPath, err := filepath.Rel(searchDir, path)
+ if err != nil {
+ return err
+ }
+ return parser.parseFile(filepath.ToSlash(filepath.Dir(filepath.Clean(filepath.Join(packageDir, relPath)))), path, nil)
+ })
}
func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg) error {
- if pkg.Internal || !pkg.Resolved { // ignored internal and not resolved dependencies
+ ignoreInternal := pkg.Internal && !parser.ParseInternal
+ if ignoreInternal || !pkg.Resolved { // ignored internal and not resolved dependencies
return nil
}
+ // Skip cgo
+ if pkg.Raw == nil && pkg.Name == "C" {
+ return nil
+ }
srcDir := pkg.Raw.Dir
files, err := ioutil.ReadDir(srcDir) // only parsing files in the dir(don't contains sub dir files)
if err != nil {
@@ -1428,7 +1364,7 @@ func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg) error {
}
path := filepath.Join(srcDir, f.Name())
- if err := parser.parseFile(path); err != nil {
+ if err := parser.parseFile(pkg.Name, path, nil); err != nil {
return err
}
}
@@ -1442,44 +1378,84 @@ func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg) error {
return nil
}
-func (parser *Parser) visit(path string, f os.FileInfo, err error) error {
- if err := parser.Skip(path, f); err != nil {
- return err
+func (parser *Parser) parseFile(packageDir, path string, src interface{}) error {
+ if strings.HasSuffix(strings.ToLower(path), "_test.go") || filepath.Ext(path) != ".go" {
+ return nil
}
- return parser.parseFile(path)
+
+ // positions are relative to FileSet
+ astFile, err := goparser.ParseFile(token.NewFileSet(), path, src, goparser.ParseComments)
+ if err != nil {
+ return fmt.Errorf("ParseFile error:%+v", err)
+ }
+ parser.packages.CollectAstFile(packageDir, path, astFile)
+ return nil
}
-func (parser *Parser) parseFile(path string) error {
- if ext := filepath.Ext(path); ext == ".go" {
- fset := token.NewFileSet() // positions are relative to fset
- astFile, err := goparser.ParseFile(fset, path, nil, goparser.ParseComments)
- if err != nil {
- return fmt.Errorf("ParseFile error:%+v", err)
+func (parser *Parser) checkOperationIDUniqueness() error {
+ // operationsIds contains all operationId annotations to check it's unique
+ operationsIds := make(map[string]string)
+ saveOperationID := func(operationID, currentPath string) error {
+ if operationID == "" {
+ return nil
+ }
+ if previousPath, ok := operationsIds[operationID]; ok {
+ return fmt.Errorf(
+ "duplicated @id annotation '%s' found in '%s', previously declared in: '%s'",
+ operationID, currentPath, previousPath)
+ }
+ operationsIds[operationID] = currentPath
+ return nil
+ }
+ getOperationID := func(itm spec.PathItem) (string, string) {
+ if itm.Get != nil {
+ return "GET", itm.Get.ID
+ }
+ if itm.Put != nil {
+ return "PUT", itm.Put.ID
+ }
+ if itm.Post != nil {
+ return "POST", itm.Post.ID
+ }
+ if itm.Delete != nil {
+ return "DELETE", itm.Delete.ID
+ }
+ if itm.Options != nil {
+ return "OPTIONS", itm.Options.ID
+ }
+ if itm.Head != nil {
+ return "HEAD", itm.Head.ID
+ }
+ if itm.Patch != nil {
+ return "PATCH", itm.Patch.ID
+ }
+ return "", ""
+ }
+ for path, itm := range parser.swagger.Paths.Paths {
+ method, id := getOperationID(itm)
+ if err := saveOperationID(id, fmt.Sprintf("%s %s", method, path)); err != nil {
+ return err
}
-
- parser.files[path] = astFile
}
return nil
}
// Skip returns filepath.SkipDir error if match vendor and hidden folder
func (parser *Parser) Skip(path string, f os.FileInfo) error {
-
- if !parser.ParseVendor { // ignore vendor
- if f.IsDir() && f.Name() == "vendor" {
+ if f.IsDir() {
+ if !parser.ParseVendor && f.Name() == "vendor" || //ignore "vendor"
+ f.Name() == "docs" || //exclude docs
+ len(f.Name()) > 1 && f.Name()[0] == '.' { // exclude all hidden folder
return filepath.SkipDir
}
- }
- // issue
- if f.IsDir() && f.Name() == "docs" {
- return filepath.SkipDir
+ if parser.excludes != nil {
+ if _, ok := parser.excludes[path]; ok {
+ return filepath.SkipDir
+ }
+ }
}
- // exclude all hidden folder
- if f.IsDir() && len(f.Name()) > 1 && f.Name()[0] == '.' {
- return filepath.SkipDir
- }
return nil
}
@@ -1487,3 +1463,20 @@ func (parser *Parser) Skip(path string, f os.FileInfo) error {
func (parser *Parser) GetSwagger() *spec.Swagger {
return parser.swagger
}
+
+//addTestType just for tests
+func (parser *Parser) addTestType(typename string) {
+ if parser.parsedSchemas == nil {
+ parser.parsedSchemas = make(map[*TypeSpecDef]*Schema)
+ }
+ if parser.packages.uniqueDefinitions == nil {
+ parser.packages.uniqueDefinitions = make(map[string]*TypeSpecDef)
+ }
+ typeDef := &TypeSpecDef{}
+ parser.packages.uniqueDefinitions[typename] = typeDef
+ parser.parsedSchemas[typeDef] = &Schema{
+ PkgPath: "",
+ Name: typename,
+ Schema: PrimitiveSchema(OBJECT),
+ }
+}
diff --git a/parser_test.go b/parser_test.go
index 063f938af..6ab188d55 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -6,13 +6,14 @@ import (
"go/token"
"io/ioutil"
"os"
- "path"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
+const defaultParseDepth = 100
+
func TestNew(t *testing.T) {
swagMode = test
New()
@@ -38,7 +39,12 @@ func TestParser_ParseGeneralApiInfo(t *testing.T) {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
- "version": "1.0"
+ "version": "1.0",
+ "x-logo": {
+ "altText": "Petstore logo",
+ "backgroundColor": "#FFFFFF",
+ "url": "https://redocly.github.io/redoc/petstore-logo.png"
+ }
},
"host": "petstore.swagger.io",
"basePath": "/v2",
@@ -59,11 +65,13 @@ func TestParser_ParseGeneralApiInfo(t *testing.T) {
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information"
- }
+ },
+ "x-tokenName": "id_token"
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -82,6 +90,7 @@ func TestParser_ParseGeneralApiInfo(t *testing.T) {
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -145,6 +154,7 @@ func TestParser_ParseGeneralApiInfoTemplated(t *testing.T) {
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -163,6 +173,7 @@ func TestParser_ParseGeneralApiInfoTemplated(t *testing.T) {
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -223,7 +234,6 @@ func TestParser_ParseGeneralApiInfoWithOpsInSameFile(t *testing.T) {
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
- "license": {},
"version": "1.0"
},
"paths": {}
@@ -250,28 +260,25 @@ func TestGetAllGoFileInfo(t *testing.T) {
searchDir := "testdata/pet"
p := New()
- err := p.getAllGoFileInfo(searchDir)
+ err := p.getAllGoFileInfo("testdata", searchDir)
assert.NoError(t, err)
- assert.NotEmpty(t, p.files[filepath.Join("testdata", "pet", "main.go")])
- assert.NotEmpty(t, p.files[filepath.Join("testdata", "pet", "web", "handler.go")])
- assert.Equal(t, 2, len(p.files))
+ assert.Equal(t, 2, len(p.packages.files))
}
func TestParser_ParseType(t *testing.T) {
searchDir := "testdata/simple/"
p := New()
- err := p.getAllGoFileInfo(searchDir)
+ err := p.getAllGoFileInfo("testdata", searchDir)
assert.NoError(t, err)
- for _, file := range p.files {
- p.ParseType(file)
- }
+ _, err = p.packages.ParseTypes()
- assert.NotNil(t, p.TypeDefinitions["api"]["Pet3"])
- assert.NotNil(t, p.TypeDefinitions["web"]["Pet"])
- assert.NotNil(t, p.TypeDefinitions["web"]["Pet2"])
+ assert.NoError(t, err)
+ assert.NotNil(t, p.packages.uniqueDefinitions["api.Pet3"])
+ assert.NotNil(t, p.packages.uniqueDefinitions["web.Pet"])
+ assert.NotNil(t, p.packages.uniqueDefinitions["web.Pet2"])
}
func TestGetSchemes(t *testing.T) {
@@ -281,6 +288,20 @@ func TestGetSchemes(t *testing.T) {
}
func TestParseSimpleApi1(t *testing.T) {
+ expected, err := ioutil.ReadFile("testdata/simple/expected.json")
+ assert.NoError(t, err)
+ searchDir := "testdata/simple"
+ mainAPIFile := "main.go"
+ p := New()
+ p.PropNamingStrategy = PascalCase
+ err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
+ assert.NoError(t, err)
+
+ b, _ := json.MarshalIndent(p.swagger, "", " ")
+ assert.Equal(t, string(expected), string(b))
+}
+
+func TestParseSimpleApi_ForSnakecase(t *testing.T) {
expected := `{
"swagger": "2.0",
"info": {
@@ -334,15 +355,6 @@ func TestParseSimpleApi1(t *testing.T) {
"$ref": "#/definitions/web.APIError"
}
},
- "401": {
- "description": "Unauthorized",
- "schema": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
"404": {
"description": "Can not find ID",
"schema": {
@@ -516,75 +528,29 @@ func TestParseSimpleApi1(t *testing.T) {
}
},
"definitions": {
- "api.SwagReturn": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- }
- },
- "cross.Cross": {
- "type": "object",
- "properties": {
- "Array": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "String": {
- "type": "string"
- }
- }
- },
"web.APIError": {
"type": "object",
"properties": {
- "CreatedAt": {
+ "created_at": {
"type": "string"
},
- "ErrorCode": {
+ "error_code": {
"type": "integer"
},
- "ErrorMessage": {
+ "error_message": {
"type": "string"
}
}
},
- "web.AnonymousStructArray": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "foo": {
- "type": "string"
- }
- }
- }
- },
- "web.CrossAlias": {
- "$ref": "#/definitions/cross.Cross"
- },
- "web.IndirectRecursiveTest": {
- "type": "object",
- "properties": {
- "Tags": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.Tag"
- }
- }
- }
- },
"web.Pet": {
"type": "object",
"required": [
- "name",
- "photo_urls"
+ "price"
],
"properties": {
+ "birthday": {
+ "type": "integer"
+ },
"category": {
"type": "object",
"properties": {
@@ -619,8 +585,6 @@ func TestParseSimpleApi1(t *testing.T) {
},
"name": {
"type": "string",
- "maxLength": 16,
- "minLength": 4,
"example": "detail_category_name"
},
"photo_urls": {
@@ -637,50 +601,43 @@ func TestParseSimpleApi1(t *testing.T) {
}
}
},
- "data": {
- "type": "object"
+ "coeffs": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
},
- "decimal": {
- "type": "number"
+ "custom_string": {
+ "type": "string"
},
- "enum_array": {
+ "custom_string_arr": {
"type": "array",
"items": {
- "type": "integer",
- "enum": [
- 1,
- 2,
- 3,
- 5,
- 7
- ]
+ "type": "string"
}
},
+ "data": {
+ "type": "object"
+ },
+ "decimal": {
+ "type": "number"
+ },
"id": {
"type": "integer",
"format": "int64",
- "readOnly": true,
"example": 1
},
- "int_array": {
- "type": "array",
- "items": {
- "type": "integer"
- },
- "example": [
- 1,
- 2
- ]
- },
"is_alive": {
"type": "boolean",
- "default": true,
"example": true
},
"name": {
"type": "string",
"example": "poti"
},
+ "null_int": {
+ "type": "integer"
+ },
"pets": {
"type": "array",
"items": {
@@ -705,16 +662,10 @@ func TestParseSimpleApi1(t *testing.T) {
},
"price": {
"type": "number",
- "maximum": 1000,
- "minimum": 1,
"example": 3.25
},
"status": {
- "type": "string",
- "enum": [
- "healthy",
- "ill"
- ]
+ "type": "string"
},
"tags": {
"type": "array",
@@ -736,75 +687,22 @@ func TestParseSimpleApi1(t *testing.T) {
"id": {
"type": "integer"
},
- "middlename": {
- "type": "string",
- "x-abc": "def",
- "x-nullable": true
- }
- }
- },
- "web.Pet5a": {
- "type": "object",
- "required": [
- "name",
- "odd"
- ],
- "properties": {
- "name": {
- "type": "string"
- },
- "odd": {
- "type": "boolean"
- }
- }
- },
- "web.Pet5b": {
- "type": "object",
- "required": [
- "name"
- ],
- "properties": {
- "name": {
- "type": "string"
- }
- }
- },
- "web.Pet5c": {
- "type": "object",
- "required": [
- "name",
- "odd"
- ],
- "properties": {
- "name": {
+ "middle_name": {
"type": "string"
- },
- "odd": {
- "type": "boolean"
}
}
},
"web.RevValue": {
"type": "object",
"properties": {
- "Data": {
+ "data": {
"type": "integer"
},
- "Err": {
+ "err": {
"type": "integer"
},
- "Status": {
+ "status": {
"type": "boolean"
- },
- "cross": {
- "type": "object",
- "$ref": "#/definitions/cross.Cross"
- },
- "crosses": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/cross.Cross"
- }
}
}
},
@@ -825,12 +723,6 @@ func TestParseSimpleApi1(t *testing.T) {
}
}
}
- },
- "web.Tags": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.Tag"
- }
}
},
"securityDefinitions": {
@@ -854,6 +746,7 @@ func TestParseSimpleApi1(t *testing.T) {
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -872,6 +765,7 @@ func TestParseSimpleApi1(t *testing.T) {
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -881,18 +775,18 @@ func TestParseSimpleApi1(t *testing.T) {
}
}
}`
- searchDir := "testdata/simple"
+ searchDir := "testdata/simple2"
mainAPIFile := "main.go"
p := New()
- p.PropNamingStrategy = PascalCase
- err := p.ParseAPI(searchDir, mainAPIFile)
+ p.PropNamingStrategy = SnakeCase
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
-func TestParseSimpleApi_ForSnakecase(t *testing.T) {
+func TestParseSimpleApi_ForLowerCamelcase(t *testing.T) {
expected := `{
"swagger": "2.0",
"info": {
@@ -1122,26 +1016,20 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"web.APIError": {
"type": "object",
"properties": {
- "created_at": {
+ "createdAt": {
"type": "string"
},
- "error_code": {
+ "errorCode": {
"type": "integer"
},
- "error_message": {
+ "errorMessage": {
"type": "string"
}
}
},
"web.Pet": {
"type": "object",
- "required": [
- "price"
- ],
"properties": {
- "birthday": {
- "type": "integer"
- },
"category": {
"type": "object",
"properties": {
@@ -1153,7 +1041,7 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"type": "string",
"example": "category_name"
},
- "photo_urls": {
+ "photoURLs": {
"type": "array",
"format": "url",
"items": {
@@ -1164,11 +1052,8 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"http://test/image/2.jpg"
]
},
- "small_category": {
+ "smallCategory": {
"type": "object",
- "required": [
- "name"
- ],
"properties": {
"id": {
"type": "integer",
@@ -1178,7 +1063,7 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"type": "string",
"example": "detail_category_name"
},
- "photo_urls": {
+ "photoURLs": {
"type": "array",
"items": {
"type": "string"
@@ -1192,21 +1077,6 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
}
}
},
- "coeffs": {
- "type": "array",
- "items": {
- "type": "number"
- }
- },
- "custom_string": {
- "type": "string"
- },
- "custom_string_arr": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
"data": {
"type": "object"
},
@@ -1218,7 +1088,7 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"format": "int64",
"example": 1
},
- "is_alive": {
+ "isAlive": {
"type": "boolean",
"example": true
},
@@ -1226,9 +1096,6 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"type": "string",
"example": "poti"
},
- "null_int": {
- "type": "integer"
- },
"pets": {
"type": "array",
"items": {
@@ -1241,7 +1108,7 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"$ref": "#/definitions/web.Pet2"
}
},
- "photo_urls": {
+ "photoURLs": {
"type": "array",
"items": {
"type": "string"
@@ -1272,13 +1139,13 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"web.Pet2": {
"type": "object",
"properties": {
- "deleted_at": {
+ "deletedAt": {
"type": "string"
},
"id": {
"type": "integer"
},
- "middle_name": {
+ "middleName": {
"type": "string"
}
}
@@ -1337,6 +1204,7 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -1355,6 +1223,7 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
+ "authorizationUrl": "",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
@@ -1364,223 +1233,51 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) {
}
}
}`
- searchDir := "testdata/simple2"
+ searchDir := "testdata/simple3"
mainAPIFile := "main.go"
p := New()
- p.PropNamingStrategy = SnakeCase
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
-func TestParseSimpleApi_ForLowerCamelcase(t *testing.T) {
+func TestParseStructComment(t *testing.T) {
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
- "termsOfService": "http://swagger.io/terms/",
- "contact": {
- "name": "API Support",
- "url": "http://www.swagger.io/support",
- "email": "support@swagger.io"
- },
- "license": {
- "name": "Apache 2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
- },
+ "contact": {},
"version": "1.0"
},
- "host": "petstore.swagger.io",
- "basePath": "/v2",
+ "host": "localhost:4000",
+ "basePath": "/api",
"paths": {
- "/file/upload": {
- "post": {
- "description": "Upload file",
+ "/posts/{post_id}": {
+ "get": {
+ "description": "get string by ID",
"consumes": [
- "multipart/form-data"
+ "application/json"
],
"produces": [
"application/json"
],
- "summary": "Upload file",
- "operationId": "file.upload",
+ "summary": "Add a new pet to the store",
"parameters": [
{
- "type": "file",
- "description": "this is a test file",
- "name": "file",
- "in": "formData",
+ "type": "integer",
+ "format": "int64",
+ "description": "Some ID",
+ "name": "post_id",
+ "in": "path",
"required": true
}
],
"responses": {
"200": {
- "description": "ok",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "We need ID!!",
- "schema": {
- "$ref": "#/definitions/web.APIError"
- }
- },
- "404": {
- "description": "Can not find ID",
- "schema": {
- "$ref": "#/definitions/web.APIError"
- }
- }
- }
- }
- },
- "/testapi/get-string-by-int/{some_id}": {
- "get": {
- "description": "get string by ID",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "summary": "Add a new pet to the store",
- "operationId": "get-string-by-int",
- "parameters": [
- {
- "type": "integer",
- "format": "int64",
- "description": "Some ID",
- "name": "some_id",
- "in": "path",
- "required": true
- },
- {
- "description": "Some ID",
- "name": "some_id",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/web.Pet"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "ok",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "We need ID!!",
- "schema": {
- "$ref": "#/definitions/web.APIError"
- }
- },
- "404": {
- "description": "Can not find ID",
- "schema": {
- "$ref": "#/definitions/web.APIError"
- }
- }
- }
- }
- },
- "/testapi/get-struct-array-by-string/{some_id}": {
- "get": {
- "security": [
- {
- "ApiKeyAuth": []
- },
- {
- "BasicAuth": []
- },
- {
- "OAuth2Application": [
- "write"
- ]
- },
- {
- "OAuth2Implicit": [
- "read",
- "admin"
- ]
- },
- {
- "OAuth2AccessCode": [
- "read"
- ]
- },
- {
- "OAuth2Password": [
- "admin"
- ]
- }
- ],
- "description": "get struct array by ID",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "operationId": "get-struct-array-by-string",
- "parameters": [
- {
- "type": "string",
- "description": "Some ID",
- "name": "some_id",
- "in": "path",
- "required": true
- },
- {
- "enum": [
- 1,
- 2,
- 3
- ],
- "type": "integer",
- "description": "Category",
- "name": "category",
- "in": "query",
- "required": true
- },
- {
- "minimum": 0,
- "type": "integer",
- "default": 0,
- "description": "Offset",
- "name": "offset",
- "in": "query",
- "required": true
- },
- {
- "maximum": 50,
- "type": "integer",
- "default": 10,
- "description": "Limit",
- "name": "limit",
- "in": "query",
- "required": true
- },
- {
- "maxLength": 50,
- "minLength": 1,
- "type": "string",
- "default": "\"\"",
- "description": "q",
- "name": "q",
- "in": "query",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "ok",
+ "description": "OK",
"schema": {
"type": "string"
}
@@ -1606,280 +1303,61 @@ func TestParseSimpleApi_ForLowerCamelcase(t *testing.T) {
"type": "object",
"properties": {
"createdAt": {
- "type": "string"
- },
- "errorCode": {
- "type": "integer"
- },
- "errorMessage": {
- "type": "string"
- }
- }
- },
- "web.Pet": {
- "type": "object",
- "properties": {
- "category": {
- "type": "object",
- "properties": {
- "id": {
- "type": "integer",
- "example": 1
- },
- "name": {
- "type": "string",
- "example": "category_name"
- },
- "photoURLs": {
- "type": "array",
- "format": "url",
- "items": {
- "type": "string"
- },
- "example": [
- "http://test/image/1.jpg",
- "http://test/image/2.jpg"
- ]
- },
- "smallCategory": {
- "type": "object",
- "properties": {
- "id": {
- "type": "integer",
- "example": 1
- },
- "name": {
- "type": "string",
- "example": "detail_category_name"
- },
- "photoURLs": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "example": [
- "http://test/image/1.jpg",
- "http://test/image/2.jpg"
- ]
- }
- }
- }
- }
- },
- "data": {
- "type": "object"
- },
- "decimal": {
- "type": "number"
- },
- "id": {
- "type": "integer",
- "format": "int64",
- "example": 1
- },
- "isAlive": {
- "type": "boolean",
- "example": true
- },
- "name": {
- "type": "string",
- "example": "poti"
- },
- "pets": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.Pet2"
- }
- },
- "pets2": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.Pet2"
- }
- },
- "photoURLs": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "example": [
- "http://test/image/1.jpg",
- "http://test/image/2.jpg"
- ]
- },
- "price": {
- "type": "number",
- "example": 3.25
- },
- "status": {
- "type": "string"
- },
- "tags": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.Tag"
- }
- },
- "uuid": {
- "type": "string"
- }
- }
- },
- "web.Pet2": {
- "type": "object",
- "properties": {
- "deletedAt": {
- "type": "string"
- },
- "id": {
- "type": "integer"
- },
- "middleName": {
- "type": "string"
- }
- }
- },
- "web.RevValue": {
- "type": "object",
- "properties": {
- "data": {
- "type": "integer"
- },
- "err": {
- "type": "integer"
- },
- "status": {
- "type": "boolean"
- }
- }
- },
- "web.Tag": {
- "type": "object",
- "properties": {
- "id": {
- "type": "integer",
- "format": "int64"
+ "description": "Error time",
+ "type": "string"
},
- "name": {
+ "error": {
+ "description": "Error an Api error",
"type": "string"
},
- "pets": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.Pet"
- }
+ "errorCtx": {
+ "description": "Error ` + "`" + `context` + "`" + ` tick comment",
+ "type": "string"
+ },
+ "errorNo": {
+ "description": "Error ` + "`" + `number` + "`" + ` tick comment",
+ "type": "integer"
}
}
}
- },
- "securityDefinitions": {
- "ApiKeyAuth": {
- "type": "apiKey",
- "name": "Authorization",
- "in": "header"
- },
- "BasicAuth": {
- "type": "basic"
- },
- "OAuth2AccessCode": {
- "type": "oauth2",
- "flow": "accessCode",
- "authorizationUrl": "https://example.com/oauth/authorize",
- "tokenUrl": "https://example.com/oauth/token",
- "scopes": {
- "admin": " Grants read and write access to administrative information"
- }
- },
- "OAuth2Application": {
- "type": "oauth2",
- "flow": "application",
- "tokenUrl": "https://example.com/oauth/token",
- "scopes": {
- "admin": " Grants read and write access to administrative information",
- "write": " Grants write access"
- }
- },
- "OAuth2Implicit": {
- "type": "oauth2",
- "flow": "implicit",
- "authorizationUrl": "https://example.com/oauth/authorize",
- "scopes": {
- "admin": " Grants read and write access to administrative information",
- "write": " Grants write access"
- }
- },
- "OAuth2Password": {
- "type": "oauth2",
- "flow": "password",
- "tokenUrl": "https://example.com/oauth/token",
- "scopes": {
- "admin": " Grants read and write access to administrative information",
- "read": " Grants read access",
- "write": " Grants write access"
- }
- }
}
}`
- searchDir := "testdata/simple3"
+ searchDir := "testdata/struct_comment"
mainAPIFile := "main.go"
p := New()
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
-
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
-func TestParseStructComment(t *testing.T) {
+func TestParseNonExportedJSONFields(t *testing.T) {
expected := `{
"swagger": "2.0",
"info": {
- "description": "This is a sample server Petstore server.",
+ "description": "This is a sample server.",
"title": "Swagger Example API",
"contact": {},
- "license": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
- "/posts/{post_id}": {
+ "/so-something": {
"get": {
- "description": "get string by ID",
+ "description": "Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
- "summary": "Add a new pet to the store",
- "parameters": [
- {
- "type": "integer",
- "format": "int64",
- "description": "Some ID",
- "name": "post_id",
- "in": "path",
- "required": true
- }
- ],
+ "summary": "Call DoSomething",
"responses": {
"200": {
"description": "OK",
"schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "We need ID!!",
- "schema": {
- "$ref": "#/definitions/web.APIError"
- }
- },
- "404": {
- "description": "Can not find ID",
- "schema": {
- "$ref": "#/definitions/web.APIError"
+ "$ref": "#/definitions/main.MyStruct"
}
}
}
@@ -1887,28 +1365,7 @@ func TestParseStructComment(t *testing.T) {
}
},
"definitions": {
- "web.APIError": {
- "type": "object",
- "properties": {
- "createdAt": {
- "description": "Error time",
- "type": "string"
- },
- "error": {
- "description": "Error an Api error",
- "type": "string"
- },
- "errorCtx": {
- "description": "Error ` + "`" + `context` + "`" + ` tick comment",
- "type": "string"
- },
- "errorNo": {
- "description": "Error ` + "`" + `number` + "`" + ` tick comment",
- "type": "integer"
- }
- }
- },
- "web.Post": {
+ "main.MyStruct": {
"type": "object",
"properties": {
"data": {
@@ -1938,10 +1395,11 @@ func TestParseStructComment(t *testing.T) {
}
}
}`
- searchDir := "testdata/struct_comment"
+
+ searchDir := "testdata/non_exported_json_fields"
mainAPIFile := "main.go"
p := New()
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
@@ -1965,168 +1423,16 @@ func TestParsePetApi(t *testing.T) {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
- "version": "1.0"
- },
- "host": "petstore.swagger.io",
- "basePath": "/v2",
- "paths": {}
-}`
- searchDir := "testdata/pet"
- mainAPIFile := "main.go"
- p := New()
- err := p.ParseAPI(searchDir, mainAPIFile)
- assert.NoError(t, err)
- b, _ := json.MarshalIndent(p.swagger, "", " ")
- assert.Equal(t, expected, string(b))
-}
-
-func TestParseModelNotUnderRoot(t *testing.T) {
- expected := `{
- "swagger": "2.0",
- "info": {
- "description": "This is a sample server Petstore server.",
- "title": "Swagger Example API",
- "termsOfService": "http://swagger.io/terms/",
- "contact": {
- "name": "API Support",
- "url": "http://www.swagger.io/support",
- "email": "support@swagger.io"
- },
- "license": {
- "name": "Apache 2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
- },
- "version": "1.0"
- },
- "host": "petstore.swagger.io",
- "basePath": "/v2",
- "paths": {
- "/file/upload": {
- "post": {
- "description": "Upload file",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "summary": "Upload file",
- "operationId": "file.upload",
- "parameters": [
- {
- "description": "Foo to create",
- "name": "data",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/data.Foo"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "ok",
- "schema": {
- "type": "string"
- }
- }
- }
- }
- },
- "/testapi/get-string-by-int/{some_id}": {
- "get": {
- "description": "get string by ID",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "summary": "Add a new pet to the store",
- "operationId": "get-string-by-int",
- "parameters": [
- {
- "type": "integer",
- "format": "int64",
- "description": "Some ID",
- "name": "some_id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "ok",
- "schema": {
- "$ref": "#/definitions/data.Foo"
- }
- }
- }
- }
- }
- },
- "definitions": {
- "data.Foo": {
- "type": "object",
- "properties": {
- "field1": {
- "type": "string"
- }
- }
- }
- },
- "securityDefinitions": {
- "ApiKeyAuth": {
- "type": "apiKey",
- "name": "Authorization",
- "in": "header"
- },
- "BasicAuth": {
- "type": "basic"
- },
- "OAuth2AccessCode": {
- "type": "oauth2",
- "flow": "accessCode",
- "authorizationUrl": "https://example.com/oauth/authorize",
- "tokenUrl": "https://example.com/oauth/token",
- "scopes": {
- "admin": " Grants read and write access to administrative information"
- }
- },
- "OAuth2Application": {
- "type": "oauth2",
- "flow": "application",
- "tokenUrl": "https://example.com/oauth/token",
- "scopes": {
- "admin": " Grants read and write access to administrative information",
- "write": " Grants write access"
- }
- },
- "OAuth2Implicit": {
- "type": "oauth2",
- "flow": "implicit",
- "authorizationUrl": "https://example.com/oauth/authorize",
- "scopes": {
- "admin": " Grants read and write access to administrative information",
- "write": " Grants write access"
- }
- },
- "OAuth2Password": {
- "type": "oauth2",
- "flow": "password",
- "tokenUrl": "https://example.com/oauth/token",
- "scopes": {
- "admin": " Grants read and write access to administrative information",
- "read": " Grants read access",
- "write": " Grants write access"
- }
- }
- }
+ "version": "1.0"
+ },
+ "host": "petstore.swagger.io",
+ "basePath": "/v2",
+ "paths": {}
}`
- searchDir := "testdata/model_not_under_root/cmd"
+ searchDir := "testdata/pet"
mainAPIFile := "main.go"
p := New()
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
@@ -2195,7 +1501,7 @@ func TestParseModelAsTypeAlias(t *testing.T) {
searchDir := "testdata/alias_type"
mainAPIFile := "main.go"
p := New()
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
@@ -2206,10 +1512,10 @@ func TestParseComposition(t *testing.T) {
searchDir := "testdata/composition"
mainAPIFile := "main.go"
p := New()
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
- expected, err := ioutil.ReadFile(path.Join(searchDir, "expected.json"))
+ expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
@@ -2222,10 +1528,10 @@ func TestParseImportAliases(t *testing.T) {
searchDir := "testdata/alias_import"
mainAPIFile := "main.go"
p := New()
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
- expected, err := ioutil.ReadFile(path.Join(searchDir, "expected.json"))
+ expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
@@ -2238,16 +1544,47 @@ func TestParseNested(t *testing.T) {
mainAPIFile := "main.go"
p := New()
p.ParseDependency = true
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
- expected, err := ioutil.ReadFile(path.Join(searchDir, "expected.json"))
+ expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, string(expected), string(b))
}
+func TestParseDuplicated(t *testing.T) {
+ searchDir := "testdata/duplicated"
+ mainAPIFile := "main.go"
+ p := New()
+ p.ParseDependency = true
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
+ assert.Errorf(t, err, "duplicated @id declarations successfully found")
+}
+
+func TestParseDuplicatedOtherMethods(t *testing.T) {
+ searchDir := "testdata/duplicated2"
+ mainAPIFile := "main.go"
+ p := New()
+ p.ParseDependency = true
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
+ assert.Errorf(t, err, "duplicated @id declarations successfully found")
+}
+
+func TestParseConflictSchemaName(t *testing.T) {
+ searchDir := "testdata/conflict_name"
+ mainAPIFile := "main.go"
+ p := New()
+ p.ParseDependency = true
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
+ assert.NoError(t, err)
+ b, _ := json.MarshalIndent(p.swagger, "", " ")
+ expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json"))
+ assert.NoError(t, err)
+ assert.Equal(t, string(expected), string(b))
+}
+
func TestParser_ParseStructArrayObject(t *testing.T) {
src := `
package api
@@ -2256,9 +1593,9 @@ type Response struct {
Code int
Table [][]string
Data []struct{
- Field1 uint
- Field2 string
- }
+ Field1 uint
+ Field2 string
+ }
}
// @Success 200 {object} Response
@@ -2303,12 +1640,11 @@ func Test(){
assert.NoError(t, err)
p := New()
- p.ParseType(f)
- err = p.ParseRouterAPIInfo("", f)
+ p.packages.CollectAstFile("api", "api/api.go", f)
+ _, err = p.packages.ParseTypes()
assert.NoError(t, err)
- typeSpec := p.TypeDefinitions["api"]["Response"]
- err = p.ParseDefinition("api", typeSpec.Name.Name, typeSpec)
+ err = p.ParseRouterAPIInfo("", f)
assert.NoError(t, err)
out, err := json.MarshalIndent(p.swagger.Definitions, "", " ")
@@ -2334,7 +1670,7 @@ func Test(){
package rest
type ResponseWrapper struct {
- Status string
+ Status string
Code int
Messages []string
Result interface{}
@@ -2367,17 +1703,16 @@ type ResponseWrapper struct {
f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments)
assert.NoError(t, err)
- parser.ParseType(f)
+ parser.packages.CollectAstFile("api", "api/api.go", f)
f2, err := goparser.ParseFile(token.NewFileSet(), "", restsrc, goparser.ParseComments)
assert.NoError(t, err)
- parser.ParseType(f2)
+ parser.packages.CollectAstFile("rest", "rest/rest.go", f2)
- err = parser.ParseRouterAPIInfo("", f)
+ _, err = parser.packages.ParseTypes()
assert.NoError(t, err)
- typeSpec := parser.TypeDefinitions["api"]["Response"]
- err = parser.ParseDefinition("api", typeSpec.Name.Name, typeSpec)
+ err = parser.ParseRouterAPIInfo("", f)
assert.NoError(t, err)
out, err := json.MarshalIndent(parser.swagger.Definitions, "", " ")
@@ -2423,7 +1758,6 @@ func Test(){
},
"test2": {
"description": "test2",
- "type": "object",
"$ref": "#/definitions/api.Child"
}
}
@@ -2434,12 +1768,11 @@ func Test(){
assert.NoError(t, err)
p := New()
- p.ParseType(f)
- err = p.ParseRouterAPIInfo("", f)
+ p.packages.CollectAstFile("api", "api/api.go", f)
+ _, err = p.packages.ParseTypes()
assert.NoError(t, err)
- typeSpec := p.TypeDefinitions["api"]["Parent"]
- err = p.ParseDefinition("api", typeSpec.Name.Name, typeSpec)
+ err = p.ParseRouterAPIInfo("", f)
assert.NoError(t, err)
out, err := json.MarshalIndent(p.swagger.Definitions, "", " ")
@@ -2527,7 +1860,6 @@ func Test(){
},
"test6": {
"description": "test6",
- "type": "object",
"$ref": "#/definitions/api.MyMapType"
},
"test7": {
@@ -2561,12 +1893,12 @@ func Test(){
assert.NoError(t, err)
p := New()
- p.ParseType(f)
- err = p.ParseRouterAPIInfo("", f)
+ p.packages.CollectAstFile("api", "api/api.go", f)
+
+ _, err = p.packages.ParseTypes()
assert.NoError(t, err)
- typeSpec := p.TypeDefinitions["api"]["Parent"]
- err = p.ParseDefinition("api", typeSpec.Name.Name, typeSpec)
+ err = p.ParseRouterAPIInfo("", f)
assert.NoError(t, err)
out, err := json.MarshalIndent(p.swagger.Definitions, "", " ")
@@ -2784,64 +2116,6 @@ func Test3(){
assert.NotNil(t, val.Delete)
}
-func TestSkip(t *testing.T) {
- folder1 := "/tmp/vendor"
- err := os.Mkdir(folder1, os.ModePerm)
- assert.NoError(t, err)
- f1, _ := os.Stat(folder1)
-
- parser := New()
-
- assert.True(t, parser.Skip(folder1, f1) == filepath.SkipDir)
- assert.NoError(t, os.Remove(folder1))
-
- folder2 := "/tmp/.git"
- err = os.Mkdir(folder2, os.ModePerm)
- assert.NoError(t, err)
- f2, _ := os.Stat(folder2)
-
- assert.True(t, parser.Skip(folder2, f2) == filepath.SkipDir)
- assert.NoError(t, os.Remove(folder2))
-
- currentPath := "./"
- currentPathInfo, _ := os.Stat(currentPath)
- assert.True(t, parser.Skip(currentPath, currentPathInfo) == nil)
-}
-
-func TestSkipMustParseVendor(t *testing.T) {
- folder1 := "/tmp/vendor"
- err := os.Mkdir(folder1, os.ModePerm)
- assert.NoError(t, err)
-
- f1, _ := os.Stat(folder1)
-
- parser := New()
- parser.ParseVendor = true
-
- assert.True(t, parser.Skip(folder1, f1) == nil)
- assert.NoError(t, os.Remove(folder1))
-
- folder2 := "/tmp/.git"
- err = os.Mkdir(folder2, os.ModePerm)
- assert.NoError(t, err)
-
- f2, _ := os.Stat(folder2)
-
- assert.True(t, parser.Skip(folder2, f2) == filepath.SkipDir)
- assert.NoError(t, os.Remove(folder2))
-
- currentPath := "./"
- currentPathInfo, _ := os.Stat(currentPath)
- assert.True(t, parser.Skip(currentPath, currentPathInfo) == nil)
-
- folder3 := "/tmp/test/vendor/github.com/swaggo/swag"
- assert.NoError(t, os.MkdirAll(folder3, os.ModePerm))
- f3, _ := os.Stat(folder3)
-
- assert.Nil(t, parser.Skip(folder3, f3))
- assert.NoError(t, os.RemoveAll("/tmp/test"))
-}
-
// func TestParseDeterministic(t *testing.T) {
// mainAPIFile := "main.go"
// for _, searchDir := range []string{
@@ -2855,7 +2129,7 @@ func TestSkipMustParseVendor(t *testing.T) {
// for i := 0; i < 100; i++ {
// p := New()
// p.PropNamingStrategy = PascalCase
-// err := p.ParseAPI(searchDir, mainAPIFile)
+// err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
// b, _ := json.MarshalIndent(p.swagger, "", " ")
// assert.NotEqual(t, "", string(b))
@@ -2874,7 +2148,7 @@ func TestApiParseTag(t *testing.T) {
mainAPIFile := "main.go"
p := New(SetMarkdownFileDirectory(searchDir))
p.PropNamingStrategy = PascalCase
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
if len(p.swagger.Tags) != 3 {
@@ -2898,12 +2172,21 @@ func TestApiParseTag(t *testing.T) {
}
}
+func TestApiParseTag_NonExistendTag(t *testing.T) {
+ searchDir := "testdata/tags_nonexistend_tag"
+ mainAPIFile := "main.go"
+ p := New(SetMarkdownFileDirectory(searchDir))
+ p.PropNamingStrategy = PascalCase
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
+ assert.Error(t, err)
+}
+
func TestParseTagMarkdownDescription(t *testing.T) {
searchDir := "testdata/tags"
mainAPIFile := "main.go"
p := New(SetMarkdownFileDirectory(searchDir))
p.PropNamingStrategy = PascalCase
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
if err != nil {
t.Error("Failed to parse api description: " + err.Error())
}
@@ -2923,7 +2206,7 @@ func TestParseApiMarkdownDescription(t *testing.T) {
mainAPIFile := "main.go"
p := New(SetMarkdownFileDirectory(searchDir))
p.PropNamingStrategy = PascalCase
- err := p.ParseAPI(searchDir, mainAPIFile)
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
if err != nil {
t.Error("Failed to parse api description: " + err.Error())
}
@@ -2937,7 +2220,7 @@ func TestIgnoreInvalidPkg(t *testing.T) {
searchDir := "testdata/deps_having_invalid_pkg"
mainAPIFile := "main.go"
p := New()
- if err := p.ParseAPI(searchDir, mainAPIFile); err != nil {
+ if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil {
t.Error("Failed to ignore valid pkg: " + err.Error())
}
}
@@ -2947,7 +2230,7 @@ func TestFixes432(t *testing.T) {
mainAPIFile := "cmd/main.go"
p := New()
- if err := p.ParseAPI(searchDir, mainAPIFile); err != nil {
+ if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil {
t.Error("Failed to ignore valid pkg: " + err.Error())
}
}
@@ -2958,7 +2241,7 @@ func TestParseOutsideDependencies(t *testing.T) {
p := New()
p.ParseDependency = true
- if err := p.ParseAPI(searchDir, mainAPIFile); err != nil {
+ if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil {
t.Error("Failed to parse api: " + err.Error())
}
}
@@ -2979,15 +2262,296 @@ type Student struct {
// @Router /test [get]
func Fun() {
+}
+`
+ expected := `{
+ "info": {
+ "contact": {}
+ },
+ "paths": {
+ "/test": {
+ "get": {
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "age",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "name": "name",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "name": "teachers",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": ""
+ }
+ }
+ }
+ }
+ }
+}`
+
+ f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments)
+ assert.NoError(t, err)
+
+ p := New()
+ p.packages.CollectAstFile("api", "api/api.go", f)
+
+ _, err = p.packages.ParseTypes()
+ assert.NoError(t, err)
+
+ err = p.ParseRouterAPIInfo("", f)
+ assert.NoError(t, err)
+
+ b, _ := json.MarshalIndent(p.swagger, "", " ")
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseRenamedStructDefinition(t *testing.T) {
+ src := `
+package main
+
+type Child struct {
+ Name string
+}//@name Student
+
+type Parent struct {
+ Name string
+ Child Child
+}//@name Teacher
+
+// @Param request body Parent true "query params"
+// @Success 200 {object} Parent
+// @Router /test [get]
+func Fun() {
+
+}
+`
+ f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments)
+ assert.NoError(t, err)
+
+ p := New()
+ p.packages.CollectAstFile("api", "api/api.go", f)
+ _, err = p.packages.ParseTypes()
+ assert.NoError(t, err)
+
+ err = p.ParseRouterAPIInfo("", f)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ teacher, ok := p.swagger.Definitions["Teacher"]
+ assert.True(t, ok)
+ ref := teacher.Properties["child"].SchemaProps.Ref
+ assert.Equal(t, "#/definitions/Student", ref.String())
+ _, ok = p.swagger.Definitions["Student"]
+ assert.True(t, ok)
+ path, ok := p.swagger.Paths.Paths["/test"]
+ assert.True(t, ok)
+ assert.Equal(t, "#/definitions/Teacher", path.Get.Parameters[0].Schema.Ref.String())
+ ref = path.Get.Responses.ResponsesProps.StatusCodeResponses[200].ResponseProps.Schema.Ref
+ assert.Equal(t, "#/definitions/Teacher", ref.String())
+}
+
+func TestParseJSONFieldString(t *testing.T) {
+ expected := `{
+ "swagger": "2.0",
+ "info": {
+ "description": "This is a sample server.",
+ "title": "Swagger Example API",
+ "contact": {},
+ "version": "1.0"
+ },
+ "host": "localhost:4000",
+ "basePath": "/",
+ "paths": {
+ "/do-something": {
+ "post": {
+ "description": "Does something",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "summary": "Call DoSomething",
+ "parameters": [
+ {
+ "description": "My Struct",
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/main.MyStruct"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/main.MyStruct"
+ }
+ },
+ "500": {
+ "description": ""
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "main.MyStruct": {
+ "type": "object",
+ "properties": {
+ "boolvar": {
+ "description": "boolean as a string",
+ "type": "string",
+ "example": "false"
+ },
+ "floatvar": {
+ "description": "float as a string",
+ "type": "string",
+ "example": "0"
+ },
+ "id": {
+ "type": "integer",
+ "format": "int64",
+ "example": 1
+ },
+ "myint": {
+ "description": "integer as string",
+ "type": "string",
+ "example": "0"
+ },
+ "name": {
+ "type": "string",
+ "example": "poti"
+ },
+ "truebool": {
+ "description": "boolean as a string",
+ "type": "string",
+ "example": "true"
+ }
+ }
+ }
+ }
+}`
+
+ searchDir := "testdata/json_field_string"
+ mainAPIFile := "main.go"
+ p := New()
+ err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
+ assert.NoError(t, err)
+ b, _ := json.MarshalIndent(p.swagger, "", " ")
+ assert.Equal(t, expected, string(b))
+}
+
+func TestParseSwaggerignoreForEmbedded(t *testing.T) {
+ src := `
+package main
+
+type Child struct {
+ ChildName string
+}//@name Student
+
+type Parent struct {
+ Name string
+ Child ` + "`swaggerignore:\"true\"`" + `
+}//@name Teacher
+
+// @Param request body Parent true "query params"
+// @Success 200 {object} Parent
+// @Router /test [get]
+func Fun() {
+
}
`
f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments)
assert.NoError(t, err)
p := New()
- p.ParseType(f)
+ p.packages.CollectAstFile("api", "api/api.go", f)
+ p.packages.ParseTypes()
err = p.ParseRouterAPIInfo("", f)
assert.NoError(t, err)
- assert.Equal(t, 3, len(p.swagger.Paths.Paths["/test"].Get.Parameters))
+ assert.NoError(t, err)
+ teacher, ok := p.swagger.Definitions["Teacher"]
+ assert.True(t, ok)
+
+ name, ok := teacher.Properties["name"]
+ assert.True(t, ok)
+ assert.Len(t, name.Type, 1)
+ assert.Equal(t, "string", name.Type[0])
+
+ childName, ok := teacher.Properties["childName"]
+ assert.False(t, ok)
+ assert.Empty(t, childName)
+}
+
+func TestDefineTypeOfExample(t *testing.T) {
+ var example interface{}
+ var err error
+
+ example, err = defineTypeOfExample("string", "", "example")
+ assert.NoError(t, err)
+ assert.Equal(t, example.(string), "example")
+
+ example, err = defineTypeOfExample("number", "", "12.34")
+ assert.NoError(t, err)
+ assert.Equal(t, example.(float64), 12.34)
+
+ example, err = defineTypeOfExample("boolean", "", "true")
+ assert.NoError(t, err)
+ assert.Equal(t, example.(bool), true)
+
+ example, err = defineTypeOfExample("array", "", "one,two,three")
+ assert.Error(t, err)
+ assert.Nil(t, example)
+
+ example, err = defineTypeOfExample("array", "string", "one,two,three")
+ assert.NoError(t, err)
+ arr := []string{}
+
+ for _, v := range example.([]interface{}) {
+ arr = append(arr, v.(string))
+ }
+
+ assert.Equal(t, arr, []string{"one", "two", "three"})
+
+ example, err = defineTypeOfExample("object", "", "key_one:one,key_two:two,key_three:three")
+ assert.Error(t, err)
+ assert.Nil(t, example)
+
+ example, err = defineTypeOfExample("object", "string", "key_one,key_two,key_three")
+ assert.Error(t, err)
+ assert.Nil(t, example)
+
+ example, err = defineTypeOfExample("object", "oops", "key_one:one,key_two:two,key_three:three")
+ assert.Error(t, err)
+ assert.Nil(t, example)
+
+ example, err = defineTypeOfExample("object", "string", "key_one:one,key_two:two,key_three:three")
+ assert.NoError(t, err)
+ obj := map[string]string{}
+
+ for k, v := range example.(map[string]interface{}) {
+ obj[k] = v.(string)
+ }
+
+ assert.Equal(t, obj, map[string]string{"key_one": "one", "key_two": "two", "key_three": "three"})
+
+ example, err = defineTypeOfExample("oops", "", "")
+ assert.Error(t, err)
+ assert.Nil(t, example)
}
diff --git a/property.go b/property.go
deleted file mode 100644
index 3a3fbbcbc..000000000
--- a/property.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package swag
-
-import (
- "errors"
- "fmt"
- "go/ast"
- "strings"
-)
-
-// ErrFailedConvertPrimitiveType Failed to convert for swag to interpretable type
-var ErrFailedConvertPrimitiveType = errors.New("swag property: failed convert primitive type")
-
-type propertyName struct {
- SchemaType string
- ArrayType string
- CrossPkg string
-}
-
-type propertyNewFunc func(schemeType string, crossPkg string) propertyName
-
-func newArrayProperty(schemeType string, crossPkg string) propertyName {
- return propertyName{
- SchemaType: "array",
- ArrayType: schemeType,
- CrossPkg: crossPkg,
- }
-}
-
-func newProperty(schemeType string, crossPkg string) propertyName {
- return propertyName{
- SchemaType: schemeType,
- ArrayType: "string",
- CrossPkg: crossPkg,
- }
-}
-
-func convertFromSpecificToPrimitive(typeName string) (string, error) {
- typeName = strings.ToUpper(typeName)
- switch typeName {
- case "TIME", "OBJECTID", "UUID":
- return "string", nil
- case "DECIMAL":
- return "number", nil
- }
- return "", ErrFailedConvertPrimitiveType
-}
-
-func parseFieldSelectorExpr(astTypeSelectorExpr *ast.SelectorExpr, parser *Parser, propertyNewFunc propertyNewFunc) propertyName {
- if primitiveType, err := convertFromSpecificToPrimitive(astTypeSelectorExpr.Sel.Name); err == nil {
- return propertyNewFunc(primitiveType, "")
- }
-
- if pkgName, ok := astTypeSelectorExpr.X.(*ast.Ident); ok {
- if typeDefinitions, ok := parser.TypeDefinitions[pkgName.Name][astTypeSelectorExpr.Sel.Name]; ok {
- if expr, ok := typeDefinitions.Type.(*ast.SelectorExpr); ok {
- if primitiveType, err := convertFromSpecificToPrimitive(expr.Sel.Name); err == nil {
- return propertyNewFunc(primitiveType, "")
- }
- }
- parser.ParseDefinition(pkgName.Name, astTypeSelectorExpr.Sel.Name, typeDefinitions)
- return propertyNewFunc(astTypeSelectorExpr.Sel.Name, pkgName.Name)
- }
- if aliasedNames, ok := parser.ImportAliases[pkgName.Name]; ok {
- for aliasedName := range aliasedNames {
- if typeDefinitions, ok := parser.TypeDefinitions[aliasedName][astTypeSelectorExpr.Sel.Name]; ok {
- if expr, ok := typeDefinitions.Type.(*ast.SelectorExpr); ok {
- if primitiveType, err := convertFromSpecificToPrimitive(expr.Sel.Name); err == nil {
- return propertyNewFunc(primitiveType, "")
- }
- }
- parser.ParseDefinition(aliasedName, astTypeSelectorExpr.Sel.Name, typeDefinitions)
- return propertyNewFunc(astTypeSelectorExpr.Sel.Name, aliasedName)
- }
- }
- }
- name := fmt.Sprintf("%s.%v", pkgName, astTypeSelectorExpr.Sel.Name)
- if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType {
- return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}
- }
- }
- return propertyName{SchemaType: "string", ArrayType: "string"}
-}
-
-// getPropertyName returns the string value for the given field if it exists
-// allowedValues: array, boolean, integer, null, number, object, string
-func getPropertyName(pkgName string, expr ast.Expr, parser *Parser) (propertyName, error) {
- switch tp := expr.(type) {
- case *ast.SelectorExpr:
- return parseFieldSelectorExpr(tp, parser, newProperty), nil
- case *ast.StarExpr:
- return getPropertyName(pkgName, tp.X, parser)
- case *ast.ArrayType:
- return getArrayPropertyName(pkgName, tp.Elt, parser), nil
- case *ast.MapType, *ast.StructType, *ast.InterfaceType:
- return propertyName{SchemaType: "object", ArrayType: "object"}, nil
- case *ast.FuncType:
- return propertyName{SchemaType: "func", ArrayType: ""}, nil
- case *ast.Ident:
- name := tp.Name
- // check if it is a custom type
- if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType {
- return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}, nil
- }
-
- name = TransToValidSchemeType(name)
- return propertyName{SchemaType: name, ArrayType: name}, nil
- default:
- return propertyName{}, errors.New("not supported" + fmt.Sprint(expr))
- }
-}
-
-func getArrayPropertyName(pkgName string, astTypeArrayElt ast.Expr, parser *Parser) propertyName {
- switch elt := astTypeArrayElt.(type) {
- case *ast.StructType, *ast.MapType, *ast.InterfaceType:
- return propertyName{SchemaType: "array", ArrayType: "object"}
- case *ast.ArrayType:
- return propertyName{SchemaType: "array", ArrayType: "array"}
- case *ast.StarExpr:
- return getArrayPropertyName(pkgName, elt.X, parser)
- case *ast.SelectorExpr:
- return parseFieldSelectorExpr(elt, parser, newArrayProperty)
- case *ast.Ident:
- name := elt.Name
- if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType {
- name = actualPrimitiveType
- } else {
- name = TransToValidSchemeType(elt.Name)
- }
- return propertyName{SchemaType: "array", ArrayType: name}
- default:
- name := fmt.Sprintf("%s", astTypeArrayElt)
- if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType {
- name = actualPrimitiveType
- } else {
- name = TransToValidSchemeType(name)
- }
- return propertyName{SchemaType: "array", ArrayType: name}
- }
-}
diff --git a/property_test.go b/property_test.go
deleted file mode 100644
index 2e65183b6..000000000
--- a/property_test.go
+++ /dev/null
@@ -1,322 +0,0 @@
-package swag
-
-import (
- "go/ast"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetPropertyNameSelectorExpr(t *testing.T) {
- input := &ast.SelectorExpr{
- X: &ast.Ident{
- NamePos: 1136,
- Name: "time",
- Obj: (*ast.Object)(nil),
- },
- Sel: &ast.Ident{
- NamePos: 1141,
- Name: "Time",
- Obj: (*ast.Object)(nil),
- },
- }
- expected := propertyName{
- "string",
- "string",
- "",
- }
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameIdentObjectId(t *testing.T) {
- input := &ast.SelectorExpr{
- X: &ast.Ident{
- NamePos: 1136,
- Name: "hoge",
- Obj: (*ast.Object)(nil),
- },
- Sel: &ast.Ident{
- NamePos: 1141,
- Name: "ObjectId",
- Obj: (*ast.Object)(nil),
- },
- }
- expected := propertyName{
- "string",
- "string",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameIdentUUID(t *testing.T) {
- input := &ast.SelectorExpr{
- X: &ast.Ident{
- NamePos: 1136,
- Name: "hoge",
- Obj: (*ast.Object)(nil),
- },
- Sel: &ast.Ident{
- NamePos: 1141,
- Name: "uuid",
- Obj: (*ast.Object)(nil),
- },
- }
- expected := propertyName{
- "string",
- "string",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameIdentDecimal(t *testing.T) {
- input := &ast.SelectorExpr{
- X: &ast.Ident{
- NamePos: 1136,
- Name: "hoge",
- Obj: (*ast.Object)(nil),
- },
- Sel: &ast.Ident{
- NamePos: 1141,
- Name: "Decimal",
- Obj: (*ast.Object)(nil),
- },
- }
- expected := propertyName{
- "number",
- "string",
- "",
- }
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameIdentTime(t *testing.T) {
- input := &ast.SelectorExpr{
- X: &ast.Ident{
- NamePos: 1136,
- Name: "hoge",
- Obj: (*ast.Object)(nil),
- },
- Sel: &ast.Ident{
- NamePos: 1141,
- Name: "Time",
- Obj: (*ast.Object)(nil),
- },
- }
- expected := propertyName{
- "string",
- "string",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, nil)
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameStarExprIdent(t *testing.T) {
- input := &ast.StarExpr{
- Star: 1026,
- X: &ast.Ident{
- NamePos: 1027,
- Name: "string",
- Obj: (*ast.Object)(nil),
- },
- }
- expected := propertyName{
- "string",
- "string",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameStarExprMap(t *testing.T) {
- input := &ast.StarExpr{
- Star: 1026,
- X: &ast.MapType{
- Map: 1027,
- Key: &ast.Ident{
- NamePos: 1034,
- Name: "string",
- Obj: (*ast.Object)(nil),
- },
- Value: &ast.Ident{
- NamePos: 1041,
- Name: "string",
- Obj: (*ast.Object)(nil),
- },
- },
- }
- expected := propertyName{
- "object",
- "object",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameArrayStarExpr(t *testing.T) {
- input := &ast.ArrayType{
- Lbrack: 465,
- Len: nil,
- Elt: &ast.StarExpr{
- X: &ast.Ident{
- NamePos: 467,
- Name: "string",
- Obj: (*ast.Object)(nil),
- },
- },
- }
- expected := propertyName{
- "array",
- "string",
- "",
- }
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameArrayStarExprSelector(t *testing.T) {
- input := &ast.ArrayType{
- Lbrack: 1111,
- Len: nil,
- Elt: &ast.StarExpr{
- X: &ast.SelectorExpr{
- X: &ast.Ident{
- NamePos: 1136,
- Name: "hoge",
- Obj: (*ast.Object)(nil),
- },
- Sel: &ast.Ident{
- NamePos: 1141,
- Name: "ObjectId",
- Obj: (*ast.Object)(nil),
- },
- },
- },
- }
- expected := propertyName{
- "array",
- "string",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameArrayStructType(t *testing.T) {
- input := &ast.ArrayType{
- Lbrack: 1111,
- Len: nil,
- Elt: &ast.StructType{},
- }
- expected := propertyName{
- "array",
- "object",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameMap(t *testing.T) {
- input := &ast.MapType{
- Key: &ast.Ident{
- Name: "string",
- },
- Value: &ast.Ident{
- Name: "string",
- },
- }
- expected := propertyName{
- "object",
- "object",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameStruct(t *testing.T) {
- input := &ast.StructType{}
- expected := propertyName{
- "object",
- "object",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameInterface(t *testing.T) {
- input := &ast.InterfaceType{}
- expected := propertyName{
- "object",
- "object",
- "",
- }
-
- propertyName, err := getPropertyName("test", input, New())
- assert.NoError(t, err)
- assert.Equal(t, expected, propertyName)
-}
-
-func TestGetPropertyNameChannel(t *testing.T) {
- input := &ast.ChanType{}
- _, err := getPropertyName("test", input, New())
- assert.Error(t, err)
-}
-
-func TestParseTag(t *testing.T) {
- searchDir := "testdata/tags"
- mainAPIFile := "main.go"
- p := New(SetMarkdownFileDirectory(searchDir))
- p.PropNamingStrategy = PascalCase
- err := p.ParseAPI(searchDir, mainAPIFile)
- assert.NoError(t, err)
-
- if len(p.swagger.Tags) != 3 {
- t.Log(len(p.swagger.Tags))
- t.Log("Number of tags did not match")
- t.FailNow()
- }
-
- dogs := p.swagger.Tags[0]
- if dogs.TagProps.Name != "dogs" || dogs.TagProps.Description != "Dogs are cool" {
- t.Log("Failed to parse dogs name or description")
- t.FailNow()
- }
-
- cats := p.swagger.Tags[1]
- if cats.TagProps.Name != "cats" || cats.TagProps.Description != "Cats are the devil" {
- t.Log("Failed to parse cats name or description")
- t.FailNow()
- }
-}
diff --git a/schema.go b/schema.go
index c05a08c12..adafe3deb 100644
--- a/schema.go
+++ b/schema.go
@@ -1,6 +1,32 @@
package swag
-import "fmt"
+import (
+ "errors"
+ "fmt"
+ "go/ast"
+ "strings"
+
+ "github.com/go-openapi/spec"
+)
+
+const (
+ //ARRAY array
+ ARRAY = "array"
+ //OBJECT object
+ OBJECT = "object"
+ //PRIMITIVE primitive
+ PRIMITIVE = "primitive"
+ //BOOLEAN boolean
+ BOOLEAN = "boolean"
+ //INTEGER integer
+ INTEGER = "integer"
+ //NUMBER number
+ NUMBER = "number"
+ //STRING string
+ STRING = "string"
+ //FUNC func
+ FUNC = "func"
+)
// CheckSchemaType checks if typeName is not a name of primitive type
func CheckSchemaType(typeName string) error {
@@ -13,7 +39,7 @@ func CheckSchemaType(typeName string) error {
// IsSimplePrimitiveType determine whether the type name is a simple primitive type
func IsSimplePrimitiveType(typeName string) bool {
switch typeName {
- case "string", "number", "integer", "boolean":
+ case STRING, NUMBER, INTEGER, BOOLEAN:
return true
default:
return false
@@ -23,7 +49,7 @@ func IsSimplePrimitiveType(typeName string) bool {
// IsPrimitiveType determine whether the type name is a primitive type
func IsPrimitiveType(typeName string) bool {
switch typeName {
- case "string", "number", "integer", "boolean", "array", "object", "func":
+ case STRING, NUMBER, INTEGER, BOOLEAN, ARRAY, OBJECT, FUNC:
return true
default:
return false
@@ -32,24 +58,24 @@ func IsPrimitiveType(typeName string) bool {
// IsNumericType determines whether the swagger type name is a numeric type
func IsNumericType(typeName string) bool {
- return typeName == "integer" || typeName == "number"
+ return typeName == INTEGER || typeName == NUMBER
}
// TransToValidSchemeType indicates type will transfer golang basic type to swagger supported type.
func TransToValidSchemeType(typeName string) string {
switch typeName {
case "uint", "int", "uint8", "int8", "uint16", "int16", "byte":
- return "integer"
+ return INTEGER
case "uint32", "int32", "rune":
- return "integer"
+ return INTEGER
case "uint64", "int64":
- return "integer"
+ return INTEGER
case "float32", "float64":
- return "number"
+ return NUMBER
case "bool":
- return "boolean"
+ return BOOLEAN
case "string":
- return "string"
+ return STRING
default:
return typeName // to support user defined types
}
@@ -79,3 +105,84 @@ func IsGolangPrimitiveType(typeName string) bool {
return false
}
}
+
+// TransToValidCollectionFormat determine valid collection format
+func TransToValidCollectionFormat(format string) string {
+ switch format {
+ case "csv", "multi", "pipes", "tsv", "ssv":
+ return format
+ default:
+ return ""
+ }
+}
+
+// TypeDocName get alias from comment '// @name ', otherwise the original type name to display in doc
+func TypeDocName(pkgName string, spec *ast.TypeSpec) string {
+ if spec != nil {
+ if spec.Comment != nil {
+ for _, comment := range spec.Comment.List {
+ text := strings.TrimSpace(comment.Text)
+ text = strings.TrimLeft(text, "//")
+ text = strings.TrimSpace(text)
+ texts := strings.Split(text, " ")
+ if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" {
+ return texts[1]
+ }
+ }
+ }
+ if spec.Name != nil {
+ return fullTypeName(strings.Split(pkgName, ".")[0], spec.Name.Name)
+ }
+ }
+
+ return pkgName
+}
+
+//RefSchema build a reference schema
+func RefSchema(refType string) *spec.Schema {
+ return spec.RefSchema("#/definitions/" + refType)
+}
+
+//PrimitiveSchema build a primitive schema
+func PrimitiveSchema(refType string) *spec.Schema {
+ return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{refType}}}
+}
+
+// BuildCustomSchema build custom schema specified by tag swaggertype
+func BuildCustomSchema(types []string) (*spec.Schema, error) {
+ if len(types) == 0 {
+ return nil, nil
+ }
+
+ switch types[0] {
+ case PRIMITIVE:
+ if len(types) == 1 {
+ return nil, errors.New("need primitive type after primitive")
+ }
+ return BuildCustomSchema(types[1:])
+ case ARRAY:
+ if len(types) == 1 {
+ return nil, errors.New("need array item type after array")
+ }
+ schema, err := BuildCustomSchema(types[1:])
+ if err != nil {
+ return nil, err
+ }
+ return spec.ArrayProperty(schema), nil
+ case OBJECT:
+ if len(types) == 1 {
+ return PrimitiveSchema(types[0]), nil
+ }
+ schema, err := BuildCustomSchema(types[1:])
+ if err != nil {
+ return nil, err
+ }
+ return spec.MapProperty(schema), nil
+ default:
+ err := CheckSchemaType(types[0])
+ if err != nil {
+ return nil, err
+ }
+ return PrimitiveSchema(types[0]), nil
+ }
+}
diff --git a/schema_test.go b/schema_test.go
index a0993e0c9..a0d64247f 100644
--- a/schema_test.go
+++ b/schema_test.go
@@ -3,32 +3,44 @@ package swag
import (
"testing"
+ "github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func TestValidDataType(t *testing.T) {
- assert.NoError(t, CheckSchemaType("string"))
- assert.NoError(t, CheckSchemaType("number"))
- assert.NoError(t, CheckSchemaType("integer"))
- assert.NoError(t, CheckSchemaType("boolean"))
- assert.NoError(t, CheckSchemaType("array"))
- assert.NoError(t, CheckSchemaType("object"))
+ assert.NoError(t, CheckSchemaType(STRING))
+ assert.NoError(t, CheckSchemaType(NUMBER))
+ assert.NoError(t, CheckSchemaType(INTEGER))
+ assert.NoError(t, CheckSchemaType(BOOLEAN))
+ assert.NoError(t, CheckSchemaType(ARRAY))
+ assert.NoError(t, CheckSchemaType(OBJECT))
assert.Error(t, CheckSchemaType("oops"))
}
func TestTransToValidSchemeType(t *testing.T) {
- assert.Equal(t, TransToValidSchemeType("uint"), "integer")
- assert.Equal(t, TransToValidSchemeType("uint32"), "integer")
- assert.Equal(t, TransToValidSchemeType("uint64"), "integer")
- assert.Equal(t, TransToValidSchemeType("float32"), "number")
- assert.Equal(t, TransToValidSchemeType("bool"), "boolean")
- assert.Equal(t, TransToValidSchemeType("string"), "string")
+ assert.Equal(t, TransToValidSchemeType("uint"), INTEGER)
+ assert.Equal(t, TransToValidSchemeType("uint32"), INTEGER)
+ assert.Equal(t, TransToValidSchemeType("uint64"), INTEGER)
+ assert.Equal(t, TransToValidSchemeType("float32"), NUMBER)
+ assert.Equal(t, TransToValidSchemeType("bool"), BOOLEAN)
+ assert.Equal(t, TransToValidSchemeType("string"), STRING)
// should accept any type, due to user defined types
TransToValidSchemeType("oops")
}
+func TestTransToValidCollectionFormat(t *testing.T) {
+ assert.Equal(t, TransToValidCollectionFormat("csv"), "csv")
+ assert.Equal(t, TransToValidCollectionFormat("multi"), "multi")
+ assert.Equal(t, TransToValidCollectionFormat("pipes"), "pipes")
+ assert.Equal(t, TransToValidCollectionFormat("tsv"), "tsv")
+ assert.Equal(t, TransToValidSchemeType("string"), STRING)
+
+ // should accept any type, due to user defined types
+ assert.Equal(t, TransToValidCollectionFormat("oops"), "")
+}
+
func TestIsGolangPrimitiveType(t *testing.T) {
assert.Equal(t, IsGolangPrimitiveType("uint"), true)
@@ -50,9 +62,66 @@ func TestIsGolangPrimitiveType(t *testing.T) {
assert.Equal(t, IsGolangPrimitiveType("oops"), false)
}
+func TestIsSimplePrimitiveType(t *testing.T) {
+
+ assert.Equal(t, IsSimplePrimitiveType("string"), true)
+ assert.Equal(t, IsSimplePrimitiveType("number"), true)
+ assert.Equal(t, IsSimplePrimitiveType("integer"), true)
+ assert.Equal(t, IsSimplePrimitiveType("boolean"), true)
+
+ assert.Equal(t, IsSimplePrimitiveType("oops"), false)
+}
+
+func TestBuildCustomSchema(t *testing.T) {
+ var schema *spec.Schema
+ var err error
+
+ schema, err = BuildCustomSchema([]string{})
+ assert.NoError(t, err)
+ assert.Nil(t, schema)
+
+ schema, err = BuildCustomSchema([]string{"primitive"})
+ assert.Error(t, err)
+ assert.Nil(t, schema)
+
+ schema, err = BuildCustomSchema([]string{"primitive", "oops"})
+ assert.Error(t, err)
+ assert.Nil(t, schema)
+
+ schema, err = BuildCustomSchema([]string{"primitive", "string"})
+ assert.NoError(t, err)
+ assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"string"})
+
+ schema, err = BuildCustomSchema([]string{"array"})
+ assert.Error(t, err)
+ assert.Nil(t, schema)
+
+ schema, err = BuildCustomSchema([]string{"array", "oops"})
+ assert.Error(t, err)
+ assert.Nil(t, schema)
+
+ schema, err = BuildCustomSchema([]string{"array", "string"})
+ assert.NoError(t, err)
+ assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"array"})
+ assert.Equal(t, schema.SchemaProps.Items.Schema.SchemaProps.Type, spec.StringOrArray{"string"})
+
+ schema, err = BuildCustomSchema([]string{"object"})
+ assert.NoError(t, err)
+ assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"object"})
+
+ schema, err = BuildCustomSchema([]string{"object", "oops"})
+ assert.Error(t, err)
+ assert.Nil(t, schema)
+
+ schema, err = BuildCustomSchema([]string{"object", "string"})
+ assert.NoError(t, err)
+ assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"object"})
+ assert.Equal(t, schema.SchemaProps.AdditionalProperties.Schema.Type, spec.StringOrArray{"string"})
+}
+
func TestIsNumericType(t *testing.T) {
- assert.Equal(t, IsNumericType("integer"), true)
- assert.Equal(t, IsNumericType("number"), true)
+ assert.Equal(t, IsNumericType(INTEGER), true)
+ assert.Equal(t, IsNumericType(NUMBER), true)
- assert.Equal(t, IsNumericType("string"), false)
+ assert.Equal(t, IsNumericType(STRING), false)
}
diff --git a/testdata/alias_import/api/api.go b/testdata/alias_import/api/api.go
index 0e24583f3..a01a3ddd5 100644
--- a/testdata/alias_import/api/api.go
+++ b/testdata/alias_import/api/api.go
@@ -1,10 +1,11 @@
package api
import (
- "github.com/gin-gonic/gin"
+ "log"
+ "net/http"
+
"github.com/swaggo/swag/testdata/alias_import/data"
"github.com/swaggo/swag/testdata/alias_type/types"
- "log"
)
// @Summary Get application
@@ -14,7 +15,7 @@ import (
// @Produce json
// @Success 200 {object} data.ApplicationResponse "ok"
// @Router /testapi/application [get]
-func GetApplication(c *gin.Context) {
+func GetApplication(w http.ResponseWriter, r *http.Request) {
var foo = data.ApplicationResponse{
Application: types.Application{
Name: "name",
diff --git a/testdata/alias_import/data/applicationresponse.go b/testdata/alias_import/data/applicationresponse.go
index b6bcf3c58..da4170508 100644
--- a/testdata/alias_import/data/applicationresponse.go
+++ b/testdata/alias_import/data/applicationresponse.go
@@ -5,6 +5,8 @@ import (
)
type ApplicationResponse struct {
+ typesapplication.TypeToEmbed
+
Application typesapplication.Application `json:"application"`
ApplicationArray []typesapplication.Application `json:"application_array"`
ApplicationTime typesapplication.DateOnly `json:"application_time"`
diff --git a/testdata/alias_import/expected.json b/testdata/alias_import/expected.json
index 4b6232987..fab871b41 100644
--- a/testdata/alias_import/expected.json
+++ b/testdata/alias_import/expected.json
@@ -45,7 +45,6 @@
"type": "object",
"properties": {
"application": {
- "type": "object",
"$ref": "#/definitions/types.Application"
},
"application_array": {
@@ -56,6 +55,9 @@
},
"application_time": {
"type": "string"
+ },
+ "embedded": {
+ "type": "string"
}
}
},
diff --git a/testdata/alias_import/main.go b/testdata/alias_import/main.go
index bc8768d94..bf1b5c93b 100644
--- a/testdata/alias_import/main.go
+++ b/testdata/alias_import/main.go
@@ -1,7 +1,8 @@
package alias_import
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/alias_import/api"
)
@@ -20,7 +21,6 @@ import (
// @host petstore.swagger.io
// @BasePath /v2
func main() {
- r := gin.New()
- r.GET("/testapi/application", api.GetApplication)
- r.Run()
+ http.HandleFunc("/testapi/application", api.GetApplication)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/alias_import/types/application.go b/testdata/alias_import/types/application.go
index ceb7120ec..ddff98ef0 100644
--- a/testdata/alias_import/types/application.go
+++ b/testdata/alias_import/types/application.go
@@ -7,3 +7,7 @@ type Application struct {
}
type DateOnly time.Time
+
+type TypeToEmbed struct {
+ Embedded string
+}
diff --git a/testdata/alias_type/api/api.go b/testdata/alias_type/api/api.go
index 1d6199436..3c9638e06 100644
--- a/testdata/alias_type/api/api.go
+++ b/testdata/alias_type/api/api.go
@@ -1,10 +1,11 @@
package api
import (
- "github.com/gin-gonic/gin"
- "github.com/swaggo/swag/testdata/alias_type/data"
"log"
+ "net/http"
"time"
+
+ "github.com/swaggo/swag/testdata/alias_type/data"
)
/*// @Summary Get time as string
@@ -14,7 +15,7 @@ import (
// @Produce json
// @Success 200 {object} data.StringAlias "ok"
// @Router /testapi/time-as-string [get]
-func GetTimeAsStringAlias(c *gin.Context) {
+func GetTimeAsStringAlias(w http.ResponseWriter, r *http.Request) {
var foo data.StringAlias = "test"
log.Println(foo)
//write your code
@@ -27,7 +28,7 @@ func GetTimeAsStringAlias(c *gin.Context) {
// @Produce json
// @Success 200 {object} data.DateOnly "ok"
// @Router /testapi/time-as-time [get]
-func GetTimeAsTimeAlias(c *gin.Context) {
+func GetTimeAsTimeAlias(w http.ResponseWriter, r *http.Request) {
var foo = data.DateOnly(time.Now())
log.Println(foo)
//write your code
@@ -40,7 +41,7 @@ func GetTimeAsTimeAlias(c *gin.Context) {
// @Produce json
// @Success 200 {object} data.TimeContainer "ok"
// @Router /testapi/time-as-time-container [get]
-func GetTimeAsTimeContainer(c *gin.Context) {
+func GetTimeAsTimeContainer(w http.ResponseWriter, r *http.Request) {
now := time.Now()
var foo = data.TimeContainer{
Name: "test",
diff --git a/testdata/alias_type/main.go b/testdata/alias_type/main.go
index 94affaf99..f136fbb95 100644
--- a/testdata/alias_type/main.go
+++ b/testdata/alias_type/main.go
@@ -1,7 +1,8 @@
package alias_type
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/alias_type/api"
)
@@ -20,7 +21,6 @@ import (
// @host petstore.swagger.io
// @BasePath /v2
func main() {
- r := gin.New()
- r.GET("/testapi/time-as-time-container", api.GetTimeAsTimeContainer)
- r.Run()
+ http.HandleFunc("/testapi/time-as-time-container", api.GetTimeAsTimeContainer)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/code_examples/api/api1.go b/testdata/code_examples/api/api1.go
new file mode 100644
index 000000000..4bbd5512f
--- /dev/null
+++ b/testdata/code_examples/api/api1.go
@@ -0,0 +1,13 @@
+package api
+
+import (
+ _ "github.com/swaggo/swag/testdata/conflict_name/model"
+ "net/http"
+)
+
+// @Description Check if Health of service it's OK!
+// @Router /health [get]
+// @x-codeSamples file
+func Get1(w http.ResponseWriter, r *http.Request) {
+
+}
diff --git a/testdata/code_examples/example.json b/testdata/code_examples/example.json
new file mode 100644
index 000000000..26e1cef56
--- /dev/null
+++ b/testdata/code_examples/example.json
@@ -0,0 +1,4 @@
+{
+ "lang": "JavaScript",
+ "source": "console.log('Hello World');"
+}
\ No newline at end of file
diff --git a/testdata/code_examples/main.go b/testdata/code_examples/main.go
new file mode 100644
index 000000000..476d5baa2
--- /dev/null
+++ b/testdata/code_examples/main.go
@@ -0,0 +1,8 @@
+package main
+
+// @title Swag test
+// @version 1.0
+// @description test for conflict name
+func main() {
+
+}
diff --git a/testdata/composition/api/api.go b/testdata/composition/api/api.go
index 0845b6b92..bd3c62487 100644
--- a/testdata/composition/api/api.go
+++ b/testdata/composition/api/api.go
@@ -1,7 +1,8 @@
package api
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/composition/common"
)
@@ -11,16 +12,29 @@ type Foo struct {
type Bar struct {
Field2 string
}
+type EmptyStruct struct {
+}
+type unexported struct {
+}
+type Ignored struct {
+ Field5 string `swaggerignore:"true"`
+}
type FooBar struct {
Foo
Bar
+ EmptyStruct
+ unexported
+ Ignored
}
type FooBarPointer struct {
*common.ResponseFormat
*Foo
*Bar
+ *EmptyStruct
+ *unexported
+ *Ignored
}
type BarMap map[string]Bar
@@ -39,7 +53,7 @@ type MapValue struct {
// @Produce json
// @Success 200 {object} api.Foo
// @Router /testapi/get-foo [get]
-func GetFoo(c *gin.Context) {
+func GetFoo(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = Foo{}
}
@@ -50,7 +64,7 @@ func GetFoo(c *gin.Context) {
// @Produce json
// @Success 200 {object} api.Bar
// @Router /testapi/get-bar [get]
-func GetBar(c *gin.Context) {
+func GetBar(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = Bar{}
}
@@ -61,7 +75,7 @@ func GetBar(c *gin.Context) {
// @Produce json
// @Success 200 {object} api.FooBar
// @Router /testapi/get-foobar [get]
-func GetFooBar(c *gin.Context) {
+func GetFooBar(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = FooBar{}
}
@@ -72,7 +86,7 @@ func GetFooBar(c *gin.Context) {
// @Produce json
// @Success 200 {object} api.FooBarPointer
// @Router /testapi/get-foobar-pointer [get]
-func GetFooBarPointer(c *gin.Context) {
+func GetFooBarPointer(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = FooBarPointer{}
}
@@ -83,7 +97,7 @@ func GetFooBarPointer(c *gin.Context) {
// @Produce json
// @Success 200 {object} api.BarMap
// @Router /testapi/get-barmap [get]
-func GetBarMap(c *gin.Context) {
+func GetBarMap(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = BarMap{}
}
@@ -94,7 +108,7 @@ func GetBarMap(c *gin.Context) {
// @Produce json
// @Success 200 {object} api.FooBarMap
// @Router /testapi/get-foobarmap [get]
-func GetFooBarMap(c *gin.Context) {
+func GetFooBarMap(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = FooBarMap{}
}
diff --git a/testdata/composition/expected.json b/testdata/composition/expected.json
index d14bfbd91..f260e066c 100644
--- a/testdata/composition/expected.json
+++ b/testdata/composition/expected.json
@@ -5,7 +5,6 @@
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
- "license": {},
"version": "1.0"
},
"host": "petstore.swagger.io",
diff --git a/testdata/composition/main.go b/testdata/composition/main.go
index 23e08ee00..e0dc20d64 100644
--- a/testdata/composition/main.go
+++ b/testdata/composition/main.go
@@ -1,7 +1,8 @@
package composition
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/composition/api"
)
@@ -14,11 +15,10 @@ import (
// @BasePath /v2
func main() {
- r := gin.New()
- r.GET("/testapi/get-foo", api.GetFoo)
- r.GET("/testapi/get-bar", api.GetBar)
- r.GET("/testapi/get-foobar", api.GetFooBar)
- r.GET("/testapi/get-foobar-pointer", api.GetFooBarPointer)
- r.GET("/testapi/get-barmap", api.GetBarMap)
- r.Run()
+ http.handleFunc("/testapi/get-foo", api.GetFoo)
+ http.handleFunc("/testapi/get-bar", api.GetBar)
+ http.handleFunc("/testapi/get-foobar", api.GetFooBar)
+ http.handleFunc("/testapi/get-foobar-pointer", api.GetFooBarPointer)
+ http.handleFunc("/testapi/get-barmap", api.GetBarMap)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/conflict_name/api/api1.go b/testdata/conflict_name/api/api1.go
new file mode 100644
index 000000000..7b04198a7
--- /dev/null
+++ b/testdata/conflict_name/api/api1.go
@@ -0,0 +1,17 @@
+package api
+
+import (
+ _ "github.com/swaggo/swag/testdata/conflict_name/model"
+ "net/http"
+)
+
+// @Tags Health
+// @Description Check if Health of service it's OK!
+// @ID health
+// @Accept json
+// @Produce json
+// @Success 200 {object} model.ErrorsResponse
+// @Router /health [get]
+func Get1(w http.ResponseWriter, r *http.Request) {
+
+}
diff --git a/testdata/conflict_name/api/api2.go b/testdata/conflict_name/api/api2.go
new file mode 100644
index 000000000..e71b7e6c1
--- /dev/null
+++ b/testdata/conflict_name/api/api2.go
@@ -0,0 +1,17 @@
+package api
+
+import (
+ _ "github.com/swaggo/swag/testdata/conflict_name/model2"
+ "net/http"
+)
+
+// @Tags Health
+// @Description Check if Health of service it's OK!
+// @ID health2
+// @Accept json
+// @Produce json
+// @Success 200 {object} model.ErrorsResponse
+// @Router /health2 [get]
+func Get2(w http.ResponseWriter, r *http.Request) {
+
+}
diff --git a/testdata/conflict_name/expected.json b/testdata/conflict_name/expected.json
new file mode 100644
index 000000000..74f046c46
--- /dev/null
+++ b/testdata/conflict_name/expected.json
@@ -0,0 +1,113 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "description": "test for conflict name",
+ "title": "Swag test",
+ "contact": {},
+ "version": "1.0"
+ },
+ "paths": {
+ "/health": {
+ "get": {
+ "description": "Check if Health of service it's OK!",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Health"
+ ],
+ "operationId": "health",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse"
+ }
+ }
+ }
+ }
+ },
+ "/health2": {
+ "get": {
+ "description": "Check if Health of service it's OK!",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Health"
+ ],
+ "operationId": "health2",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "github.com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse": {
+ "type": "object",
+ "properties": {
+ "newTime": {
+ "$ref": "#/definitions/model.MyPayload"
+ }
+ }
+ },
+ "github.com_swaggo_swag_testdata_conflict_name_model.MyStruct": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "github.com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse": {
+ "type": "object",
+ "properties": {
+ "newTime": {
+ "$ref": "#/definitions/model.MyPayload2"
+ }
+ }
+ },
+ "github.com_swaggo_swag_testdata_conflict_name_model2.MyStruct": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "model.MyPayload": {
+ "type": "object",
+ "properties": {
+ "my": {
+ "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model.MyStruct"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "model.MyPayload2": {
+ "type": "object",
+ "properties": {
+ "my": {
+ "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model2.MyStruct"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/testdata/conflict_name/main.go b/testdata/conflict_name/main.go
new file mode 100644
index 000000000..476d5baa2
--- /dev/null
+++ b/testdata/conflict_name/main.go
@@ -0,0 +1,8 @@
+package main
+
+// @title Swag test
+// @version 1.0
+// @description test for conflict name
+func main() {
+
+}
diff --git a/testdata/conflict_name/model/model.go b/testdata/conflict_name/model/model.go
new file mode 100644
index 000000000..a55440ee6
--- /dev/null
+++ b/testdata/conflict_name/model/model.go
@@ -0,0 +1,14 @@
+package model
+
+type MyStruct struct {
+ Name string `json:"name"`
+}
+
+type MyPayload struct {
+ My MyStruct
+ Name string `json:"name"`
+}
+
+type ErrorsResponse struct {
+ NewTime MyPayload
+}
diff --git a/testdata/conflict_name/model2/model.go b/testdata/conflict_name/model2/model.go
new file mode 100644
index 000000000..bd67e781e
--- /dev/null
+++ b/testdata/conflict_name/model2/model.go
@@ -0,0 +1,14 @@
+package model
+
+type MyStruct struct {
+ Name string `json:"name"`
+}
+
+type MyPayload2 struct {
+ My MyStruct
+ Name string `json:"name"`
+}
+
+type ErrorsResponse struct {
+ NewTime MyPayload2
+}
diff --git a/testdata/duplicated/api/api.go b/testdata/duplicated/api/api.go
new file mode 100644
index 000000000..829a43048
--- /dev/null
+++ b/testdata/duplicated/api/api.go
@@ -0,0 +1,15 @@
+package api
+
+import "net/http"
+
+// @Description get Foo
+// @ID get-foo
+// @Success 200 {string} string
+// @Router /testapi/get-foo [get]
+func GetFoo(w http.ResponseWriter, r *http.Request) {}
+
+// @Description post Bar
+// @ID get-foo
+// @Success 200 {string} string
+// @Router /testapi/post-bar [post]
+func PostBar(w http.ResponseWriter, r *http.Request) {}
diff --git a/testdata/duplicated/main.go b/testdata/duplicated/main.go
new file mode 100644
index 000000000..d1e16664f
--- /dev/null
+++ b/testdata/duplicated/main.go
@@ -0,0 +1,21 @@
+package composition
+
+import (
+ "net/http"
+
+ "github.com/swaggo/swag/testdata/duplicated/api"
+)
+
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server
+// @termsOfService http://swagger.io/terms/
+
+// @host petstore.swagger.io
+// @BasePath /v2
+
+func main() {
+ http.HandleFunc("/testapi/get-foo", api.GetFoo)
+ http.HandleFunc("/testapi/post-bar", api.PostBar)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/testdata/duplicated2/api/api.go b/testdata/duplicated2/api/api.go
new file mode 100644
index 000000000..ef1fcc29c
--- /dev/null
+++ b/testdata/duplicated2/api/api.go
@@ -0,0 +1,33 @@
+package api
+
+import "net/http"
+
+// @Description put Foo
+// @ID put-foo
+// @Success 200 {string} string
+// @Router /testapi/put-foo [put]
+func PutFoo(w http.ResponseWriter, r *http.Request) {}
+
+// @Description head Foo
+// @ID head-foo
+// @Success 200 {string} string
+// @Router /testapi/head-foo [head]
+func HeadFoo(w http.ResponseWriter, r *http.Request) {}
+
+// @Description options Foo
+// @ID options-foo
+// @Success 200 {string} string
+// @Router /testapi/options-foo [options]
+func OptionsFoo(w http.ResponseWriter, r *http.Request) {}
+
+// @Description patch Foo
+// @ID patch-foo
+// @Success 200 {string} string
+// @Router /testapi/patch-foo [patch]
+func PatchFoo(w http.ResponseWriter, r *http.Request) {}
+
+// @Description delete Foo
+// @ID put-foo
+// @Success 200 {string} string
+// @Router /testapi/delete-foo [delete]
+func DeleteFoo(w http.ResponseWriter, r *http.Request) {}
diff --git a/testdata/duplicated2/main.go b/testdata/duplicated2/main.go
new file mode 100644
index 000000000..90988f76d
--- /dev/null
+++ b/testdata/duplicated2/main.go
@@ -0,0 +1,24 @@
+package composition
+
+import (
+ "net/http"
+
+ "github.com/swaggo/swag/testdata/duplicated2/api"
+)
+
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server
+// @termsOfService http://swagger.io/terms/
+
+// @host petstore.swagger.io
+// @BasePath /v2
+
+func main() {
+ http.HandleFunc("/testapi/put-foo", api.PutFoo)
+ http.HandleFunc("/testapi/head-foo", api.HeadFoo)
+ http.HandleFunc("/testapi/options-foo", api.OptionsFoo)
+ http.HandleFunc("/testapi/patch-foo", api.PatchFoo)
+ http.HandleFunc("/testapi/delete-foo", api.DeleteFoo)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/testdata/json_field_string/main.go b/testdata/json_field_string/main.go
new file mode 100755
index 000000000..564503a2a
--- /dev/null
+++ b/testdata/json_field_string/main.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+)
+
+type MyStruct struct {
+ ID int `json:"id" example:"1" format:"int64"`
+ Name string `json:"name" example:"poti"`
+ Intvar int `json:"myint,string"` // integer as string
+ Boolvar bool `json:",string"` // boolean as a string
+ TrueBool bool `json:"truebool,string" example:"true"` // boolean as a string
+ Floatvar float64 `json:",string"` // float as a string
+}
+
+// @Summary Call DoSomething
+// @Description Does something
+// @Accept json
+// @Produce json
+// @Param body body MyStruct true "My Struct"
+// @Success 200 {object} MyStruct
+// @Failure 500
+// @Router /do-something [post]
+func DoSomething(w http.ResponseWriter, r *http.Request) {
+ objectFromJSON := new(MyStruct)
+ if err := json.NewDecoder(r.Body).Decode(&objectFromJSON); err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Print(err.Error())
+ }
+ json.NewEncoder(w).Encode(ojbectFromJSON)
+}
+
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server.
+// @host localhost:4000
+// @basePath /
+func main() {
+ http.HandleFund("/do-something", DoSomething)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/testdata/main.go b/testdata/main.go
index f6ef4e21f..9ee7c9339 100644
--- a/testdata/main.go
+++ b/testdata/main.go
@@ -42,8 +42,10 @@ package main
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
+// @x-tokenname id_token
// @x-google-endpoints [{"name":"name.endpoints.environment.cloud.goog","allowCors":true}]
// @x-google-marks "marks values"
+// @x-logo {"url":"https://redocly.github.io/redoc/petstore-logo.png", "altText": "Petstore logo", "backgroundColor": "#FFFFFF"}
func main() {}
diff --git a/testdata/model_not_under_root/cmd/api/api.go b/testdata/model_not_under_root/cmd/api/api.go
deleted file mode 100644
index efc56c736..000000000
--- a/testdata/model_not_under_root/cmd/api/api.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package api
-
-import (
- "log"
-
- "github.com/gin-gonic/gin"
- "github.com/swaggo/swag/testdata/model_not_under_root/data"
-)
-
-// @Summary Add a new pet to the store
-// @Description get string by ID
-// @ID get-string-by-int
-// @Accept json
-// @Produce json
-// @Param some_id path int true "Some ID" Format(int64)
-// @Success 200 {object} data.Foo "ok"
-// @Router /testapi/get-string-by-int/{some_id} [get]
-func GetStringByInt(c *gin.Context) {
- var foo data.Foo
- log.Println(foo)
- //write your code
-}
-
-// @Summary Upload file
-// @Description Upload file
-// @ID file.upload
-// @Accept json
-// @Produce json
-// @Param data body data.Foo true "Foo to create"
-// @Success 200 {string} string "ok"
-// @Router /file/upload [post]
-func Upload(ctx *gin.Context) {
- //write your code
-}
diff --git a/testdata/model_not_under_root/data/foo.go b/testdata/model_not_under_root/data/foo.go
deleted file mode 100644
index 7fe7208f9..000000000
--- a/testdata/model_not_under_root/data/foo.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package data
-
-type Foo struct {
- Field1 string `json:"field1"`
-}
diff --git a/testdata/nested/api/api.go b/testdata/nested/api/api.go
index e7869da85..0dec600ac 100644
--- a/testdata/nested/api/api.go
+++ b/testdata/nested/api/api.go
@@ -1,7 +1,8 @@
package api
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/nested2"
)
@@ -23,7 +24,7 @@ type Bar struct {
// @Produce json
// @Success 200 {object} api.Foo
// @Router /testapi/get-foo [get]
-func GetFoo(c *gin.Context) {
+func GetFoo(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = Foo{}
}
diff --git a/testdata/nested/expected.json b/testdata/nested/expected.json
index e6ebb1cc4..6ed9d51b1 100644
--- a/testdata/nested/expected.json
+++ b/testdata/nested/expected.json
@@ -5,7 +5,6 @@
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
- "license": {},
"version": "1.0"
},
"host": "petstore.swagger.io",
@@ -66,11 +65,9 @@
"type": "string"
},
"insideData": {
- "type": "object",
"$ref": "#/definitions/api.Bar"
},
"outsideData": {
- "type": "object",
"$ref": "#/definitions/nested2.Body"
}
}
diff --git a/testdata/nested/main.go b/testdata/nested/main.go
index 1649b9bbb..6e63913b3 100644
--- a/testdata/nested/main.go
+++ b/testdata/nested/main.go
@@ -1,7 +1,8 @@
package composition
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/nested/api"
)
@@ -14,7 +15,6 @@ import (
// @BasePath /v2
func main() {
- r := gin.New()
- r.GET("/testapi/get-foo", api.GetFoo)
- r.Run()
+ http.HandleFunc("/testapi/get-foo", api.GetFoo)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/nested2/inner/data.go b/testdata/nested2/inner/data.go
new file mode 100644
index 000000000..8a7eab5c4
--- /dev/null
+++ b/testdata/nested2/inner/data.go
@@ -0,0 +1 @@
+package inner
diff --git a/testdata/non_exported_json_fields/main.go b/testdata/non_exported_json_fields/main.go
new file mode 100644
index 000000000..b02dcc56c
--- /dev/null
+++ b/testdata/non_exported_json_fields/main.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "net/http"
+)
+
+type MyStruct struct {
+ ID int `json:"id" example:"1" format:"int64"`
+ // Post name
+ Name string `json:"name" example:"poti"`
+ // Post data
+ Data struct {
+ // Post tag
+ Tag []string `json:"name"`
+ } `json:"data"`
+ // not-exported variable, for internal use only, not marshaled
+ internal1 string
+ internal2 int
+ internal3 bool
+ internal4 struct {
+ NestedInternal string
+ }
+}
+
+// @Summary Call DoSomething
+// @Description Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON
+// @Accept json
+// @Produce json
+// @Success 200 {object} MyStruct
+// @Router /so-something [get]
+func DoSomething(w http.ResponseWriter, r *http.Request) {
+ //write your code
+}
+
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server.
+// @host localhost:4000
+// @basePath /api
+func main() {
+ http.HandleFunc("/do-something", DoSomething)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/testdata/pare_outside_dependencies/cmd/main.go b/testdata/pare_outside_dependencies/cmd/main.go
index a93ea32c3..9f22eb586 100644
--- a/testdata/pare_outside_dependencies/cmd/main.go
+++ b/testdata/pare_outside_dependencies/cmd/main.go
@@ -1,7 +1,8 @@
package main
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/example/basic/api"
)
@@ -20,10 +21,8 @@ import (
// @host petstore.swagger.io
// @BasePath /v2
func main() {
- r := gin.New()
- r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt)
- r.GET("//testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString)
- r.POST("/testapi/upload", api.Upload)
- r.Run()
-
+ http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
+ http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
+ http.HandleFunc("/testapi/upload", api.Upload)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/simple/api/api.go b/testdata/simple/api/api.go
index ba6734d71..bde8f703b 100644
--- a/testdata/simple/api/api.go
+++ b/testdata/simple/api/api.go
@@ -1,7 +1,9 @@
package api
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
+ _ "github.com/swaggo/swag/testdata/simple/web"
)
// @Summary Add a new pet to the store
@@ -15,7 +17,7 @@ import (
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
-func GetStringByInt(c *gin.Context) {
+func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
@@ -25,8 +27,8 @@ func GetStringByInt(c *gin.Context) {
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
-// @Param offset query int true "Offset" Mininum(0) default(0)
-// @Param limit query int true "Limit" Maxinum(50) default(10)
+// @Param offset query int true "Offset" Minimum(0) default(0)
+// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
@@ -38,7 +40,7 @@ func GetStringByInt(c *gin.Context) {
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
-func GetStructArrayByString(c *gin.Context) {
+func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
@@ -53,39 +55,45 @@ func GetStructArrayByString(c *gin.Context) {
// @Failure 401 {array} string
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
-func Upload(ctx *gin.Context) {
+func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
+// @Router /AnonymousField [get]
func AnonymousField() {
}
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
+// @Router /Pet2 [get]
func Pet2() {
}
// @Summary Use IndirectRecursiveTest
// @Success 200 {object} web.IndirectRecursiveTest
+// @Router /IndirectRecursiveTest [get]
func IndirectRecursiveTest() {
}
// @Summary Use Tags
// @Success 200 {object} web.Tags
+// @Router /Tags [get]
func Tags() {
}
// @Summary Use CrossAlias
// @Success 200 {object} web.CrossAlias
+// @Router /CrossAlias [get]
func CrossAlias() {
}
// @Summary Use AnonymousStructArray
// @Success 200 {object} web.AnonymousStructArray
+// @Router /AnonymousStructArray [get]
func AnonymousStructArray() {
}
@@ -94,16 +102,19 @@ type Pet3 struct {
}
// @Success 200 {object} web.Pet5a "ok"
+// @Router /GetPet5a [get]
func GetPet5a() {
}
// @Success 200 {object} web.Pet5b "ok"
+// @Router /GetPet5b [get]
func GetPet5b() {
}
// @Success 200 {object} web.Pet5c "ok"
+// @Router /GetPet5c [get]
func GetPet5c() {
}
@@ -111,6 +122,7 @@ func GetPet5c() {
type SwagReturn []map[string]string
// @Success 200 {object} api.SwagReturn "ok"
+// @Router /GetPet6MapString [get]
func GetPet6MapString() {
}
diff --git a/testdata/simple/expected.json b/testdata/simple/expected.json
new file mode 100644
index 000000000..efd201783
--- /dev/null
+++ b/testdata/simple/expected.json
@@ -0,0 +1,739 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "description": "This is a sample server Petstore server.",
+ "title": "Swagger Example API",
+ "termsOfService": "http://swagger.io/terms/",
+ "contact": {
+ "name": "API Support",
+ "url": "http://www.swagger.io/support",
+ "email": "support@swagger.io"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ },
+ "version": "1.0"
+ },
+ "host": "petstore.swagger.io",
+ "basePath": "/v2",
+ "paths": {
+ "/AnonymousField": {
+ "get": {
+ "summary": "use Anonymous field",
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "$ref": "#/definitions/web.RevValue"
+ }
+ }
+ }
+ }
+ },
+ "/AnonymousStructArray": {
+ "get": {
+ "summary": "Use AnonymousStructArray",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/CrossAlias": {
+ "get": {
+ "summary": "Use CrossAlias",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/web.CrossAlias"
+ }
+ }
+ }
+ }
+ },
+ "/GetPet5a": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "$ref": "#/definitions/web.Pet5a"
+ }
+ }
+ }
+ }
+ },
+ "/GetPet5b": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "$ref": "#/definitions/web.Pet5b"
+ }
+ }
+ }
+ }
+ },
+ "/GetPet5c": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "$ref": "#/definitions/web.Pet5c"
+ }
+ }
+ }
+ }
+ },
+ "/GetPet6MapString": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/IndirectRecursiveTest": {
+ "get": {
+ "summary": "Use IndirectRecursiveTest",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/web.IndirectRecursiveTest"
+ }
+ }
+ }
+ }
+ },
+ "/Pet2": {
+ "get": {
+ "summary": "use pet2",
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "$ref": "#/definitions/web.Pet2"
+ }
+ }
+ }
+ }
+ },
+ "/Tags": {
+ "get": {
+ "summary": "Use Tags",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/web.Tag"
+ }
+ }
+ }
+ }
+ }
+ },
+ "/file/upload": {
+ "post": {
+ "description": "Upload file",
+ "consumes": [
+ "multipart/form-data"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "summary": "Upload file",
+ "operationId": "file.upload",
+ "parameters": [
+ {
+ "type": "file",
+ "description": "this is a test file",
+ "name": "file",
+ "in": "formData",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "400": {
+ "description": "We need ID!!",
+ "schema": {
+ "$ref": "#/definitions/web.APIError"
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "404": {
+ "description": "Can not find ID",
+ "schema": {
+ "$ref": "#/definitions/web.APIError"
+ }
+ }
+ }
+ }
+ },
+ "/testapi/get-string-by-int/{some_id}": {
+ "get": {
+ "description": "get string by ID",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "summary": "Add a new pet to the store",
+ "operationId": "get-string-by-int",
+ "parameters": [
+ {
+ "type": "integer",
+ "format": "int64",
+ "description": "Some ID",
+ "name": "some_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Some ID",
+ "name": "some_id",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/web.Pet"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "400": {
+ "description": "We need ID!!",
+ "schema": {
+ "$ref": "#/definitions/web.APIError"
+ }
+ },
+ "404": {
+ "description": "Can not find ID",
+ "schema": {
+ "$ref": "#/definitions/web.APIError"
+ }
+ }
+ }
+ }
+ },
+ "/testapi/get-struct-array-by-string/{some_id}": {
+ "get": {
+ "security": [
+ {
+ "ApiKeyAuth": []
+ },
+ {
+ "BasicAuth": []
+ },
+ {
+ "OAuth2Application": [
+ "write"
+ ]
+ },
+ {
+ "OAuth2Implicit": [
+ "read",
+ "admin"
+ ]
+ },
+ {
+ "OAuth2AccessCode": [
+ "read"
+ ]
+ },
+ {
+ "OAuth2Password": [
+ "admin"
+ ]
+ }
+ ],
+ "description": "get struct array by ID",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "get-struct-array-by-string",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Some ID",
+ "name": "some_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "enum": [
+ 1,
+ 2,
+ 3
+ ],
+ "type": "integer",
+ "description": "Category",
+ "name": "category",
+ "in": "query",
+ "required": true
+ },
+ {
+ "minimum": 0,
+ "type": "integer",
+ "default": 0,
+ "description": "Offset",
+ "name": "offset",
+ "in": "query",
+ "required": true
+ },
+ {
+ "maximum": 50,
+ "type": "integer",
+ "default": 10,
+ "description": "Limit",
+ "name": "limit",
+ "in": "query",
+ "required": true
+ },
+ {
+ "maxLength": 50,
+ "minLength": 1,
+ "type": "string",
+ "default": "\"\"",
+ "description": "q",
+ "name": "q",
+ "in": "query",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "ok",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "400": {
+ "description": "We need ID!!",
+ "schema": {
+ "$ref": "#/definitions/web.APIError"
+ }
+ },
+ "404": {
+ "description": "Can not find ID",
+ "schema": {
+ "$ref": "#/definitions/web.APIError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "cross.Cross": {
+ "type": "object",
+ "properties": {
+ "Array": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "String": {
+ "type": "string"
+ }
+ }
+ },
+ "web.APIError": {
+ "type": "object",
+ "properties": {
+ "CreatedAt": {
+ "type": "string"
+ },
+ "ErrorCode": {
+ "type": "integer"
+ },
+ "ErrorMessage": {
+ "type": "string"
+ }
+ }
+ },
+ "web.CrossAlias": {
+ "type": "object",
+ "properties": {
+ "Array": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "String": {
+ "type": "string"
+ }
+ }
+ },
+ "web.IndirectRecursiveTest": {
+ "type": "object",
+ "properties": {
+ "Tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/web.Tag"
+ }
+ }
+ }
+ },
+ "web.Pet": {
+ "type": "object",
+ "required": [
+ "name",
+ "photo_urls"
+ ],
+ "properties": {
+ "category": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "example": 1
+ },
+ "name": {
+ "type": "string",
+ "example": "category_name"
+ },
+ "photo_urls": {
+ "type": "array",
+ "format": "url",
+ "items": {
+ "type": "string"
+ },
+ "example": [
+ "http://test/image/1.jpg",
+ "http://test/image/2.jpg"
+ ]
+ },
+ "small_category": {
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "example": 1
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 16,
+ "minLength": 4,
+ "example": "detail_category_name"
+ },
+ "photo_urls": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "example": [
+ "http://test/image/1.jpg",
+ "http://test/image/2.jpg"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "data": {
+ "type": "object"
+ },
+ "decimal": {
+ "type": "number"
+ },
+ "enum_array": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "enum": [
+ 1,
+ 2,
+ 3,
+ 5,
+ 7
+ ]
+ }
+ },
+ "id": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true,
+ "example": 1
+ },
+ "int_array": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2
+ ]
+ },
+ "is_alive": {
+ "type": "boolean",
+ "default": true,
+ "example": true
+ },
+ "name": {
+ "type": "string",
+ "example": "poti"
+ },
+ "pets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/web.Pet2"
+ }
+ },
+ "pets2": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/web.Pet2"
+ }
+ },
+ "photo_urls": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "example": [
+ "http://test/image/1.jpg",
+ "http://test/image/2.jpg"
+ ]
+ },
+ "price": {
+ "type": "number",
+ "maximum": 1000,
+ "minimum": 1,
+ "example": 3.25
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "healthy",
+ "ill"
+ ]
+ },
+ "string_map": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "example": {
+ "key1": "value",
+ "key2": "value2"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/web.Tag"
+ }
+ },
+ "uuid": {
+ "type": "string"
+ }
+ }
+ },
+ "web.Pet2": {
+ "type": "object",
+ "properties": {
+ "deleted_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "middlename": {
+ "type": "string",
+ "x-abc": "def",
+ "x-nullable": true
+ }
+ }
+ },
+ "web.Pet5a": {
+ "type": "object",
+ "required": [
+ "name",
+ "odd"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "odd": {
+ "type": "boolean"
+ }
+ }
+ },
+ "web.Pet5b": {
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "web.Pet5c": {
+ "type": "object",
+ "required": [
+ "name",
+ "odd"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "odd": {
+ "type": "boolean"
+ }
+ }
+ },
+ "web.RevValue": {
+ "type": "object",
+ "properties": {
+ "Data": {
+ "type": "integer"
+ },
+ "Err": {
+ "type": "integer"
+ },
+ "Status": {
+ "type": "boolean"
+ },
+ "cross": {
+ "$ref": "#/definitions/cross.Cross"
+ },
+ "crosses": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cross.Cross"
+ }
+ }
+ }
+ },
+ "web.Tag": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "pets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/web.Pet"
+ }
+ }
+ }
+ }
+ },
+ "securityDefinitions": {
+ "ApiKeyAuth": {
+ "type": "apiKey",
+ "name": "Authorization",
+ "in": "header"
+ },
+ "BasicAuth": {
+ "type": "basic"
+ },
+ "OAuth2AccessCode": {
+ "type": "oauth2",
+ "flow": "accessCode",
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "admin": " Grants read and write access to administrative information"
+ }
+ },
+ "OAuth2Application": {
+ "type": "oauth2",
+ "flow": "application",
+ "authorizationUrl": "",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "admin": " Grants read and write access to administrative information",
+ "write": " Grants write access"
+ }
+ },
+ "OAuth2Implicit": {
+ "type": "oauth2",
+ "flow": "implicit",
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "scopes": {
+ "admin": " Grants read and write access to administrative information",
+ "write": " Grants write access"
+ }
+ },
+ "OAuth2Password": {
+ "type": "oauth2",
+ "flow": "password",
+ "authorizationUrl": "",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "admin": " Grants read and write access to administrative information",
+ "read": " Grants read access",
+ "write": " Grants write access"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/testdata/simple/main.go b/testdata/simple/main.go
index d68402270..824a017c5 100644
--- a/testdata/simple/main.go
+++ b/testdata/simple/main.go
@@ -1,7 +1,8 @@
package main
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/simple/api"
)
@@ -47,9 +48,8 @@ import (
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
- r := gin.New()
- r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt)
- r.GET("/testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString)
- r.POST("/testapi/upload", api.Upload)
- r.Run()
+ http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
+ http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
+ http.HandleFunc("/testapi/upload", api.Upload)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/simple/web/handler.go b/testdata/simple/web/handler.go
index ece1628dc..6c4d39e43 100644
--- a/testdata/simple/web/handler.go
+++ b/testdata/simple/web/handler.go
@@ -3,7 +3,7 @@ package web
import (
"time"
- "github.com/satori/go.uuid"
+ "github.com/gofrs/uuid"
"github.com/shopspring/decimal"
"github.com/swaggo/swag/testdata/simple/cross"
)
@@ -20,20 +20,21 @@ type Pet struct {
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
} `json:"small_category"`
} `json:"category"`
- Name string `json:"name" example:"poti" binding:"required"`
- PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"`
- Tags []Tag `json:"tags"`
- Pets *[]Pet2 `json:"pets"`
- Pets2 []*Pet2 `json:"pets2"`
- Status string `json:"status" enums:"healthy,ill"`
- Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000"`
- IsAlive bool `json:"is_alive" example:"true" default:"true"`
- Data interface{} `json:"data"`
- Hidden string `json:"-"`
- UUID uuid.UUID `json:"uuid"`
- Decimal decimal.Decimal `json:"decimal"`
- IntArray []int `json:"int_array" example:"1,2"`
- EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"`
+ Name string `json:"name" example:"poti" binding:"required"`
+ PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"`
+ Tags []Tag `json:"tags"`
+ Pets *[]Pet2 `json:"pets"`
+ Pets2 []*Pet2 `json:"pets2"`
+ Status string `json:"status" enums:"healthy,ill"`
+ Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000"`
+ IsAlive bool `json:"is_alive" example:"true" default:"true"`
+ Data interface{} `json:"data"`
+ Hidden string `json:"-"`
+ UUID uuid.UUID `json:"uuid"`
+ Decimal decimal.Decimal `json:"decimal"`
+ IntArray []int `json:"int_array" example:"1,2"`
+ StringMap map[string]string `json:"string_map" example:"key1:value,key2:value2"`
+ EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"`
}
type Tag struct {
diff --git a/testdata/simple2/api/api.go b/testdata/simple2/api/api.go
index bed956f38..f65c534c1 100644
--- a/testdata/simple2/api/api.go
+++ b/testdata/simple2/api/api.go
@@ -1,7 +1,7 @@
package api
import (
- "github.com/gin-gonic/gin"
+ "net/http"
)
// @Summary Add a new pet to the store
@@ -15,7 +15,7 @@ import (
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
-func GetStringByInt(c *gin.Context) {
+func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
@@ -25,8 +25,8 @@ func GetStringByInt(c *gin.Context) {
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
-// @Param offset query int true "Offset" Mininum(0) default(0)
-// @Param limit query int true "Limit" Maxinum(50) default(10)
+// @Param offset query int true "Offset" Minimum(0) default(0)
+// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
@@ -38,7 +38,7 @@ func GetStringByInt(c *gin.Context) {
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
-func GetStructArrayByString(c *gin.Context) {
+func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
@@ -52,7 +52,7 @@ func GetStructArrayByString(c *gin.Context) {
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
-func Upload(ctx *gin.Context) {
+func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
diff --git a/testdata/simple2/main.go b/testdata/simple2/main.go
index e506504a9..5858e02eb 100644
--- a/testdata/simple2/main.go
+++ b/testdata/simple2/main.go
@@ -1,7 +1,8 @@
package main
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/simple2/api"
)
@@ -47,9 +48,8 @@ import (
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
- r := gin.New()
- r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt)
- r.GET("//testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString)
- r.POST("/testapi/upload", api.Upload)
- r.Run()
+ http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
+ http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
+ http.HandleFunc("/testapi/upload", api.Upload)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/simple2/web/handler.go b/testdata/simple2/web/handler.go
index d9a568692..bf5923d6c 100644
--- a/testdata/simple2/web/handler.go
+++ b/testdata/simple2/web/handler.go
@@ -6,7 +6,7 @@ import (
"strconv"
"time"
- uuid "github.com/satori/go.uuid"
+ uuid "github.com/gofrs/uuid"
"github.com/shopspring/decimal"
)
@@ -53,8 +53,8 @@ type Pet struct {
Hidden string `json:"-"`
UUID uuid.UUID
Decimal decimal.Decimal
- customString CustomString
- customStringArr []CustomString
+ CustomString CustomString
+ CustomStringArr []CustomString
NullInt sql.NullInt64 `swaggertype:"integer"`
Coeffs []big.Float `swaggertype:"array,number"`
Birthday TimestampTime `swaggertype:"primitive,integer"`
diff --git a/testdata/simple3/api/api.go b/testdata/simple3/api/api.go
index bed956f38..10db26060 100644
--- a/testdata/simple3/api/api.go
+++ b/testdata/simple3/api/api.go
@@ -1,8 +1,6 @@
package api
-import (
- "github.com/gin-gonic/gin"
-)
+import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
@@ -15,7 +13,7 @@ import (
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
-func GetStringByInt(c *gin.Context) {
+func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
@@ -25,8 +23,8 @@ func GetStringByInt(c *gin.Context) {
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
-// @Param offset query int true "Offset" Mininum(0) default(0)
-// @Param limit query int true "Limit" Maxinum(50) default(10)
+// @Param offset query int true "Offset" Minimum(0) default(0)
+// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
@@ -38,7 +36,7 @@ func GetStringByInt(c *gin.Context) {
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
-func GetStructArrayByString(c *gin.Context) {
+func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
@@ -52,7 +50,7 @@ func GetStructArrayByString(c *gin.Context) {
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
-func Upload(ctx *gin.Context) {
+func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
diff --git a/testdata/simple3/main.go b/testdata/simple3/main.go
index 38f8ef4a9..307cff1dd 100644
--- a/testdata/simple3/main.go
+++ b/testdata/simple3/main.go
@@ -1,7 +1,8 @@
package main
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/simple3/api"
)
@@ -47,9 +48,8 @@ import (
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
- r := gin.New()
- r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt)
- r.GET("//testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString)
- r.POST("/testapi/upload", api.Upload)
- r.Run()
+ http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
+ http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
+ http.HandleFunc("/testapi/upload", api.Upload)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/simple3/web/handler.go b/testdata/simple3/web/handler.go
index 0447e4d26..505b7620b 100644
--- a/testdata/simple3/web/handler.go
+++ b/testdata/simple3/web/handler.go
@@ -3,7 +3,7 @@ package web
import (
"time"
- uuid "github.com/satori/go.uuid"
+ uuid "github.com/gofrs/uuid"
"github.com/shopspring/decimal"
)
diff --git a/testdata/simple_cgo/api/api.go b/testdata/simple_cgo/api/api.go
new file mode 100644
index 000000000..d81ae1990
--- /dev/null
+++ b/testdata/simple_cgo/api/api.go
@@ -0,0 +1,17 @@
+package api
+
+import "net/http"
+
+// @Summary Add a new pet to the store
+// @Description get string by ID
+// @ID get-string-by-int
+// @Accept json
+// @Produce json
+// @Param some_id path int true "Some ID" Format(int64)
+// @Param some_id body int true "Some ID"
+// @Success 200 {string} string "ok"
+// @Failure 400 {object} string "We need ID!!"
+// @Failure 404 {object} string "Can not find ID"
+// @Router /testapi/get-string-by-int/{some_id} [get]
+func GetStringByInt(w http.ResponseWriter, r *http.Request) {
+}
diff --git a/testdata/model_not_under_root/cmd/main.go b/testdata/simple_cgo/main.go
similarity index 85%
rename from testdata/model_not_under_root/cmd/main.go
rename to testdata/simple_cgo/main.go
index 03d7c3621..971ae70ed 100644
--- a/testdata/model_not_under_root/cmd/main.go
+++ b/testdata/simple_cgo/main.go
@@ -1,8 +1,18 @@
package main
+/*
+#include
+
+void Hello(){
+ printf("Hello world\n");
+}
+*/
+import "C"
+
import (
- "github.com/gin-gonic/gin"
- "github.com/swaggo/swag/testdata/model_not_under_root/cmd/api"
+ "net/http"
+
+ "github.com/swaggo/swag/testdata/simple_cgo/api"
)
// @title Swagger Example API
@@ -47,7 +57,8 @@ import (
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
- r := gin.New()
- r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt)
- r.Run()
+ C.Hello()
+
+ http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/struct_comment/api/api.go b/testdata/struct_comment/api/api.go
index 539f90923..f96433d2d 100644
--- a/testdata/struct_comment/api/api.go
+++ b/testdata/struct_comment/api/api.go
@@ -1,8 +1,6 @@
package api
-import (
- "github.com/gin-gonic/gin"
-)
+import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
@@ -13,6 +11,6 @@ import (
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /posts/{post_id} [get]
-func GetPost(c *gin.Context) {
+func GetPost(w http.ResponseWriter, r *http.Request) {
//write your code
}
diff --git a/testdata/struct_comment/main.go b/testdata/struct_comment/main.go
index 7fa1a2771..6994817b5 100644
--- a/testdata/struct_comment/main.go
+++ b/testdata/struct_comment/main.go
@@ -1,7 +1,8 @@
package main
import (
- "github.com/gin-gonic/gin"
+ "net/http"
+
"github.com/swaggo/swag/testdata/struct_comment/api"
)
@@ -11,7 +12,6 @@ import (
// @host localhost:4000
// @basePath /api
func main() {
- r := gin.New()
- r.GET("/posts/:post_id", api.GetPost)
- r.Run()
+ http.HandleFunc("/posts/", api.GetPost)
+ http.ListenAndServe(":8080", nil)
}
diff --git a/testdata/tags/cats.md b/testdata/tags/cats.md
new file mode 100644
index 000000000..1fcfb09b0
--- /dev/null
+++ b/testdata/tags/cats.md
@@ -0,0 +1,3 @@
+## Cats
+
+Cats are also very cool!
\ No newline at end of file
diff --git a/testdata/tags/main.go b/testdata/tags/main.go
index 381d545f9..d1507ebe0 100644
--- a/testdata/tags/main.go
+++ b/testdata/tags/main.go
@@ -8,5 +8,5 @@ package main
// @tag.docs.url https://google.de
// @tag.docs.description google is super useful to find out that cats are evil!
// @tag.name apes
-// @tag.description.markdown
+// @tag.description Apes are also cool
func main() {}
diff --git a/testdata/tags2/apes.md b/testdata/tags2/apes.md
new file mode 100644
index 000000000..5e6434d9f
--- /dev/null
+++ b/testdata/tags2/apes.md
@@ -0,0 +1,3 @@
+## Apes
+
+Apes are very cool!
\ No newline at end of file
diff --git a/testdata/tags2/api.md b/testdata/tags2/api.md
new file mode 100644
index 000000000..94c5bda83
--- /dev/null
+++ b/testdata/tags2/api.md
@@ -0,0 +1,5 @@
+## CoolApi Title
+
+### Cool API SubTitle
+
+We love markdown!
\ No newline at end of file
diff --git a/testdata/tags2/main.go b/testdata/tags2/main.go
new file mode 100644
index 000000000..381d545f9
--- /dev/null
+++ b/testdata/tags2/main.go
@@ -0,0 +1,12 @@
+package main
+
+// @description.markdown
+// @tag.name dogs
+// @tag.description Dogs are cool
+// @tag.name cats
+// @tag.description Cats are the devil
+// @tag.docs.url https://google.de
+// @tag.docs.description google is super useful to find out that cats are evil!
+// @tag.name apes
+// @tag.description.markdown
+func main() {}
diff --git a/testdata/tags_nonexistend_tag/apes.md b/testdata/tags_nonexistend_tag/apes.md
new file mode 100644
index 000000000..5e6434d9f
--- /dev/null
+++ b/testdata/tags_nonexistend_tag/apes.md
@@ -0,0 +1,3 @@
+## Apes
+
+Apes are very cool!
\ No newline at end of file
diff --git a/testdata/tags_nonexistend_tag/api.md b/testdata/tags_nonexistend_tag/api.md
new file mode 100644
index 000000000..94c5bda83
--- /dev/null
+++ b/testdata/tags_nonexistend_tag/api.md
@@ -0,0 +1,5 @@
+## CoolApi Title
+
+### Cool API SubTitle
+
+We love markdown!
\ No newline at end of file
diff --git a/testdata/tags_nonexistend_tag/main.go b/testdata/tags_nonexistend_tag/main.go
new file mode 100644
index 000000000..5e8b6191b
--- /dev/null
+++ b/testdata/tags_nonexistend_tag/main.go
@@ -0,0 +1,11 @@
+package main
+
+// @description.markdown
+// @tag.name dogs
+// @tag.description Dogs are cool
+// @tag.name cats
+// @tag.description Cats are the devil
+// @tag.docs.url https://google.de
+// @tag.docs.description google is super useful to find out that cats are evil!
+// @tag.description.markdown
+func main() {}
diff --git a/types.go b/types.go
new file mode 100644
index 000000000..cf849e2bf
--- /dev/null
+++ b/types.go
@@ -0,0 +1,59 @@
+package swag
+
+import (
+ "github.com/go-openapi/spec"
+ "go/ast"
+)
+
+//Schema parsed schema
+type Schema struct {
+ PkgPath string //package import path used to rename Name of a definition int case of conflict
+ Name string //Name in definitions
+ *spec.Schema //
+}
+
+//TypeSpecDef the whole information of a typeSpec
+type TypeSpecDef struct {
+ //path of package starting from under ${GOPATH}/src or from module path in go.mod
+ PkgPath string
+
+ //ast file where TypeSpec is
+ File *ast.File
+
+ //the TypeSpec of this type definition
+ TypeSpec *ast.TypeSpec
+}
+
+//Name name of the typeSpec
+func (t *TypeSpecDef) Name() string {
+ return t.TypeSpec.Name.Name
+}
+
+//FullName full name of the typeSpec
+func (t *TypeSpecDef) FullName() string {
+ return fullTypeName(t.File.Name.Name, t.TypeSpec.Name.Name)
+}
+
+//AstFileInfo information of a ast.File
+type AstFileInfo struct {
+ //File ast.File
+ File *ast.File
+
+ //Path path of the ast.File
+ Path string
+
+ //PackagePath package import path of the ast.File
+ PackagePath string
+}
+
+//PackageDefinitions files and definition in a package
+type PackageDefinitions struct {
+ //package name
+ Name string
+
+ //files in this package, map key is file's relative path starting package path
+ Files map[string]*ast.File
+
+ //definitions in this package, map key is typeName
+ TypeDefinitions map[string]*TypeSpecDef
+}
diff --git a/version.go b/version.go
index 60c01d518..70c9b4ab9 100644
--- a/version.go
+++ b/version.go
@@ -1,4 +1,4 @@
package swag
// Version of swag
-const Version = "v1.6.5"
+const Version = "v1.7.0"