Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"html"
"io"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -202,6 +203,13 @@ type Config struct {
// route altogether.
DocsRenderer string

// DocsRendererConfig is optional renderer-specific config that gets
// JSON-marshaled into the docs HTML. Only the Scalar renderer uses it,
// where it becomes the value of Scalar's `data-configuration` attribute.
// See https://github.com/scalar/scalar/blob/main/documentation/configuration.md
// for the available options.
DocsRendererConfig any

// SchemasPath is the path to the API schemas. If set to `/schemas` it will
// allow clients to get `/schemas/{schema}` to view the schema in a browser
// or for use in editors like VSCode to provide autocomplete & validation.
Expand Down Expand Up @@ -610,6 +618,15 @@ func (a *api) registerDocsRoute() {
"style-src 'unsafe-inline'", // TODO: Somehow drop 'unsafe-inline'
}

var configAttr string
if a.config.DocsRendererConfig != nil {
b, err := json.Marshal(a.config.DocsRendererConfig)
if err != nil {
panic("failed to marshal DocsRendererConfig: " + err.Error())
}
configAttr = ` data-configuration="` + html.EscapeString(string(b)) + `"`
}

body = []byte(`<!doctype html>
<html lang="en">
<head>
Expand All @@ -619,7 +636,7 @@ func (a *api) registerDocsRoute() {
<title>` + title + `</title>
</head>
<body>
<script id="api-reference" data-url="` + openAPIPath + `.json"></script>
<script id="api-reference" data-url="` + openAPIPath + `.json"` + configAttr + `></script>
<script src="https://unpkg.com/@scalar/api-reference@1.44.20/dist/browser/standalone.js" crossorigin integrity="sha384-tMz7GAo6dMy55x9tLFtH+sHtogji6Scmb+feBR31TAHmvSPRUTboK9H3M5NFaP4R"></script>
</body>
</html>`)
Expand Down
37 changes: 37 additions & 0 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,43 @@ func TestDocsRenderers(t *testing.T) {
assert.Contains(t, resp.Body.String(), "@scalar/api-reference")
})

t.Run("ScalarRendererConfig", func(t *testing.T) {
_, api := humatest.New(t, huma.Config{
OpenAPI: &huma.OpenAPI{
Info: &huma.Info{Title: "Test API", Version: "1.0.0"},
},
DocsPath: "/docs",
DocsRenderer: huma.DocsRendererScalar,
DocsRendererConfig: map[string]any{
"theme": "purple",
"layout": "modern",
},
OpenAPIPath: "/openapi",
Formats: huma.DefaultFormats,
})

resp := api.Get("/docs")
assert.Equal(t, http.StatusOK, resp.Code)
assert.Contains(t, resp.Body.String(), `data-configuration="`)
assert.Contains(t, resp.Body.String(), `&#34;theme&#34;:&#34;purple&#34;`)
assert.Contains(t, resp.Body.String(), `&#34;layout&#34;:&#34;modern&#34;`)
})

t.Run("ScalarRendererConfigInvalid", func(t *testing.T) {
assert.Panics(t, func() {
humatest.New(t, huma.Config{
OpenAPI: &huma.OpenAPI{
Info: &huma.Info{Title: "Test API", Version: "1.0.0"},
},
DocsPath: "/docs",
DocsRenderer: huma.DocsRendererScalar,
DocsRendererConfig: make(chan int),
OpenAPIPath: "/openapi",
Formats: huma.DefaultFormats,
})
})
})

t.Run("SwaggerUIRenderer", func(t *testing.T) {
_, api := humatest.New(t, huma.Config{
OpenAPI: &huma.OpenAPI{
Expand Down