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() { | ||||
| 	// 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)) | ||||
| } | ||||
|  | ||||
							
								
								
									
										24
									
								
								internal.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								internal.go
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ package echoswagger | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"html/template" | ||||
| 	"net/http" | ||||
| 	"reflect" | ||||
| @ -20,6 +21,7 @@ const ( | ||||
| ) | ||||
| 
 | ||||
| type UISetting struct { | ||||
| 	DetachSpec bool | ||||
| 	HideTop    bool | ||||
| 	CDN        string | ||||
| } | ||||
| @ -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()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										24
									
								
								spec.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								spec.go
									
									
									
									
									
								
							| @ -15,26 +15,36 @@ 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 { | ||||
| 		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.err = r.genSpec(c) | ||||
| 		r.cleanUp() | ||||
| 	}) | ||||
| 	if r.err != nil { | ||||
| 			return c.String(http.StatusInternalServerError, r.err.Error()) | ||||
| 		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) | ||||
| 			r.spec.Host = uri.Host | ||||
| 		swagger.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) | ||||
| 		swagger.Host = c.Request().Host | ||||
| 	} | ||||
| 	swagger.BasePath = connectPath(basePath) | ||||
| 	return swagger, nil | ||||
| } | ||||
| 
 | ||||
| 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() | ||||
| 		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