#21 Integrated doc HTML and spec JSON into one request

master
elvinchan 2019-05-12 23:10:04 +08:00
parent 818f3e3ce0
commit 526c519e76
9 changed files with 128 additions and 65 deletions

View File

@ -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).

View File

@ -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).

View File

@ -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: [

View File

@ -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))
}

View File

@ -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
View File

@ -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{})

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
})
}