feat(19.3-02): opt-in /api-docs Swagger UI page with a pinned, SRI-checked swagger-ui-dist
- surf: APIDocsProvider (one plugin) supplies a YAML or JSON OpenAPI document, converted once at boot to compact JSON in source key order; zero or several providers or a spec that does not convert fail boot - http.api_docs.enabled/path/title mount GET /api-docs and /api-docs.json (summercms.apidocs, public, refusing to replace a plugin route) - the page loads swagger-ui-dist 5.33.1 from jsDelivr with sha384 SRI and crossorigin on all three assets, a per-request CSP nonce for its inline script, nosniff and no-referrer - docs: API documentation page section, README rows, ExampleAPIDocsProvider
This commit is contained in:
@@ -72,6 +72,24 @@ func (p *BlogPlugin) Buckets() map[string]surf.Bucket {
|
||||
}
|
||||
}
|
||||
|
||||
// openAPISpec is the plugin's hand-written OpenAPI document. A plugin
|
||||
// usually keeps it in a file and embeds it with //go:embed.
|
||||
const openAPISpec = `openapi: 3.0.3
|
||||
info:
|
||||
title: Blog API
|
||||
version: "1"
|
||||
paths:
|
||||
/api/blog/posts/{id}:
|
||||
get:
|
||||
summary: Show a post
|
||||
`
|
||||
|
||||
// APIDocsSpec supplies the document behind the opt-in /api-docs page
|
||||
// (surf.APIDocsProvider).
|
||||
func (p *BlogPlugin) APIDocsSpec() ([]byte, error) {
|
||||
return []byte(openAPISpec), nil
|
||||
}
|
||||
|
||||
// Routes is the Go form of the plugin's routes.php.
|
||||
func (p *BlogPlugin) Routes(r pact.Router) error {
|
||||
r.Group("/api/blog", surf.Use("throttle:60,1"), func(g pact.Router) {
|
||||
@@ -195,6 +213,27 @@ func ExampleClientIP() {
|
||||
// 203.0.113.7
|
||||
}
|
||||
|
||||
func ExampleAPIDocsProvider() {
|
||||
cfg, err := compass.Open(compass.Options{Dir: "config", Env: "development", Environ: []string{}})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
_ = cfg.Set("http.body_limits.default_bytes", 1048576)
|
||||
_ = cfg.Set("http.body_limits.upload_bytes", 1048576)
|
||||
_ = cfg.Set("http.api_docs.enabled", true)
|
||||
h, err := surf.Assemble(backpack.New(cfg), []party.Plugin{&BlogPlugin{}})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, httptest.NewRequest("GET", "/api-docs.json", nil))
|
||||
fmt.Println(rec.Code, rec.Body.String())
|
||||
// Output:
|
||||
// 200 {"openapi":"3.0.3","info":{"title":"Blog API","version":"1"},"paths":{"/api/blog/posts/{id}":{"get":{"summary":"Show a post"}}}}
|
||||
}
|
||||
|
||||
// TestDocsDeclarations checks the plugin declarations the Routing and Rate
|
||||
// limiting pages show.
|
||||
func TestDocsDeclarations(t *testing.T) {
|
||||
@@ -205,4 +244,7 @@ func TestDocsDeclarations(t *testing.T) {
|
||||
if b := p.Buckets()["blog.comments"]; b.Max != 1 || b.Decay != time.Minute {
|
||||
t.Fatalf("Buckets() = %+v", b)
|
||||
}
|
||||
if spec, err := p.APIDocsSpec(); err != nil || !strings.HasPrefix(string(spec), "openapi:") {
|
||||
t.Fatalf("APIDocsSpec() = %q, %v", spec, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user