Merge pull request #22 from elvinchan/21
#21 Integrated doc HTML and spec JSON into one request
This commit is contained in:
commit
795eb4e894
@ -31,7 +31,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// ApiRoot with Echo instance
|
// ApiRoot with Echo instance
|
||||||
r := echoswagger.New(echo.New(), "", "doc/", nil)
|
r := echoswagger.New(echo.New(), "doc/", nil)
|
||||||
|
|
||||||
// Routes with parameters & responses
|
// Routes with parameters & responses
|
||||||
r.POST("/", createUser).
|
r.POST("/", createUser).
|
||||||
|
@ -31,7 +31,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// ApiRoot with Echo instance
|
// ApiRoot with Echo instance
|
||||||
r := echoswagger.New(echo.New(), "", "doc/", nil)
|
r := echoswagger.New(echo.New(), "doc/", nil)
|
||||||
|
|
||||||
// Routes with parameters & responses
|
// Routes with parameters & responses
|
||||||
r.POST("/", createUser).
|
r.POST("/", createUser).
|
||||||
|
@ -51,9 +51,11 @@ const SwaggerUIContent = `{{define "swagger"}}
|
|||||||
if (!window.location.pathname.endsWith("/")) {
|
if (!window.location.pathname.endsWith("/")) {
|
||||||
specPath = "/" + specPath
|
specPath = "/" + specPath
|
||||||
}
|
}
|
||||||
|
var spec = "{{.spec}}"
|
||||||
// Build a system
|
// Build a system
|
||||||
const ui = SwaggerUIBundle({
|
const ui = SwaggerUIBundle({
|
||||||
url: window.location.origin+window.location.pathname+specPath,
|
url: window.location.origin+window.location.pathname+specPath,
|
||||||
|
spec: spec ? JSON.parse(spec) : undefined,
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
presets: [
|
presets: [
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -18,8 +18,9 @@ func TestMain(t *testing.T) {
|
|||||||
c := e.Echo().NewContext(req, rec)
|
c := e.Echo().NewContext(req, rec)
|
||||||
b, err := ioutil.ReadFile("./swagger.json")
|
b, err := ioutil.ReadFile("./swagger.json")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
if assert.NoError(t, e.(*echoswagger.Root).SpecHandler("/doc")(c)) {
|
s, err := e.(*echoswagger.Root).GetSpec(c, "/doc")
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Nil(t, err)
|
||||||
assert.JSONEq(t, string(b), rec.Body.String())
|
rs, err := json.Marshal(s)
|
||||||
}
|
assert.Nil(t, err)
|
||||||
|
assert.JSONEq(t, string(b), string(rs))
|
||||||
}
|
}
|
||||||
|
24
internal.go
24
internal.go
@ -2,6 +2,7 @@ package echoswagger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -20,6 +21,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UISetting struct {
|
type UISetting struct {
|
||||||
|
DetachSpec bool
|
||||||
HideTop bool
|
HideTop bool
|
||||||
CDN string
|
CDN string
|
||||||
}
|
}
|
||||||
@ -31,7 +33,7 @@ type RawDefine struct {
|
|||||||
Schema *JSONSchema
|
Schema *JSONSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Root) docHandler() echo.HandlerFunc {
|
func (r *Root) docHandler(docPath string) echo.HandlerFunc {
|
||||||
t, err := template.New("swagger").Parse(SwaggerUIContent)
|
t, err := template.New("swagger").Parse(SwaggerUIContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -42,12 +44,26 @@ func (r *Root) docHandler() echo.HandlerFunc {
|
|||||||
cdn = DefaultCDN
|
cdn = DefaultCDN
|
||||||
}
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
t.Execute(buf, map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
"title": r.spec.Info.Title,
|
"title": r.spec.Info.Title,
|
||||||
"hideTop": r.ui.HideTop,
|
|
||||||
"cdn": cdn,
|
"cdn": cdn,
|
||||||
"specName": SpecName,
|
"specName": SpecName,
|
||||||
})
|
}
|
||||||
|
if !r.ui.DetachSpec {
|
||||||
|
spec, err := r.GetSpec(c, docPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(spec)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
params["spec"] = string(b)
|
||||||
|
params["hideTop"] = true
|
||||||
|
} else {
|
||||||
|
params["hideTop"] = r.ui.HideTop
|
||||||
|
}
|
||||||
|
t.Execute(buf, params)
|
||||||
return c.HTMLBlob(http.StatusOK, buf.Bytes())
|
return c.HTMLBlob(http.StatusOK, buf.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
spec.go
24
spec.go
@ -15,26 +15,36 @@ const (
|
|||||||
SpecName = "swagger.json"
|
SpecName = "swagger.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Root) SpecHandler(docPath string) echo.HandlerFunc {
|
func (r *Root) specHandler(docPath string) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
spec, err := r.GetSpec(c, docPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate swagger spec data
|
||||||
|
func (r *Root) GetSpec(c echo.Context, docPath string) (Swagger, error) {
|
||||||
r.once.Do(func() {
|
r.once.Do(func() {
|
||||||
r.err = r.genSpec(c)
|
r.err = r.genSpec(c)
|
||||||
r.cleanUp()
|
r.cleanUp()
|
||||||
})
|
})
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return c.String(http.StatusInternalServerError, r.err.Error())
|
return Swagger{}, r.err
|
||||||
}
|
}
|
||||||
|
swagger := *r.spec
|
||||||
var basePath string
|
var basePath string
|
||||||
if uri, err := url.ParseRequestURI(c.Request().Referer()); err == nil {
|
if uri, err := url.ParseRequestURI(c.Request().Referer()); err == nil {
|
||||||
basePath = trimSuffixSlash(uri.Path, docPath)
|
basePath = trimSuffixSlash(uri.Path, docPath)
|
||||||
r.spec.Host = uri.Host
|
swagger.Host = uri.Host
|
||||||
} else {
|
} else {
|
||||||
basePath = trimSuffixSlash(c.Request().URL.Path, connectPath(docPath, SpecName))
|
basePath = trimSuffixSlash(c.Request().URL.Path, connectPath(docPath, SpecName))
|
||||||
r.spec.Host = c.Request().Host
|
swagger.Host = c.Request().Host
|
||||||
}
|
|
||||||
r.spec.BasePath = connectPath(basePath)
|
|
||||||
return c.JSON(http.StatusOK, r.spec)
|
|
||||||
}
|
}
|
||||||
|
swagger.BasePath = connectPath(basePath)
|
||||||
|
return swagger, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Root) genSpec(c echo.Context) error {
|
func (r *Root) genSpec(c echo.Context) error {
|
||||||
|
50
spec_test.go
50
spec_test.go
@ -21,12 +21,26 @@ func TestSpec(t *testing.T) {
|
|||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
c := e.NewContext(req, rec)
|
c := e.NewContext(req, rec)
|
||||||
j := `{"swagger":"2.0","info":{"title":"Project APIs","version":""},"host":"example.com","basePath":"/","paths":{}}`
|
j := `{"swagger":"2.0","info":{"title":"Project APIs","version":""},"host":"example.com","basePath":"/","paths":{}}`
|
||||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc/")(c)) {
|
if assert.NoError(t, r.(*Root).specHandler("/doc/")(c)) {
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
assert.JSONEq(t, j, rec.Body.String())
|
assert.JSONEq(t, j, rec.Body.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("BasicGenerater", func(t *testing.T) {
|
||||||
|
r := prepareApiRoot()
|
||||||
|
e := r.(*Root).echo
|
||||||
|
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":"/","paths":{}}`
|
||||||
|
s, err := r.(*Root).GetSpec(c, "/doc/")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
rs, err := json.Marshal(s)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.JSONEq(t, j, string(rs))
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Methods", func(t *testing.T) {
|
t.Run("Methods", func(t *testing.T) {
|
||||||
r := prepareApiRoot()
|
r := prepareApiRoot()
|
||||||
var h echo.HandlerFunc
|
var h echo.HandlerFunc
|
||||||
@ -41,7 +55,7 @@ func TestSpec(t *testing.T) {
|
|||||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
c := e.NewContext(req, rec)
|
c := e.NewContext(req, rec)
|
||||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) {
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
s := r.(*Root).spec
|
s := r.(*Root).spec
|
||||||
assert.Len(t, s.Paths, 1)
|
assert.Len(t, s.Paths, 1)
|
||||||
@ -55,32 +69,6 @@ func TestSpec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ErrorGroupSecurity", func(t *testing.T) {
|
|
||||||
r := prepareApiRoot()
|
|
||||||
e := r.(*Root).echo
|
|
||||||
var h echo.HandlerFunc
|
|
||||||
r.Group("G", "/g").SetSecurity("JWT").GET("/", h)
|
|
||||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
c := e.NewContext(req, rec)
|
|
||||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
|
||||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ErrorApiSecurity", func(t *testing.T) {
|
|
||||||
r := prepareApiRoot()
|
|
||||||
e := r.(*Root).echo
|
|
||||||
var h echo.HandlerFunc
|
|
||||||
r.GET("/", h).SetSecurity("JWT")
|
|
||||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
c := e.NewContext(req, rec)
|
|
||||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
|
||||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("CleanUp", func(t *testing.T) {
|
t.Run("CleanUp", func(t *testing.T) {
|
||||||
r := prepareApiRoot()
|
r := prepareApiRoot()
|
||||||
e := r.(*Root).echo
|
e := r.(*Root).echo
|
||||||
@ -96,7 +84,7 @@ func TestSpec(t *testing.T) {
|
|||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
c := e.NewContext(req, rec)
|
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"}]}`
|
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).SpecHandler("/doc")(c)) {
|
if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) {
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
assert.JSONEq(t, j, rec.Body.String())
|
assert.JSONEq(t, j, rec.Body.String())
|
||||||
}
|
}
|
||||||
@ -184,7 +172,7 @@ func TestReferer(t *testing.T) {
|
|||||||
req.Header.Add("referer", tt.referer)
|
req.Header.Add("referer", tt.referer)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
c := e.NewContext(req, rec)
|
c := e.NewContext(req, rec)
|
||||||
if assert.NoError(t, r.(*Root).SpecHandler(tt.docPath)(c)) {
|
if assert.NoError(t, r.(*Root).specHandler(tt.docPath)(c)) {
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
var v struct {
|
var v struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
@ -226,7 +214,7 @@ func TestAddDefinition(t *testing.T) {
|
|||||||
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
c := e.NewContext(req, rec)
|
c := e.NewContext(req, rec)
|
||||||
if assert.NoError(t, r.(*Root).SpecHandler("/doc")(c)) {
|
if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) {
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
assert.Len(t, r.(*Root).spec.Definitions, 2)
|
assert.Len(t, r.(*Root).spec.Definitions, 2)
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@ type ApiRoot interface {
|
|||||||
AddSecurityOAuth2(name, desc string, flow OAuth2FlowType, authorizationUrl, tokenUrl string, scopes map[string]string) ApiRoot
|
AddSecurityOAuth2(name, desc string, flow OAuth2FlowType, authorizationUrl, tokenUrl string, scopes map[string]string) ApiRoot
|
||||||
|
|
||||||
// SetUI sets UI setting.
|
// SetUI sets UI setting.
|
||||||
|
// If DetachSpec is false, HideTop will not take effect
|
||||||
SetUI(ui UISetting) ApiRoot
|
SetUI(ui UISetting) ApiRoot
|
||||||
|
|
||||||
// SetScheme sets available protocol schemes.
|
// SetScheme sets available protocol schemes.
|
||||||
@ -232,8 +233,8 @@ func New(e *echo.Echo, docPath string, i *Info) ApiRoot {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
e.GET(connectPath(docPath), r.docHandler())
|
e.GET(connectPath(docPath), r.docHandler(docPath))
|
||||||
e.GET(connectPath(docPath, SpecName), r.SpecHandler(docPath))
|
e.GET(connectPath(docPath, SpecName), r.specHandler(docPath))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ func TestUI(t *testing.T) {
|
|||||||
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
c := se.echo.NewContext(req, rec)
|
c := se.echo.NewContext(req, rec)
|
||||||
h := se.docHandler()
|
h := se.docHandler("doc/")
|
||||||
|
|
||||||
if assert.NoError(t, h(c)) {
|
if assert.NoError(t, h(c)) {
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
@ -344,7 +344,7 @@ func TestUI(t *testing.T) {
|
|||||||
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
c := se.echo.NewContext(req, rec)
|
c := se.echo.NewContext(req, rec)
|
||||||
h := se.docHandler()
|
h := se.docHandler("doc/")
|
||||||
|
|
||||||
cdn := "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.18.0"
|
cdn := "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.18.0"
|
||||||
r.SetUI(UISetting{
|
r.SetUI(UISetting{
|
||||||
@ -355,6 +355,17 @@ func TestUI(t *testing.T) {
|
|||||||
if assert.NoError(t, h(c)) {
|
if assert.NoError(t, h(c)) {
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
assert.Contains(t, rec.Body.String(), cdn)
|
assert.Contains(t, rec.Body.String(), cdn)
|
||||||
|
assert.Contains(t, rec.Body.String(), `{\x22swagger\x22:`)
|
||||||
|
assert.Contains(t, rec.Body.String(), "#swagger-ui>.swagger-container>.topbar")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.SetUI(UISetting{
|
||||||
|
DetachSpec: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if assert.NoError(t, h(c)) {
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.NotContains(t, rec.Body.String(), "{\x22swagger\x22:")
|
||||||
assert.Contains(t, rec.Body.String(), "#swagger-ui>.swagger-container>.topbar")
|
assert.Contains(t, rec.Body.String(), "#swagger-ui>.swagger-container>.topbar")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -474,3 +485,37 @@ func TestEcho(t *testing.T) {
|
|||||||
a := prepareApi()
|
a := prepareApi()
|
||||||
assert.NotNil(t, a.Route())
|
assert.NotNil(t, a.Route())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandlers(t *testing.T) {
|
||||||
|
t.Run("ErrorGroupSecurity", func(t *testing.T) {
|
||||||
|
r := prepareApiRoot()
|
||||||
|
e := r.(*Root).echo
|
||||||
|
var h echo.HandlerFunc
|
||||||
|
r.Group("G", "/g").SetSecurity("JWT").GET("/", h)
|
||||||
|
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) {
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||||
|
}
|
||||||
|
if assert.NoError(t, r.(*Root).docHandler("/doc")(c)) {
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ErrorApiSecurity", func(t *testing.T) {
|
||||||
|
r := prepareApiRoot()
|
||||||
|
e := r.(*Root).echo
|
||||||
|
var h echo.HandlerFunc
|
||||||
|
r.GET("/", h).SetSecurity("JWT")
|
||||||
|
req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) {
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||||
|
}
|
||||||
|
if assert.NoError(t, r.(*Root).docHandler("/doc")(c)) {
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user