#21 Integrated doc HTML and spec JSON into one request
This commit is contained in:
parent
818f3e3ce0
commit
526c519e76
@ -31,7 +31,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// ApiRoot with Echo instance
|
||||
r := echoswagger.New(echo.New(), "", "doc/", nil)
|
||||
r := echoswagger.New(echo.New(), "doc/", nil)
|
||||
|
||||
// Routes with parameters & responses
|
||||
r.POST("/", createUser).
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// ApiRoot with Echo instance
|
||||
r := echoswagger.New(echo.New(), "", "doc/", nil)
|
||||
r := echoswagger.New(echo.New(), "doc/", nil)
|
||||
|
||||
// Routes with parameters & responses
|
||||
r.POST("/", createUser).
|
||||
|
@ -51,9 +51,11 @@ const SwaggerUIContent = `{{define "swagger"}}
|
||||
if (!window.location.pathname.endsWith("/")) {
|
||||
specPath = "/" + specPath
|
||||
}
|
||||
var spec = "{{.spec}}"
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: window.location.origin+window.location.pathname+specPath,
|
||||
spec: spec ? JSON.parse(spec) : undefined,
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
|
@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
@ -18,8 +18,9 @@ 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).SpecHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.JSONEq(t, string(b), rec.Body.String())
|
||||
}
|
||||
s, err := e.(*echoswagger.Root).GetSpec(c, "/doc")
|
||||
assert.Nil(t, err)
|
||||
rs, err := json.Marshal(s)
|
||||
assert.Nil(t, err)
|
||||
assert.JSONEq(t, string(b), string(rs))
|
||||
}
|
||||
|
28
internal.go
28
internal.go
@ -2,6 +2,7 @@ package echoswagger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@ -20,8 +21,9 @@ const (
|
||||
)
|
||||
|
||||
type UISetting struct {
|
||||
HideTop bool
|
||||
CDN string
|
||||
DetachSpec bool
|
||||
HideTop bool
|
||||
CDN string
|
||||
}
|
||||
|
||||
type RawDefineDic map[string]RawDefine
|
||||
@ -31,7 +33,7 @@ type RawDefine struct {
|
||||
Schema *JSONSchema
|
||||
}
|
||||
|
||||
func (r *Root) docHandler() echo.HandlerFunc {
|
||||
func (r *Root) docHandler(docPath string) echo.HandlerFunc {
|
||||
t, err := template.New("swagger").Parse(SwaggerUIContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -42,12 +44,26 @@ func (r *Root) docHandler() echo.HandlerFunc {
|
||||
cdn = DefaultCDN
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
t.Execute(buf, map[string]interface{}{
|
||||
params := map[string]interface{}{
|
||||
"title": r.spec.Info.Title,
|
||||
"hideTop": r.ui.HideTop,
|
||||
"cdn": cdn,
|
||||
"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())
|
||||
}
|
||||
}
|
||||
|
44
spec.go
44
spec.go
@ -15,28 +15,38 @@ const (
|
||||
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 {
|
||||
r.once.Do(func() {
|
||||
r.err = r.genSpec(c)
|
||||
r.cleanUp()
|
||||
})
|
||||
if r.err != nil {
|
||||
return c.String(http.StatusInternalServerError, r.err.Error())
|
||||
spec, err := r.GetSpec(c, docPath)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, 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)
|
||||
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.err = r.genSpec(c)
|
||||
r.cleanUp()
|
||||
})
|
||||
if r.err != nil {
|
||||
return Swagger{}, r.err
|
||||
}
|
||||
swagger := *r.spec
|
||||
var basePath string
|
||||
if uri, err := url.ParseRequestURI(c.Request().Referer()); err == nil {
|
||||
basePath = trimSuffixSlash(uri.Path, docPath)
|
||||
swagger.Host = uri.Host
|
||||
} else {
|
||||
basePath = trimSuffixSlash(c.Request().URL.Path, connectPath(docPath, SpecName))
|
||||
swagger.Host = c.Request().Host
|
||||
}
|
||||
swagger.BasePath = connectPath(basePath)
|
||||
return swagger, nil
|
||||
}
|
||||
|
||||
func (r *Root) genSpec(c echo.Context) error {
|
||||
r.spec.Swagger = SwaggerVersion
|
||||
r.spec.Paths = make(map[string]interface{})
|
||||
|
50
spec_test.go
50
spec_test.go
@ -21,12 +21,26 @@ 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).SpecHandler("/doc/")(c)) {
|
||||
if assert.NoError(t, r.(*Root).specHandler("/doc/")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
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) {
|
||||
r := prepareApiRoot()
|
||||
var h echo.HandlerFunc
|
||||
@ -41,7 +55,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).SpecHandler("/doc")(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)
|
||||
@ -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) {
|
||||
r := prepareApiRoot()
|
||||
e := r.(*Root).echo
|
||||
@ -96,7 +84,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).SpecHandler("/doc")(c)) {
|
||||
if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.JSONEq(t, j, rec.Body.String())
|
||||
}
|
||||
@ -184,7 +172,7 @@ func TestReferer(t *testing.T) {
|
||||
req.Header.Add("referer", tt.referer)
|
||||
rec := httptest.NewRecorder()
|
||||
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)
|
||||
var v struct {
|
||||
Host string `json:"host"`
|
||||
@ -226,7 +214,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).SpecHandler("/doc")(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)
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ type ApiRoot interface {
|
||||
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.
|
||||
@ -232,8 +233,8 @@ func New(e *echo.Echo, docPath string, i *Info) ApiRoot {
|
||||
},
|
||||
}
|
||||
|
||||
e.GET(connectPath(docPath), r.docHandler())
|
||||
e.GET(connectPath(docPath, SpecName), r.SpecHandler(docPath))
|
||||
e.GET(connectPath(docPath), r.docHandler(docPath))
|
||||
e.GET(connectPath(docPath, SpecName), r.specHandler(docPath))
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -330,7 +330,7 @@ func TestUI(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := se.echo.NewContext(req, rec)
|
||||
h := se.docHandler()
|
||||
h := se.docHandler("doc/")
|
||||
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
@ -344,7 +344,7 @@ func TestUI(t *testing.T) {
|
||||
req := httptest.NewRequest(echo.GET, "/doc/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
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"
|
||||
r.SetUI(UISetting{
|
||||
@ -355,6 +355,17 @@ func TestUI(t *testing.T) {
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
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")
|
||||
}
|
||||
})
|
||||
@ -474,3 +485,37 @@ func TestEcho(t *testing.T) {
|
||||
a := prepareApi()
|
||||
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