chore: add multiple api example (#207)

* chore: add multiple api example
* chore: update README.md
This commit is contained in:
Bogdan U 2022-04-22 16:50:33 +03:00 committed by GitHub
parent bd7f2153bf
commit 88c9ed2643
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 581 additions and 36 deletions

View File

@ -143,6 +143,9 @@ Demo project tree, `swag init` is run at relative `.`
└── main.go └── main.go
``` ```
## Multiple APIs
This feature where introduced in swag v1.7.9
## Configuration ## Configuration
You can configure Swagger using different configuration options You can configure Swagger using different configuration options

View File

@ -0,0 +1,23 @@
# Multiple API feature
Since swag 1.7.9 we are allowing registration of multiple endpoints into the same server.
Generate documentation for v1 endpoints
```shell
swag i -g main.go -dir api/v1 --instanceName v1
```
Generate documentation for v2 endpoints
```shell
swag i -g main.go -dir api/v2 --instanceName v2
```
Run example
```shell
go run main.go
```
Now you can access the v1 swagger here [http://localhost:8080/swagger/v1/index.html](http://localhost:8080/swagger/v1/index.html) ,
and v2 swagger here [http://localhost:8080/swagger/v2/index.html](http://localhost:8080/swagger/v2/index.html)

View File

@ -0,0 +1,26 @@
package v1
import (
"github.com/gin-gonic/gin"
)
type Book struct {
ID int `json:"id,omitempty"`
Title string `json:"title"`
Author string `json:"author"`
Year *uint16 `json:"year"`
}
//
// @Summary Get a list of books in the the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Success 200 {array} Book "ok"
// @Router /books [get]
func GetBooks(ctx *gin.Context) {
ctx.JSON(200, []Book{
{ID: 1, Title: "Book 1", Author: "Author 1", Year: nil},
{ID: 2, Title: "Book 2", Author: "Author 2", Year: nil},
})
}

View File

@ -0,0 +1,25 @@
package v1
import (
"github.com/gin-gonic/gin"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample 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
// @BasePath /v1
func Register(router *gin.Engine) {
v1 := router.Group("v1")
v1.GET("/books", GetBooks)
}

View File

@ -0,0 +1,26 @@
package v2
import (
"github.com/gin-gonic/gin"
)
type Book struct {
ID int `json:"id,omitempty"`
Title string `json:"title"`
Author string `json:"author"`
Year *uint16 `json:"year"`
}
//
// @Summary Get a list of books in the the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Success 200 {array} Book "ok"
// @Router /books [get]
func GetBooks(ctx *gin.Context) {
ctx.JSON(200, []Book{
{ID: 1, Title: "Book 3", Author: "Author 3", Year: nil},
{ID: 2, Title: "Book 4", Author: "Author 4", Year: nil},
})
}

View File

@ -0,0 +1,25 @@
package v2
import (
"github.com/gin-gonic/gin"
)
// @title Swagger Example API
// @version 2.0
// @description This is a sample 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
// @BasePath /v2
func Register(router *gin.Engine) {
v2 := router.Group("v2")
v2.GET("/books", GetBooks)
}

View File

@ -0,0 +1,87 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplatev1 = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"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": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/books": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get a list of books in the the store",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/v1.Book"
}
}
}
}
}
}
},
"definitions": {
"v1.Book": {
"type": "object",
"properties": {
"author": {
"type": "string"
},
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"year": {
"type": "integer"
}
}
}
}
}`
// SwaggerInfov1 holds exported Swagger Info so clients can modify it
var SwaggerInfov1 = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/v1",
Schemes: []string{},
Title: "Swagger Example API",
Description: "This is a sample server.",
InfoInstanceName: "v1",
SwaggerTemplate: docTemplatev1,
}
func init() {
swag.Register(SwaggerInfov1.InstanceName(), SwaggerInfov1)
}

View File

@ -0,0 +1,63 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample 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"
},
"basePath": "/v1",
"paths": {
"/books": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get a list of books in the the store",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/v1.Book"
}
}
}
}
}
}
},
"definitions": {
"v1.Book": {
"type": "object",
"properties": {
"author": {
"type": "string"
},
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"year": {
"type": "integer"
}
}
}
}
}

View File

@ -0,0 +1,42 @@
basePath: /v1
definitions:
v1.Book:
properties:
author:
type: string
id:
type: integer
title:
type: string
year:
type: integer
type: object
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: This is a sample server.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Example API
version: "1.0"
paths:
/books:
get:
consumes:
- application/json
description: get string by ID
produces:
- application/json
responses:
"200":
description: ok
schema:
items:
$ref: '#/definitions/v1.Book'
type: array
summary: Get a list of books in the the store
swagger: "2.0"

View File

@ -0,0 +1,87 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplatev2 = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"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": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/books": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get a list of books in the the store",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/v2.Book"
}
}
}
}
}
}
},
"definitions": {
"v2.Book": {
"type": "object",
"properties": {
"author": {
"type": "string"
},
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"year": {
"type": "integer"
}
}
}
}
}`
// SwaggerInfov2 holds exported Swagger Info so clients can modify it
var SwaggerInfov2 = &swag.Spec{
Version: "2.0",
Host: "",
BasePath: "/v2",
Schemes: []string{},
Title: "Swagger Example API",
Description: "This is a sample server.",
InfoInstanceName: "v2",
SwaggerTemplate: docTemplatev2,
}
func init() {
swag.Register(SwaggerInfov2.InstanceName(), SwaggerInfov2)
}

View File

@ -0,0 +1,63 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample 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": "2.0"
},
"basePath": "/v2",
"paths": {
"/books": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get a list of books in the the store",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/v2.Book"
}
}
}
}
}
}
},
"definitions": {
"v2.Book": {
"type": "object",
"properties": {
"author": {
"type": "string"
},
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"year": {
"type": "integer"
}
}
}
}
}

View File

@ -0,0 +1,42 @@
basePath: /v2
definitions:
v2.Book:
properties:
author:
type: string
id:
type: integer
title:
type: string
year:
type: integer
type: object
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: This is a sample server.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Example API
version: "2.0"
paths:
/books:
get:
consumes:
- application/json
description: get string by ID
produces:
- application/json
responses:
"200":
description: ok
schema:
items:
$ref: '#/definitions/v2.Book'
type: array
summary: Get a list of books in the the store
swagger: "2.0"

26
example/multiple/main.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
v1 "github.com/swaggo/gin-swagger/example/multiple/api/v1"
v2 "github.com/swaggo/gin-swagger/example/multiple/api/v2"
_ "github.com/swaggo/gin-swagger/example/multiple/docs"
)
func main() {
// New gin router
router := gin.New()
// Register api/v1 endpoints
v1.Register(router)
router.GET("/swagger/v1/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("v1")))
// Register api/v2 endpoints
v2.Register(router)
router.GET("/swagger/v2/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("v2")))
// Listen and Server in
_ = router.Run()
}

View File

@ -36,36 +36,36 @@ type Config struct {
PersistAuthorization bool PersistAuthorization bool
} }
func (c Config) toSwaggerConfig() swaggerConfig { func (config Config) toSwaggerConfig() swaggerConfig {
return swaggerConfig{ return swaggerConfig{
URL: c.URL, URL: config.URL,
DeepLinking: c.DeepLinking, DeepLinking: config.DeepLinking,
DocExpansion: c.DocExpansion, DocExpansion: config.DocExpansion,
DefaultModelsExpandDepth: c.DefaultModelsExpandDepth, DefaultModelsExpandDepth: config.DefaultModelsExpandDepth,
Oauth2RedirectURL: "`${window.location.protocol}//${window.location.host}$" + Oauth2RedirectURL: "`${window.location.protocol}//${window.location.host}$" +
"{window.location.pathname.split('/').slice(0, window.location.pathname.split('/').length - 1).join('/')}" + "{window.location.pathname.split('/').slice(0, window.location.pathname.split('/').length - 1).join('/')}" +
"/oauth2-redirect.html`", "/oauth2-redirect.html`",
Title: c.Title, Title: config.Title,
PersistAuthorization: c.PersistAuthorization, PersistAuthorization: config.PersistAuthorization,
} }
} }
// URL presents the url pointing to API definition (normally swagger.json or swagger.yaml). // URL presents the url pointing to API definition (normally swagger.json or swagger.yaml).
func URL(url string) func(c *Config) { func URL(url string) func(*Config) {
return func(c *Config) { return func(c *Config) {
c.URL = url c.URL = url
} }
} }
// DocExpansion list, full, none. // DocExpansion list, full, none.
func DocExpansion(docExpansion string) func(c *Config) { func DocExpansion(docExpansion string) func(*Config) {
return func(c *Config) { return func(c *Config) {
c.DocExpansion = docExpansion c.DocExpansion = docExpansion
} }
} }
// DeepLinking set the swagger deep linking configuration. // DeepLinking set the swagger deep linking configuration.
func DeepLinking(deepLinking bool) func(c *Config) { func DeepLinking(deepLinking bool) func(*Config) {
return func(c *Config) { return func(c *Config) {
c.DeepLinking = deepLinking c.DeepLinking = deepLinking
} }
@ -73,7 +73,7 @@ func DeepLinking(deepLinking bool) func(c *Config) {
// DefaultModelsExpandDepth set the default expansion depth for models // DefaultModelsExpandDepth set the default expansion depth for models
// (set to -1 completely hide the models). // (set to -1 completely hide the models).
func DefaultModelsExpandDepth(depth int) func(c *Config) { func DefaultModelsExpandDepth(depth int) func(*Config) {
return func(c *Config) { return func(c *Config) {
c.DefaultModelsExpandDepth = depth c.DefaultModelsExpandDepth = depth
} }
@ -81,39 +81,40 @@ func DefaultModelsExpandDepth(depth int) func(c *Config) {
// InstanceName set the instance name that was used to generate the swagger documents // InstanceName set the instance name that was used to generate the swagger documents
// Defaults to swag.Name ("swagger"). // Defaults to swag.Name ("swagger").
func InstanceName(name string) func(c *Config) { func InstanceName(name string) func(*Config) {
return func(c *Config) { return func(c *Config) {
c.InstanceName = name c.InstanceName = name
} }
} }
// PersistAuthorization If set to true, it persists authorization data and it would not be lost on browser close/refresh // PersistAuthorization Persist authorization information over browser close/refresh.
// Defaults to false. // Defaults to false.
func PersistAuthorization(persistAuthorization bool) func(c *Config) { func PersistAuthorization(persistAuthorization bool) func(*Config) {
return func(c *Config) { return func(c *Config) {
c.PersistAuthorization = persistAuthorization c.PersistAuthorization = persistAuthorization
} }
} }
// WrapHandler wraps `http.Handler` into `gin.HandlerFunc`. // WrapHandler wraps `http.Handler` into `gin.HandlerFunc`.
func WrapHandler(handler *webdav.Handler, options ...func(c *Config)) gin.HandlerFunc { func WrapHandler(handler *webdav.Handler, options ...func(*Config)) gin.HandlerFunc {
defaultConfig := Config{ var config = Config{
URL: "doc.json", URL: "doc.json",
DeepLinking: true,
DocExpansion: "list", DocExpansion: "list",
DefaultModelsExpandDepth: 1,
InstanceName: swag.Name, InstanceName: swag.Name,
Title: "Swagger UI", Title: "Swagger UI",
DefaultModelsExpandDepth: 1,
DeepLinking: true,
PersistAuthorization: false,
} }
for _, c := range options { for _, c := range options {
c(&defaultConfig) c(&config)
} }
return CustomWrapHandler(&defaultConfig, handler) return CustomWrapHandler(&config, handler)
} }
// CustomWrapHandler wraps `http.Handler` into `gin.HandlerFunc` // CustomWrapHandler wraps `http.Handler` into `gin.HandlerFunc`.
func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc { func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc {
var once sync.Once var once sync.Once
@ -126,10 +127,9 @@ func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc
} }
// create a template with name // create a template with name
t := template.New("swagger_index.html") index, _ := template.New("swagger_index.html").Parse(swaggerIndexTpl)
index, _ := t.Parse(swagger_index_templ)
var rexp = regexp.MustCompile(`(.*)(index\.html|doc\.json|favicon-16x16\.png|favicon-32x32\.png|/oauth2-redirect\.html|swagger-ui\.css|swagger-ui\.css\.map|swagger-ui\.js|swagger-ui\.js\.map|swagger-ui-bundle\.js|swagger-ui-bundle\.js\.map|swagger-ui-standalone-preset\.js|swagger-ui-standalone-preset\.js\.map)[?|.]*`) var matcher = regexp.MustCompile(`(.*)(index\.html|doc\.json|favicon-16x16\.png|favicon-32x32\.png|/oauth2-redirect\.html|swagger-ui\.css|swagger-ui\.css\.map|swagger-ui\.js|swagger-ui\.js\.map|swagger-ui-bundle\.js|swagger-ui-bundle\.js\.map|swagger-ui-standalone-preset\.js|swagger-ui-standalone-preset\.js\.map)[?|.]*`)
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
if ctx.Request.Method != http.MethodGet { if ctx.Request.Method != http.MethodGet {
@ -138,7 +138,7 @@ func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc
return return
} }
matches := rexp.FindStringSubmatch(ctx.Request.RequestURI) matches := matcher.FindStringSubmatch(ctx.Request.RequestURI)
if len(matches) != 3 { if len(matches) != 3 {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound)) ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
@ -175,7 +175,7 @@ func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc
return return
} }
ctx.JSON(http.StatusOK, doc) ctx.String(http.StatusOK, doc)
default: default:
handler.ServeHTTP(ctx.Writer, ctx.Request) handler.ServeHTTP(ctx.Writer, ctx.Request)
} }
@ -183,7 +183,7 @@ func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc
} }
// DisablingWrapHandler turn handler off // DisablingWrapHandler turn handler off
// if specified environment variable passed // if specified environment variable passed.
func DisablingWrapHandler(handler *webdav.Handler, envName string) gin.HandlerFunc { func DisablingWrapHandler(handler *webdav.Handler, envName string) gin.HandlerFunc {
if os.Getenv(envName) != "" { if os.Getenv(envName) != "" {
return func(c *gin.Context) { return func(c *gin.Context) {
@ -197,8 +197,8 @@ func DisablingWrapHandler(handler *webdav.Handler, envName string) gin.HandlerFu
} }
// DisablingCustomWrapHandler turn handler off // DisablingCustomWrapHandler turn handler off
// if specified environment variable passed // if specified environment variable passed.
func DisablingCustomWrapHandler(config *Config, h *webdav.Handler, envName string) gin.HandlerFunc { func DisablingCustomWrapHandler(config *Config, handler *webdav.Handler, envName string) gin.HandlerFunc {
if os.Getenv(envName) != "" { if os.Getenv(envName) != "" {
return func(c *gin.Context) { return func(c *gin.Context) {
// Simulate behavior when route unspecified and // Simulate behavior when route unspecified and
@ -207,10 +207,10 @@ func DisablingCustomWrapHandler(config *Config, h *webdav.Handler, envName strin
} }
} }
return CustomWrapHandler(config, h) return CustomWrapHandler(config, handler)
} }
const swagger_index_templ = `<!-- HTML for static distribution bundle build --> const swaggerIndexTpl = `<!-- HTML for static distribution bundle build -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>

View File

@ -1,6 +1,7 @@
package ginSwagger package ginSwagger
import ( import (
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -42,12 +43,18 @@ func TestWrapCustomHandler(t *testing.T) {
assert.Equal(t, http.StatusInternalServerError, performRequest(http.MethodGet, "/doc.json", router).Code) assert.Equal(t, http.StatusInternalServerError, performRequest(http.MethodGet, "/doc.json", router).Code)
swag.Register(swag.Name, &mockedSwag{}) doc := &mockedSwag{}
swag.Register(swag.Name, doc)
w2 := performRequest(http.MethodGet, "/doc.json", router) w2 := performRequest(http.MethodGet, "/doc.json", router)
assert.Equal(t, http.StatusOK, w2.Code) assert.Equal(t, http.StatusOK, w2.Code)
assert.Equal(t, w2.Header()["Content-Type"][0], "application/json; charset=utf-8") assert.Equal(t, w2.Header()["Content-Type"][0], "application/json; charset=utf-8")
// Perform body rendering validation
w2Body, err := ioutil.ReadAll(w2.Body)
assert.NoError(t, err)
assert.Equal(t, doc.ReadDoc(), string(w2Body))
w3 := performRequest(http.MethodGet, "/favicon-16x16.png", router) w3 := performRequest(http.MethodGet, "/favicon-16x16.png", router)
assert.Equal(t, http.StatusOK, w3.Code) assert.Equal(t, http.StatusOK, w3.Code)
assert.Equal(t, w3.Header()["Content-Type"][0], "image/png") assert.Equal(t, w3.Header()["Content-Type"][0], "image/png")
@ -85,10 +92,10 @@ func TestDisablingWrapHandler(t *testing.T) {
router.GET("/disabling/*any", DisablingWrapHandler(swaggerFiles.Handler, disablingKey)) router.GET("/disabling/*any", DisablingWrapHandler(swaggerFiles.Handler, disablingKey))
assert.Equal(t, 404, performRequest(http.MethodGet, "/disabling/index.html", router).Code) assert.Equal(t, http.StatusNotFound, performRequest(http.MethodGet, "/disabling/index.html", router).Code)
assert.Equal(t, 404, performRequest(http.MethodGet, "/disabling/doc.json", router).Code) assert.Equal(t, http.StatusNotFound, performRequest(http.MethodGet, "/disabling/doc.json", router).Code)
assert.Equal(t, 404, performRequest(http.MethodGet, "/disabling/oauth2-redirect.html", router).Code) assert.Equal(t, http.StatusNotFound, performRequest(http.MethodGet, "/disabling/oauth2-redirect.html", router).Code)
assert.Equal(t, 404, performRequest(http.MethodGet, "/disabling/notfound", router).Code) assert.Equal(t, http.StatusNotFound, performRequest(http.MethodGet, "/disabling/notfound", router).Code)
} }
func TestDisablingCustomWrapHandler(t *testing.T) { func TestDisablingCustomWrapHandler(t *testing.T) {