Merge pull request #8 from elvinchan/7
#7 Fix relative path & add set scheme function
This commit is contained in:
commit
0e96d943fb
@ -31,7 +31,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// ApiRoot with Echo instance
|
||||
r := echoswagger.New(echo.New(), "/v1", "doc/", nil)
|
||||
r := echoswagger.New(echo.New(), "", "doc/", nil)
|
||||
|
||||
// Routes with parameters & responses
|
||||
r.POST("/", createUser).
|
||||
@ -58,12 +58,15 @@ func createUser(c echo.Context) error {
|
||||
```
|
||||
r := echoswagger.New(echo.New(), "/v1", "doc/", nil)
|
||||
```
|
||||
> Note: The parameter `basePath` is generally used when the access root path is not the root directory of the website after application is deployed. For example, the URL of an API in the program running locally is: `http://localhost:1323/users`, the actual URL after deployed to server is: `https://www.xxx.com/legacy-api/users`, then, when running locally, `basePath` should be `/`, when running on server, `basePath` should be `/legacy-api`.
|
||||
|
||||
You can use the result `ApiRoot` instance to:
|
||||
- Setup Security definitions, request/response Content-Types, UI options, etc.
|
||||
- Setup Security definitions, request/response Content-Types, UI options, Scheme, etc.
|
||||
```
|
||||
r.AddSecurityAPIKey("JWT", "JWT Token", echoswagger.SecurityInHeader).
|
||||
SetRequestContentType("application/x-www-form-urlencoded", "multipart/form-data").
|
||||
SetUI(UISetting{HideTop: true})
|
||||
SetUI(UISetting{HideTop: true}).
|
||||
SetScheme("https", "http")
|
||||
```
|
||||
- Get `echo.Echo` instance.
|
||||
```
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// ApiRoot with Echo instance
|
||||
r := echoswagger.New(echo.New(), "/v1", "doc/", nil)
|
||||
r := echoswagger.New(echo.New(), "", "doc/", nil)
|
||||
|
||||
// Routes with parameters & responses
|
||||
r.POST("/", createUser).
|
||||
@ -58,12 +58,15 @@ func createUser(c echo.Context) error {
|
||||
```
|
||||
r := echoswagger.New(echo.New(), "/v1", "doc/", nil)
|
||||
```
|
||||
> 注意:参数`basePath`一般用于程序部署后访问路径并非网站根目录时的情况,比如程序运行在本地的某个API的URL为:`http://localhost:1323/users`,部署至服务器后的实际URL为:`https://www.xxx.com/legacy-api/users`,则本地运行时,`basePath`应该传入`/`, 部署至服务器时,`basePath`应该传入`/legacy-api`。
|
||||
|
||||
你可以用这个`ApiRoot`来:
|
||||
- 设置Security定义, 请求/响应Content-Type,UI选项,等。
|
||||
- 设置Security定义, 请求/响应Content-Type,UI选项,Scheme等。
|
||||
```
|
||||
r.AddSecurityAPIKey("JWT", "JWT Token", echoswagger.SecurityInHeader).
|
||||
SetRequestContentType("application/x-www-form-urlencoded", "multipart/form-data").
|
||||
SetUI(UISetting{HideTop: true})
|
||||
SetUI(UISetting{HideTop: true}).
|
||||
SetScheme("https", "http")
|
||||
```
|
||||
- 获取`echo.Echo`实例。
|
||||
```
|
||||
|
@ -1,7 +1,7 @@
|
||||
package echoswagger
|
||||
|
||||
// CDN refer to https://www.jsdelivr.com/package/npm/swagger-ui-dist
|
||||
const DefaultCDN = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.18.3-republish2"
|
||||
const DefaultCDN = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.19.0"
|
||||
|
||||
const SwaggerUIContent = `{{define "swagger"}}
|
||||
<!DOCTYPE html>
|
||||
@ -50,7 +50,7 @@ const SwaggerUIContent = `{{define "swagger"}}
|
||||
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: {{.url}},
|
||||
url: this.window.location.origin+{{.path}},
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
|
@ -54,14 +54,7 @@ func toSwaggerPath(path string) string {
|
||||
for _, name := range params {
|
||||
path = strings.Replace(path, ":"+name, "{"+name+"}", 1)
|
||||
}
|
||||
return proccessPath(path)
|
||||
}
|
||||
|
||||
func proccessPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
return path
|
||||
return connectPath(path)
|
||||
}
|
||||
|
||||
func converter(t reflect.Type) func(s string) (interface{}, error) {
|
||||
|
@ -13,7 +13,7 @@ func main() {
|
||||
func initServer() echoswagger.ApiRoot {
|
||||
e := echo.New()
|
||||
|
||||
se := echoswagger.New(e, "/v2", "doc/", &echoswagger.Info{
|
||||
se := echoswagger.New(e, "", "doc/", &echoswagger.Info{
|
||||
Title: "Swagger Petstore",
|
||||
Description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
|
||||
Version: "1.0.0",
|
||||
@ -36,7 +36,8 @@ func initServer() echoswagger.ApiRoot {
|
||||
|
||||
se.SetExternalDocs("Find out more about Swagger", "http://swagger.io").
|
||||
SetResponseContentType("application/xml", "application/json").
|
||||
SetUI(echoswagger.UISetting{HideTop: true})
|
||||
SetUI(echoswagger.UISetting{HideTop: true}).
|
||||
SetScheme("https", "http")
|
||||
|
||||
PetController{}.Init(se.Group("pet", "/pet"))
|
||||
StoreController{}.Init(se.Group("store", "/store"))
|
||||
|
@ -14,8 +14,9 @@
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"host": "example.com",
|
||||
"basePath": "/v2",
|
||||
"basePath": "/",
|
||||
"schemes": [
|
||||
"https",
|
||||
"http"
|
||||
],
|
||||
"produces": [
|
||||
|
10
internal.go
10
internal.go
@ -31,21 +31,21 @@ type RawDefine struct {
|
||||
Schema *JSONSchema
|
||||
}
|
||||
|
||||
func (r *Root) docHandler(swaggerPath string) echo.HandlerFunc {
|
||||
func (r *Root) docHandler(realSpecPath string) echo.HandlerFunc {
|
||||
t, err := template.New("swagger").Parse(SwaggerUIContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return func(c echo.Context) error {
|
||||
cdn := DefaultCDN
|
||||
if r.ui.CDN != "" {
|
||||
cdn = r.ui.CDN
|
||||
cdn := r.ui.CDN
|
||||
if cdn == "" {
|
||||
cdn = DefaultCDN
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
t.Execute(buf, map[string]interface{}{
|
||||
"title": r.spec.Info.Title,
|
||||
"url": c.Scheme() + "://" + c.Request().Host + swaggerPath,
|
||||
"hideTop": r.ui.HideTop,
|
||||
"path": realSpecPath,
|
||||
"cdn": cdn,
|
||||
})
|
||||
return c.HTMLBlob(http.StatusOK, buf.Bytes())
|
||||
|
1
spec.go
1
spec.go
@ -28,7 +28,6 @@ func (r *Root) genSpec(c echo.Context) error {
|
||||
r.spec.Swagger = SwaggerVersion
|
||||
r.spec.Paths = make(map[string]interface{})
|
||||
r.spec.Host = c.Request().Host
|
||||
r.spec.Schemes = []string{c.Scheme()}
|
||||
|
||||
for i := range r.groups {
|
||||
group := &r.groups[i]
|
||||
|
@ -19,7 +19,7 @@ func TestSpec(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
j := `{"swagger":"2.0","info":{"title":"Project APIs","version":""},"host":"example.com","basePath":"/","schemes":["http"],"paths":{}}`
|
||||
j := `{"swagger":"2.0","info":{"title":"Project APIs","version":""},"host":"example.com","basePath":"/","paths":{}}`
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.JSONEq(t, j, rec.Body.String())
|
||||
@ -94,7 +94,7 @@ func TestSpec(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
j := `{"swagger":"2.0","info":{"title":"Project APIs","version":""},"host":"example.com","basePath":"/","schemes":["http"],"paths":{"/ping":{"get":{"responses":{"default":{"description":"successful operation"}}}},"/users/{id}":{"delete":{"tags":["Users"],"responses":{"default":{"description":"successful operation"}}}}},"tags":[{"name":"Users"}]}`
|
||||
j := `{"swagger":"2.0","info":{"title":"Project APIs","version":""},"host":"example.com","basePath":"/","paths":{"/ping":{"get":{"responses":{"default":{"description":"successful operation"}}}},"/users/{id}":{"delete":{"tags":["Users"],"responses":{"default":{"description":"successful operation"}}}}},"tags":[{"name":"Users"}]}`
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.JSONEq(t, j, rec.Body.String())
|
||||
|
21
utils.go
21
utils.go
@ -64,3 +64,24 @@ LoopType:
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// "" → "/"
|
||||
// "/" → "/"
|
||||
// "a" → "/a"
|
||||
// "/a" → "/a"
|
||||
// "/a/" → "/a/"
|
||||
func connectPath(paths ...string) string {
|
||||
var result string
|
||||
for i, path := range paths {
|
||||
// add prefix slash
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
// remove suffix slash, ignore last path
|
||||
if i != len(paths)-1 && len(path) != 0 && path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
result += path
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -112,3 +112,10 @@ func isBasicType(t reflect.Type) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isValidScheme(s string) bool {
|
||||
if s == "http" || s == "https" || s == "ws" || s == "wss" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
32
wrapper.go
32
wrapper.go
@ -15,9 +15,8 @@ TODO:
|
||||
|
||||
Notice:
|
||||
1.不会对Email和URL进行验证,因为不影响页面的正常显示
|
||||
2.只支持对应于SwaggerUI页面的Schema,不支持sw、sww等协议
|
||||
3.SetSecurity/SetSecurityWithScope 传多个参数表示Security之间是AND关系;多次调用SetSecurity/SetSecurityWithScope Security之间是OR关系
|
||||
4.只支持基本类型的Map Key
|
||||
2.SetSecurity/SetSecurityWithScope 传多个参数表示Security之间是AND关系;多次调用SetSecurity/SetSecurityWithScope Security之间是OR关系
|
||||
3.只支持基本类型的Map Key
|
||||
*/
|
||||
|
||||
type ApiRouter interface {
|
||||
@ -70,6 +69,9 @@ type ApiRoot interface {
|
||||
// SetUI sets UI setting.
|
||||
SetUI(ui UISetting) ApiRoot
|
||||
|
||||
// SetScheme sets available protocol schemes.
|
||||
SetScheme(schemes ...string) ApiRoot
|
||||
|
||||
// GetRaw returns raw `Swagger`. Only special case should use.
|
||||
GetRaw() *Swagger
|
||||
|
||||
@ -211,14 +213,6 @@ func New(e *echo.Echo, basePath, docPath string, i *Info) ApiRoot {
|
||||
if e == nil {
|
||||
panic("echoswagger: invalid Echo instance")
|
||||
}
|
||||
basePath = proccessPath(basePath)
|
||||
docPath = proccessPath(docPath)
|
||||
|
||||
var connector string
|
||||
if docPath[len(docPath)-1] != '/' {
|
||||
connector = "/"
|
||||
}
|
||||
specPath := docPath + connector + "swagger.json"
|
||||
|
||||
if i == nil {
|
||||
i = &Info{
|
||||
@ -231,7 +225,7 @@ func New(e *echo.Echo, basePath, docPath string, i *Info) ApiRoot {
|
||||
spec: &Swagger{
|
||||
Info: i,
|
||||
SecurityDefinitions: make(map[string]*SecurityDefinition),
|
||||
BasePath: basePath,
|
||||
BasePath: connectPath(basePath),
|
||||
Definitions: make(map[string]*JSONSchema),
|
||||
},
|
||||
routers: routers{
|
||||
@ -239,7 +233,9 @@ func New(e *echo.Echo, basePath, docPath string, i *Info) ApiRoot {
|
||||
},
|
||||
}
|
||||
|
||||
e.GET(docPath, r.docHandler(specPath))
|
||||
specPath := connectPath(docPath, "swagger.json")
|
||||
realSpecPath := connectPath(basePath, specPath)
|
||||
e.GET(connectPath(docPath), r.docHandler(realSpecPath))
|
||||
e.GET(specPath, r.Spec)
|
||||
return r
|
||||
}
|
||||
@ -365,6 +361,16 @@ func (r *Root) SetUI(ui UISetting) ApiRoot {
|
||||
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
|
||||
}
|
||||
|
101
wrapper_test.go
101
wrapper_test.go
@ -95,6 +95,80 @@ func TestNew(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
baseInput, docInput string
|
||||
baseOutput, docOutput, specOutput, realSpecOutput string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
baseInput: "/",
|
||||
docInput: "doc/",
|
||||
baseOutput: "/",
|
||||
docOutput: "/doc/",
|
||||
specOutput: "/doc/swagger.json",
|
||||
realSpecOutput: "/doc/swagger.json",
|
||||
name: "A",
|
||||
}, {
|
||||
baseInput: "",
|
||||
docInput: "",
|
||||
baseOutput: "/",
|
||||
docOutput: "/",
|
||||
specOutput: "/swagger.json",
|
||||
realSpecOutput: "/swagger.json",
|
||||
name: "B",
|
||||
}, {
|
||||
baseInput: "/omni-api",
|
||||
docInput: "/doc",
|
||||
baseOutput: "/omni-api",
|
||||
docOutput: "/doc",
|
||||
specOutput: "/doc/swagger.json",
|
||||
realSpecOutput: "/omni-api/doc/swagger.json",
|
||||
name: "C",
|
||||
}, {
|
||||
baseInput: "/omni-api/",
|
||||
docInput: "",
|
||||
baseOutput: "/omni-api/",
|
||||
docOutput: "/",
|
||||
specOutput: "/swagger.json",
|
||||
realSpecOutput: "/omni-api/swagger.json",
|
||||
name: "D",
|
||||
}, {
|
||||
baseInput: "/omni-api",
|
||||
docInput: "doc/",
|
||||
baseOutput: "/omni-api",
|
||||
docOutput: "/doc/",
|
||||
specOutput: "/doc/swagger.json",
|
||||
realSpecOutput: "/omni-api/doc/swagger.json",
|
||||
name: "F",
|
||||
}, {
|
||||
baseInput: "omni-api",
|
||||
docInput: "doc/",
|
||||
baseOutput: "/omni-api",
|
||||
docOutput: "/doc/",
|
||||
specOutput: "/doc/swagger.json",
|
||||
realSpecOutput: "/omni-api/doc/swagger.json",
|
||||
name: "G",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
apiRoot := New(echo.New(), tt.baseInput, tt.docInput, nil)
|
||||
r := apiRoot.(*Root)
|
||||
|
||||
assert.Equal(t, r.spec.BasePath, tt.baseOutput)
|
||||
|
||||
assert.NotNil(t, r.echo)
|
||||
assert.Len(t, r.echo.Routes(), 2)
|
||||
res := r.echo.Routes()
|
||||
paths := []string{res[0].Path, res[1].Path}
|
||||
assert.ElementsMatch(t, paths, []string{tt.docOutput, tt.specOutput})
|
||||
|
||||
assert.Equal(t, tt.realSpecOutput, connectPath(tt.baseOutput, tt.specOutput))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
r := prepareApiRoot()
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
@ -292,6 +366,21 @@ func TestAddResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUI(t *testing.T) {
|
||||
t.Run("DefaultCDN", func(t *testing.T) {
|
||||
r := New(echo.New(), "/", "doc/", nil)
|
||||
se := r.(*Root)
|
||||
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := se.echo.NewContext(req, rec)
|
||||
h := se.docHandler("/doc/swagger.json")
|
||||
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), DefaultCDN)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SetUI", func(t *testing.T) {
|
||||
r := New(echo.New(), "/", "doc/", nil)
|
||||
se := r.(*Root)
|
||||
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
||||
@ -310,6 +399,18 @@ func TestUI(t *testing.T) {
|
||||
assert.Contains(t, rec.Body.String(), cdn)
|
||||
assert.Contains(t, rec.Body.String(), "#swagger-ui>.swagger-container>.topbar")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScheme(t *testing.T) {
|
||||
r := prepareApiRoot()
|
||||
schemes := []string{"http", "https"}
|
||||
r.SetScheme(schemes...)
|
||||
assert.ElementsMatch(t, r.(*Root).spec.Schemes, schemes)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
r.SetScheme("grpc")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRaw(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user