607 lines
16 KiB
Go
607 lines
16 KiB
Go
/*
|
|
* Copyright (c) 2019 Alex aka mailoman <alex@webz.asia>
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* @author Alex aka mailoman <alex@webz.asia>
|
|
* @copyright Copyright (c) 2019 Alex aka mailoman <alex@webz.asia>
|
|
* @since 18.12.2019
|
|
*
|
|
*/
|
|
|
|
package echoswagger
|
|
|
|
import (
|
|
"reflect"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
/*
|
|
@TODO:
|
|
1. pattern
|
|
2. opreationId repeated
|
|
|
|
Notice:
|
|
1. Email and URL will not be verified because it does not affect the normal display of the page
|
|
2. SetSecurity / SetSecurityWithScope Pass multiple parameters to indicate that the security relationship is an AND relationship;
|
|
multiple calls to SetSecurity / SetSecurityWithScope Security are an OR relationship
|
|
3. Only support the basic type of Map Key
|
|
*/
|
|
|
|
type ApiRouter interface {
|
|
// GET overrides `Echo#GET()` and creates Api.
|
|
GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
|
|
|
// POST overrides `Echo#POST()` and creates Api.
|
|
POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
|
|
|
// PUT overrides `Echo#PUT()` and creates Api.
|
|
PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
|
|
|
// DELETE overrides `Echo#DELETE()` and creates Api.
|
|
DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
|
|
|
// OPTIONS overrides `Echo#OPTIONS()` and creates Api.
|
|
OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
|
|
|
// HEAD overrides `Echo#HEAD()` and creates Api.
|
|
HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
|
|
|
// PATCH overrides `Echo#PATCH()` and creates Api.
|
|
PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
|
}
|
|
|
|
type ApiRoot interface {
|
|
ApiRouter
|
|
|
|
// Group overrides `Echo#Group()` and creates ApiGroup.
|
|
Group(name, prefix string, m ...echo.MiddlewareFunc) ApiGroup
|
|
|
|
// SetRequestContentType sets request content types.
|
|
SetRequestContentType(types ...string) ApiRoot
|
|
|
|
// SetResponseContentType sets response content types.
|
|
SetResponseContentType(types ...string) ApiRoot
|
|
|
|
// SetExternalDocs sets external docs.
|
|
SetExternalDocs(desc, url string) ApiRoot
|
|
|
|
// AddSecurityBasic adds `SecurityDefinition` with type basic.
|
|
AddSecurityBasic(name, desc string) ApiRoot
|
|
|
|
// AddSecurityAPIKey adds `SecurityDefinition` with type apikey.
|
|
AddSecurityAPIKey(name, desc string, in SecurityInType) ApiRoot
|
|
|
|
// AddSecurityOAuth2 adds `SecurityDefinition` with type oauth2.
|
|
AddSecurityOAuth2(name, desc string, flow OAuth2FlowType, authorizationUrl, tokenUrl string, scopes map[string]string) ApiRoot
|
|
|
|
// SetUI sets UI setting.
|
|
// If DetachSpec is false, HideTop will not take effect
|
|
SetUI(ui UISetting) ApiRoot
|
|
|
|
// SetScheme sets available protocol schemes.
|
|
SetScheme(schemes ...string) ApiRoot
|
|
|
|
// GetRaw returns raw `Swagger`. Only special case should use.
|
|
GetRaw() *Swagger
|
|
|
|
// SetRaw sets raw `Swagger` to ApiRoot. Only special case should use.
|
|
SetRaw(s *Swagger) ApiRoot
|
|
|
|
// Echo returns the embedded Echo instance
|
|
Echo() *echo.Echo
|
|
}
|
|
|
|
type ApiGroup interface {
|
|
ApiRouter
|
|
|
|
// SetDescription sets description for ApiGroup.
|
|
SetDescription(desc string) ApiGroup
|
|
|
|
// SetExternalDocs sets external docs for ApiGroup.
|
|
SetExternalDocs(desc, url string) ApiGroup
|
|
|
|
// SetSecurity sets Security for all operations within the ApiGroup
|
|
// which names are reigistered by AddSecurity... functions.
|
|
SetSecurity(names ...string) ApiGroup
|
|
|
|
// SetSecurityWithScope sets Security with scopes for all operations
|
|
// within the ApiGroup which names are reigistered
|
|
// by AddSecurity... functions.
|
|
// Should only use when Security type is oauth2.
|
|
SetSecurityWithScope(s map[string][]string) ApiGroup
|
|
|
|
// EchoGroup returns the embedded `echo.Group` instance.
|
|
EchoGroup() *echo.Group
|
|
}
|
|
|
|
type Api interface {
|
|
// AddParamPath adds path parameter.
|
|
AddParamPath(p interface{}, name, desc string) Api
|
|
|
|
// AddParamPathNested adds path parameters nested in p.
|
|
// P must be struct type.
|
|
AddParamPathNested(p interface{}) Api
|
|
|
|
// AddParamQuery adds query parameter.
|
|
AddParamQuery(p interface{}, name, desc string, required bool) Api
|
|
|
|
// AddParamQueryNested adds query parameters nested in p.
|
|
// P must be struct type.
|
|
AddParamQueryNested(p interface{}) Api
|
|
|
|
// AddParamForm adds formData parameter.
|
|
AddParamForm(p interface{}, name, desc string, required bool) Api
|
|
|
|
// AddParamFormNested adds formData parameters nested in p.
|
|
// P must be struct type.
|
|
AddParamFormNested(p interface{}) Api
|
|
|
|
// AddParamHeader adds header parameter.
|
|
AddParamHeader(p interface{}, name, desc string, required bool) Api
|
|
|
|
// AddParamHeaderNested adds header parameters nested in p.
|
|
// P must be struct type.
|
|
AddParamHeaderNested(p interface{}) Api
|
|
|
|
// AddParamBody adds body parameter.
|
|
AddParamBody(p interface{}, name, desc string, required bool) Api
|
|
|
|
// AddParamFile adds file parameter.
|
|
AddParamFile(name, desc string, required bool) Api
|
|
|
|
// AddResponse adds response for Api.
|
|
// Header must be struct type.
|
|
AddResponse(code int, desc string, schema interface{}, header interface{}) Api
|
|
|
|
// SetRequestContentType sets request content types.
|
|
SetRequestContentType(types ...string) Api
|
|
|
|
// SetResponseContentType sets response content types.
|
|
SetResponseContentType(types ...string) Api
|
|
|
|
// SetOperationId sets operationId
|
|
SetOperationId(id string) Api
|
|
|
|
// SetDeprecated marks Api as deprecated.
|
|
SetDeprecated() Api
|
|
|
|
// SetDescription sets description.
|
|
SetDescription(desc string) Api
|
|
|
|
// SetExternalDocs sets external docs.
|
|
SetExternalDocs(desc, url string) Api
|
|
|
|
// SetSummary sets summary.
|
|
SetSummary(summary string) Api
|
|
|
|
// SetSecurity sets Security which names are reigistered
|
|
// by AddSecurity... functions.
|
|
SetSecurity(names ...string) Api
|
|
|
|
// SetSecurityWithScope sets Security for Api which names are
|
|
// reigistered by AddSecurity... functions.
|
|
// Should only use when Security type is oauth2.
|
|
SetSecurityWithScope(s map[string][]string) Api
|
|
|
|
// Route returns the embedded `echo.Route` instance.
|
|
Route() *echo.Route
|
|
}
|
|
|
|
type routers struct {
|
|
apis []api
|
|
defs *RawDefineDic
|
|
}
|
|
|
|
type Root struct {
|
|
routers
|
|
spec *Swagger
|
|
echo *echo.Echo
|
|
groups []group
|
|
ui UISetting
|
|
once sync.Once
|
|
err error
|
|
}
|
|
|
|
type group struct {
|
|
routers
|
|
echoGroup *echo.Group
|
|
security []map[string][]string
|
|
tag Tag
|
|
}
|
|
|
|
type api struct {
|
|
route *echo.Route
|
|
defs *RawDefineDic
|
|
security []map[string][]string
|
|
operation Operation
|
|
}
|
|
|
|
// New creates ApiRoot instance.
|
|
// Multiple ApiRoot are allowed in one project.
|
|
func New(e *echo.Echo, docPath string, i *Info) ApiRoot {
|
|
if e == nil {
|
|
panic("echoswagger: invalid Echo instance")
|
|
}
|
|
|
|
if i == nil {
|
|
i = &Info{
|
|
Title: "Project APIs",
|
|
}
|
|
}
|
|
defs := make(RawDefineDic)
|
|
r := &Root{
|
|
echo: e,
|
|
spec: &Swagger{
|
|
Info: i,
|
|
SecurityDefinitions: make(map[string]*SecurityDefinition),
|
|
Definitions: make(map[string]*JSONSchema),
|
|
},
|
|
routers: routers{
|
|
defs: &defs,
|
|
},
|
|
}
|
|
|
|
e.GET(connectPath(docPath), r.docHandler(docPath))
|
|
e.GET(connectPath(docPath, SpecName), r.specHandler(docPath))
|
|
return r
|
|
}
|
|
|
|
func (r *Root) GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
return r.appendRoute(r.echo.GET(path, h, m...))
|
|
}
|
|
|
|
func (r *Root) POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
return r.appendRoute(r.echo.POST(path, h, m...))
|
|
}
|
|
|
|
func (r *Root) PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
return r.appendRoute(r.echo.PUT(path, h, m...))
|
|
}
|
|
|
|
func (r *Root) DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
return r.appendRoute(r.echo.DELETE(path, h, m...))
|
|
}
|
|
|
|
func (r *Root) OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
return r.appendRoute(r.echo.OPTIONS(path, h, m...))
|
|
}
|
|
|
|
func (r *Root) HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
return r.appendRoute(r.echo.HEAD(path, h, m...))
|
|
}
|
|
|
|
func (r *Root) PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
return r.appendRoute(r.echo.PATCH(path, h, m...))
|
|
}
|
|
|
|
func (r *Root) Group(name, prefix string, m ...echo.MiddlewareFunc) ApiGroup {
|
|
if name == "" {
|
|
panic("echoswagger: invalid name of ApiGroup")
|
|
}
|
|
echoGroup := r.echo.Group(prefix, m...)
|
|
group := group{
|
|
echoGroup: echoGroup,
|
|
routers: routers{
|
|
defs: r.defs,
|
|
},
|
|
}
|
|
var counter int
|
|
LoopTags:
|
|
for _, t := range r.groups {
|
|
if t.tag.Name == name {
|
|
if counter > 0 {
|
|
name = name[:len(name)-2]
|
|
}
|
|
counter++
|
|
name += "_" + strconv.Itoa(counter)
|
|
goto LoopTags
|
|
}
|
|
}
|
|
group.tag = Tag{Name: name}
|
|
r.groups = append(r.groups, group)
|
|
return &r.groups[len(r.groups)-1]
|
|
}
|
|
|
|
func (r *Root) SetRequestContentType(types ...string) ApiRoot {
|
|
r.spec.Consumes = types
|
|
return r
|
|
}
|
|
|
|
func (r *Root) SetResponseContentType(types ...string) ApiRoot {
|
|
r.spec.Produces = types
|
|
return r
|
|
}
|
|
|
|
func (r *Root) SetExternalDocs(desc, url string) ApiRoot {
|
|
r.spec.ExternalDocs = &ExternalDocs{
|
|
Description: desc,
|
|
URL: url,
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *Root) AddSecurityBasic(name, desc string) ApiRoot {
|
|
if !r.checkSecurity(name) {
|
|
return r
|
|
}
|
|
sd := &SecurityDefinition{
|
|
Type: string(SecurityBasic),
|
|
Description: desc,
|
|
}
|
|
r.spec.SecurityDefinitions[name] = sd
|
|
return r
|
|
}
|
|
|
|
func (r *Root) AddSecurityAPIKey(name, desc string, in SecurityInType) ApiRoot {
|
|
if !r.checkSecurity(name) {
|
|
return r
|
|
}
|
|
sd := &SecurityDefinition{
|
|
Type: string(SecurityAPIKey),
|
|
Description: desc,
|
|
Name: name,
|
|
In: string(in),
|
|
}
|
|
r.spec.SecurityDefinitions[name] = sd
|
|
return r
|
|
}
|
|
|
|
func (r *Root) AddSecurityOAuth2(name, desc string, flow OAuth2FlowType, authorizationUrl, tokenUrl string, scopes map[string]string) ApiRoot {
|
|
if !r.checkSecurity(name) {
|
|
return r
|
|
}
|
|
sd := &SecurityDefinition{
|
|
Type: string(SecurityOAuth2),
|
|
Description: desc,
|
|
Flow: string(flow),
|
|
AuthorizationURL: authorizationUrl,
|
|
TokenURL: tokenUrl,
|
|
Scopes: scopes,
|
|
}
|
|
r.spec.SecurityDefinitions[name] = sd
|
|
return r
|
|
}
|
|
|
|
func (r *Root) SetUI(ui UISetting) ApiRoot {
|
|
r.ui = ui
|
|
return r
|
|
}
|
|
|
|
func (r *Root) SetScheme(schemes ...string) ApiRoot {
|
|
for _, s := range schemes {
|
|
if !isValidScheme(s) {
|
|
panic("echoswagger: invalid protocol scheme")
|
|
}
|
|
}
|
|
r.spec.Schemes = schemes
|
|
return r
|
|
}
|
|
|
|
func (r *Root) GetRaw() *Swagger {
|
|
return r.spec
|
|
}
|
|
|
|
func (r *Root) SetRaw(s *Swagger) ApiRoot {
|
|
r.spec = s
|
|
return r
|
|
}
|
|
|
|
func (r *Root) Echo() *echo.Echo {
|
|
return r.echo
|
|
}
|
|
|
|
func (g *group) GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
a := g.appendRoute(g.echoGroup.GET(path, h, m...))
|
|
a.operation.Tags = []string{g.tag.Name}
|
|
return a
|
|
}
|
|
|
|
func (g *group) POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
a := g.appendRoute(g.echoGroup.POST(path, h, m...))
|
|
a.operation.Tags = []string{g.tag.Name}
|
|
return a
|
|
}
|
|
|
|
func (g *group) PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
a := g.appendRoute(g.echoGroup.PUT(path, h, m...))
|
|
a.operation.Tags = []string{g.tag.Name}
|
|
return a
|
|
}
|
|
|
|
func (g *group) DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
a := g.appendRoute(g.echoGroup.DELETE(path, h, m...))
|
|
a.operation.Tags = []string{g.tag.Name}
|
|
return a
|
|
}
|
|
|
|
func (g *group) OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
a := g.appendRoute(g.echoGroup.OPTIONS(path, h, m...))
|
|
a.operation.Tags = []string{g.tag.Name}
|
|
return a
|
|
}
|
|
|
|
func (g *group) HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
a := g.appendRoute(g.echoGroup.HEAD(path, h, m...))
|
|
a.operation.Tags = []string{g.tag.Name}
|
|
return a
|
|
}
|
|
|
|
func (g *group) PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
|
a := g.appendRoute(g.echoGroup.PATCH(path, h, m...))
|
|
a.operation.Tags = []string{g.tag.Name}
|
|
return a
|
|
}
|
|
|
|
func (g *group) SetDescription(desc string) ApiGroup {
|
|
g.tag.Description = desc
|
|
return g
|
|
}
|
|
|
|
func (g *group) SetExternalDocs(desc, url string) ApiGroup {
|
|
g.tag.ExternalDocs = &ExternalDocs{
|
|
Description: desc,
|
|
URL: url,
|
|
}
|
|
return g
|
|
}
|
|
|
|
func (g *group) SetSecurity(names ...string) ApiGroup {
|
|
if len(names) == 0 {
|
|
return g
|
|
}
|
|
g.security = setSecurity(g.security, names...)
|
|
return g
|
|
}
|
|
|
|
func (g *group) SetSecurityWithScope(s map[string][]string) ApiGroup {
|
|
g.security = setSecurityWithScope(g.security, s)
|
|
return g
|
|
}
|
|
|
|
func (g *group) EchoGroup() *echo.Group {
|
|
return g.echoGroup
|
|
}
|
|
|
|
func (a *api) AddParamPath(p interface{}, name, desc string) Api {
|
|
return a.addParams(p, ParamInPath, name, desc, true, false)
|
|
}
|
|
|
|
func (a *api) AddParamPathNested(p interface{}) Api {
|
|
return a.addParams(p, ParamInPath, "", "", true, true)
|
|
}
|
|
|
|
func (a *api) AddParamQuery(p interface{}, name, desc string, required bool) Api {
|
|
return a.addParams(p, ParamInQuery, name, desc, required, false)
|
|
}
|
|
|
|
func (a *api) AddParamQueryNested(p interface{}) Api {
|
|
return a.addParams(p, ParamInQuery, "", "", false, true)
|
|
}
|
|
|
|
func (a *api) AddParamForm(p interface{}, name, desc string, required bool) Api {
|
|
return a.addParams(p, ParamInFormData, name, desc, required, false)
|
|
}
|
|
|
|
func (a *api) AddParamFormNested(p interface{}) Api {
|
|
return a.addParams(p, ParamInFormData, "", "", false, true)
|
|
}
|
|
|
|
func (a *api) AddParamHeader(p interface{}, name, desc string, required bool) Api {
|
|
return a.addParams(p, ParamInHeader, name, desc, required, false)
|
|
}
|
|
|
|
func (a *api) AddParamHeaderNested(p interface{}) Api {
|
|
return a.addParams(p, ParamInHeader, "", "", false, true)
|
|
}
|
|
|
|
func (a *api) AddParamBody(p interface{}, name, desc string, required bool) Api {
|
|
return a.addBodyParams(p, name, desc, required)
|
|
}
|
|
|
|
func (a *api) AddParamFile(name, desc string, required bool) Api {
|
|
name = a.operation.rename(name)
|
|
a.operation.Parameters = append(a.operation.Parameters, &Parameter{
|
|
Name: name,
|
|
In: string(ParamInFormData),
|
|
Description: desc,
|
|
Required: required,
|
|
Type: "file",
|
|
})
|
|
return a
|
|
}
|
|
|
|
func (a *api) AddResponse(code int, desc string, schema interface{}, header interface{}) Api {
|
|
r := &Response{
|
|
Description: desc,
|
|
}
|
|
|
|
st := reflect.TypeOf(schema)
|
|
if st != nil {
|
|
if !isValidSchema(st, false) {
|
|
panic("echoswagger: invalid response schema")
|
|
}
|
|
r.Schema = a.defs.genSchema(reflect.ValueOf(schema))
|
|
}
|
|
|
|
ht := reflect.TypeOf(header)
|
|
if ht != nil {
|
|
if !isValidParam(reflect.TypeOf(header), true, false) {
|
|
panic("echoswagger: invalid response header")
|
|
}
|
|
r.Headers = a.genHeader(reflect.ValueOf(header))
|
|
}
|
|
|
|
cstr := strconv.Itoa(code)
|
|
a.operation.Responses[cstr] = r
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetRequestContentType(types ...string) Api {
|
|
a.operation.Consumes = types
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetResponseContentType(types ...string) Api {
|
|
a.operation.Produces = types
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetOperationId(id string) Api {
|
|
a.operation.OperationID = id
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetDeprecated() Api {
|
|
a.operation.Deprecated = true
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetDescription(desc string) Api {
|
|
a.operation.Description = desc
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetExternalDocs(desc, url string) Api {
|
|
a.operation.ExternalDocs = &ExternalDocs{
|
|
Description: desc,
|
|
URL: url,
|
|
}
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetSummary(summary string) Api {
|
|
a.operation.Summary = summary
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetSecurity(names ...string) Api {
|
|
if len(names) == 0 {
|
|
return a
|
|
}
|
|
a.security = setSecurity(a.security, names...)
|
|
return a
|
|
}
|
|
|
|
func (a *api) SetSecurityWithScope(s map[string][]string) Api {
|
|
a.security = setSecurityWithScope(a.security, s)
|
|
return a
|
|
}
|
|
|
|
func (a *api) Route() *echo.Route {
|
|
return a.route
|
|
}
|