Merge pull request #20 from elvinchan/19
#19 Retrieve bathPath automatically
This commit is contained in:
commit
818f3e3ce0
@ -56,10 +56,8 @@ func createUser(c echo.Context) error {
|
||||
## Usage
|
||||
#### Create a `ApiRoot` with `New()`, which is a wrapper of `echo.New()`
|
||||
```
|
||||
r := echoswagger.New(echo.New(), "/v1", "doc/", nil)
|
||||
r := echoswagger.New(echo.New(), "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, Scheme, etc.
|
||||
```
|
||||
|
@ -58,8 +58,6 @@ 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选项,Scheme等。
|
||||
```
|
||||
|
@ -47,10 +47,13 @@ const SwaggerUIContent = `{{define "swagger"}}
|
||||
<script src="{{.cdn}}/swagger-ui-standalone-preset.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
|
||||
var specPath = "{{.specName}}"
|
||||
if (!window.location.pathname.endsWith("/")) {
|
||||
specPath = "/" + specPath
|
||||
}
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: this.window.location.origin+{{.path}},
|
||||
url: window.location.origin+window.location.pathname+specPath,
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
|
@ -13,7 +13,7 @@ func main() {
|
||||
func initServer() echoswagger.ApiRoot {
|
||||
e := echo.New()
|
||||
|
||||
se := echoswagger.New(e, "", "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",
|
||||
|
@ -18,7 +18,7 @@ func TestMain(t *testing.T) {
|
||||
c := e.Echo().NewContext(req, rec)
|
||||
b, err := ioutil.ReadFile("./swagger.json")
|
||||
assert.Nil(t, err)
|
||||
if assert.NoError(t, e.(*echoswagger.Root).Spec(c)) {
|
||||
if assert.NoError(t, e.(*echoswagger.Root).SpecHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.JSONEq(t, string(b), rec.Body.String())
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ type RawDefine struct {
|
||||
Schema *JSONSchema
|
||||
}
|
||||
|
||||
func (r *Root) docHandler(realSpecPath string) echo.HandlerFunc {
|
||||
func (r *Root) docHandler() echo.HandlerFunc {
|
||||
t, err := template.New("swagger").Parse(SwaggerUIContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -45,8 +45,8 @@ func (r *Root) docHandler(realSpecPath string) echo.HandlerFunc {
|
||||
t.Execute(buf, map[string]interface{}{
|
||||
"title": r.spec.Info.Title,
|
||||
"hideTop": r.ui.HideTop,
|
||||
"path": realSpecPath,
|
||||
"cdn": cdn,
|
||||
"specName": SpecName,
|
||||
})
|
||||
return c.HTMLBlob(http.StatusOK, buf.Bytes())
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSecurity(t *testing.T) {
|
||||
r := New(echo.New(), "/", "doc/", nil)
|
||||
r := New(echo.New(), "doc/", nil)
|
||||
scope := map[string]string{
|
||||
"read:users": "read users",
|
||||
"write:users": "modify users",
|
||||
@ -194,7 +194,7 @@ func TestSecurity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSecurityRepeat(t *testing.T) {
|
||||
r := New(echo.New(), "/", "doc/", nil)
|
||||
r := New(echo.New(), "doc/", nil)
|
||||
scope := map[string]string{
|
||||
"read:users": "read users",
|
||||
"write:users": "modify users",
|
||||
|
9
spec.go
9
spec.go
@ -12,9 +12,11 @@ import (
|
||||
const (
|
||||
DefPrefix = "#/definitions/"
|
||||
SwaggerVersion = "2.0"
|
||||
SpecName = "swagger.json"
|
||||
)
|
||||
|
||||
func (r *Root) Spec(c echo.Context) error {
|
||||
func (r *Root) SpecHandler(docPath string) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
r.once.Do(func() {
|
||||
r.err = r.genSpec(c)
|
||||
r.cleanUp()
|
||||
@ -22,12 +24,17 @@ func (r *Root) Spec(c echo.Context) error {
|
||||
if r.err != nil {
|
||||
return c.String(http.StatusInternalServerError, r.err.Error())
|
||||
}
|
||||
var basePath string
|
||||
if uri, err := url.ParseRequestURI(c.Request().Referer()); err == nil {
|
||||
basePath = trimSuffixSlash(uri.Path, docPath)
|
||||
r.spec.Host = uri.Host
|
||||
} else {
|
||||
basePath = trimSuffixSlash(c.Request().URL.Path, connectPath(docPath, SpecName))
|
||||
r.spec.Host = c.Request().Host
|
||||
}
|
||||
r.spec.BasePath = connectPath(basePath)
|
||||
return c.JSON(http.StatusOK, r.spec)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Root) genSpec(c echo.Context) error {
|
||||
|
71
spec_test.go
71
spec_test.go
@ -21,7 +21,7 @@ func TestSpec(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
j := `{"swagger":"2.0","info":{"title":"Project APIs","version":""},"host":"example.com","basePath":"/","paths":{}}`
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc/")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.JSONEq(t, j, rec.Body.String())
|
||||
}
|
||||
@ -41,7 +41,7 @@ func TestSpec(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
s := r.(*Root).spec
|
||||
assert.Len(t, s.Paths, 1)
|
||||
@ -63,7 +63,7 @@ func TestSpec(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
}
|
||||
})
|
||||
@ -76,7 +76,7 @@ func TestSpec(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
}
|
||||
})
|
||||
@ -96,7 +96,7 @@ func TestSpec(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
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)) {
|
||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.JSONEq(t, j, rec.Body.String())
|
||||
}
|
||||
@ -108,29 +108,72 @@ func TestSpec(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRefererHost(t *testing.T) {
|
||||
func TestReferer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, referer, host string
|
||||
name, referer, host, docPath, basePath string
|
||||
}{
|
||||
{
|
||||
referer: "http://localhost:1323/doc",
|
||||
host: "localhost:1323",
|
||||
docPath: "/doc",
|
||||
name: "A",
|
||||
basePath: "/",
|
||||
},
|
||||
{
|
||||
referer: "http://localhost:1323/doc",
|
||||
host: "localhost:1323",
|
||||
docPath: "/doc/",
|
||||
name: "B",
|
||||
basePath: "/",
|
||||
},
|
||||
{
|
||||
referer: "http://localhost:1323/doc/",
|
||||
host: "localhost:1323",
|
||||
docPath: "/doc",
|
||||
name: "C",
|
||||
basePath: "/",
|
||||
},
|
||||
{
|
||||
referer: "http://localhost:1323/api/v1/doc",
|
||||
host: "localhost:1323",
|
||||
docPath: "/doc",
|
||||
name: "D",
|
||||
basePath: "/api/v1",
|
||||
},
|
||||
{
|
||||
referer: "1/doc",
|
||||
host: "127.0.0.1",
|
||||
name: "B",
|
||||
docPath: "/doc",
|
||||
name: "E",
|
||||
basePath: "/",
|
||||
},
|
||||
{
|
||||
referer: "http://user:pass@github.com",
|
||||
host: "github.com",
|
||||
name: "C",
|
||||
docPath: "/",
|
||||
name: "F",
|
||||
basePath: "/",
|
||||
},
|
||||
{
|
||||
referer: "https://www.github.com?q=1",
|
||||
referer: "https://www.github.com/v1/docs/?q=1",
|
||||
host: "www.github.com",
|
||||
name: "D",
|
||||
docPath: "/docs/",
|
||||
name: "G",
|
||||
basePath: "/v1",
|
||||
},
|
||||
{
|
||||
referer: "https://www.github.com/?q=1#tag=TAG",
|
||||
host: "www.github.com",
|
||||
docPath: "",
|
||||
name: "H",
|
||||
basePath: "/",
|
||||
},
|
||||
{
|
||||
referer: "https://www.github.com/",
|
||||
host: "www.github.com",
|
||||
docPath: "/doc",
|
||||
name: "I",
|
||||
basePath: "/",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -141,14 +184,16 @@ func TestRefererHost(t *testing.T) {
|
||||
req.Header.Add("referer", tt.referer)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
if assert.NoError(t, r.(*Root).SpecHandler(tt.docPath)(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
var v struct {
|
||||
Host string `json:"host"`
|
||||
BasePath string `json:"basePath"`
|
||||
}
|
||||
err := json.Unmarshal(rec.Body.Bytes(), &v)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.host, v.Host)
|
||||
assert.Equal(t, tt.basePath, v.BasePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -181,7 +226,7 @@ func TestAddDefinition(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
if assert.NoError(t, r.(*Root).Spec(c)) {
|
||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Len(t, r.(*Root).spec.Definitions, 2)
|
||||
}
|
||||
|
21
utils.go
21
utils.go
@ -1,6 +1,9 @@
|
||||
package echoswagger
|
||||
|
||||
import "reflect"
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func contains(list []string, s string) bool {
|
||||
for _, t := range list {
|
||||
@ -85,3 +88,19 @@ func connectPath(paths ...string) string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func removeTrailingSlash(path string) string {
|
||||
l := len(path) - 1
|
||||
if l > 0 && strings.HasSuffix(path, "/") {
|
||||
path = path[:l]
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func trimSuffixSlash(s, suffix string) string {
|
||||
s = connectPath(s)
|
||||
suffix = connectPath(suffix)
|
||||
s = removeTrailingSlash(s)
|
||||
suffix = removeTrailingSlash(suffix)
|
||||
return strings.TrimSuffix(s, suffix)
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ type api struct {
|
||||
|
||||
// New creates ApiRoot instance.
|
||||
// Multiple ApiRoot are allowed in one project.
|
||||
func New(e *echo.Echo, basePath, docPath string, i *Info) ApiRoot {
|
||||
func New(e *echo.Echo, docPath string, i *Info) ApiRoot {
|
||||
if e == nil {
|
||||
panic("echoswagger: invalid Echo instance")
|
||||
}
|
||||
@ -225,7 +225,6 @@ func New(e *echo.Echo, basePath, docPath string, i *Info) ApiRoot {
|
||||
spec: &Swagger{
|
||||
Info: i,
|
||||
SecurityDefinitions: make(map[string]*SecurityDefinition),
|
||||
BasePath: connectPath(basePath),
|
||||
Definitions: make(map[string]*JSONSchema),
|
||||
},
|
||||
routers: routers{
|
||||
@ -233,10 +232,8 @@ func New(e *echo.Echo, basePath, docPath string, i *Info) ApiRoot {
|
||||
},
|
||||
}
|
||||
|
||||
specPath := connectPath(docPath, "swagger.json")
|
||||
realSpecPath := connectPath(basePath, specPath)
|
||||
e.GET(connectPath(docPath), r.docHandler(realSpecPath))
|
||||
e.GET(specPath, r.Spec)
|
||||
e.GET(connectPath(docPath), r.docHandler())
|
||||
e.GET(connectPath(docPath, SpecName), r.SpecHandler(docPath))
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func prepareApiRoot() ApiRoot {
|
||||
return New(echo.New(), "/", "doc/", nil)
|
||||
return New(echo.New(), "doc/", nil)
|
||||
}
|
||||
|
||||
func prepareApiGroup() ApiGroup {
|
||||
@ -29,7 +29,6 @@ func prepareApi() Api {
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
echo *echo.Echo
|
||||
basePath string
|
||||
docPath string
|
||||
info *Info
|
||||
expectPaths []string
|
||||
@ -38,7 +37,6 @@ func TestNew(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
echo: echo.New(),
|
||||
basePath: "/",
|
||||
docPath: "doc/",
|
||||
info: nil,
|
||||
expectPaths: []string{"/doc/", "/doc/swagger.json"},
|
||||
@ -47,7 +45,6 @@ func TestNew(t *testing.T) {
|
||||
},
|
||||
{
|
||||
echo: echo.New(),
|
||||
basePath: "/",
|
||||
docPath: "doc",
|
||||
info: &Info{
|
||||
Title: "Test project",
|
||||
@ -69,16 +66,15 @@ func TestNew(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.panic {
|
||||
assert.Panics(t, func() {
|
||||
New(tt.echo, tt.basePath, tt.docPath, tt.info)
|
||||
New(tt.echo, tt.docPath, tt.info)
|
||||
})
|
||||
} else {
|
||||
apiRoot := New(tt.echo, tt.basePath, tt.docPath, tt.info)
|
||||
apiRoot := New(tt.echo, tt.docPath, tt.info)
|
||||
assert.NotNil(t, apiRoot.(*Root))
|
||||
|
||||
r := apiRoot.(*Root)
|
||||
assert.NotNil(t, r.spec)
|
||||
|
||||
assert.Equal(t, r.spec.BasePath, tt.basePath)
|
||||
if tt.info == nil {
|
||||
assert.Equal(t, r.spec.Info.Title, "Project APIs")
|
||||
} else {
|
||||
@ -97,74 +93,36 @@ func TestNew(t *testing.T) {
|
||||
|
||||
func TestPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
baseInput, docInput string
|
||||
baseOutput, docOutput, specOutput, realSpecOutput string
|
||||
docInput string
|
||||
docOutput, specOutput 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)
|
||||
apiRoot := New(echo.New(), 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -367,12 +325,12 @@ func TestAddResponse(t *testing.T) {
|
||||
|
||||
func TestUI(t *testing.T) {
|
||||
t.Run("DefaultCDN", func(t *testing.T) {
|
||||
r := New(echo.New(), "/", "doc/", nil)
|
||||
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")
|
||||
h := se.docHandler()
|
||||
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
@ -381,12 +339,12 @@ func TestUI(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("SetUI", func(t *testing.T) {
|
||||
r := New(echo.New(), "/", "doc/", nil)
|
||||
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")
|
||||
h := se.docHandler()
|
||||
|
||||
cdn := "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.18.0"
|
||||
r.SetUI(UISetting{
|
||||
|
Loading…
Reference in New Issue
Block a user