Skip to content

feat(docs): enhance Scalar renderer with custom script and font sourc… - #1107

Open
Orfeo42 wants to merge 2 commits into
danielgtaylor:mainfrom
Orfeo42:feature/update-scalar-api-reference
Open

feat(docs): enhance Scalar renderer with custom script and font sourc…#1107
Orfeo42 wants to merge 2 commits into
danielgtaylor:mainfrom
Orfeo42:feature/update-scalar-api-reference

Conversation

@Orfeo42

@Orfeo42 Orfeo42 commented Aug 24, 2026

Copy link
Copy Markdown

Closes: #1103

Summary

  • Bump the pinned Scalar renderer from @scalar/api-reference@1.44.20 to 1.66.1 with a fresh SRI hash, and add Config.DocsScalar (ScalarDocsConfig with ScriptURL, ScriptIntegrity, FontSrc) to override the script location and font source without waiting for a huma release. Override values are validated at registration (rejecting ;, ,, quotes, whitespace, <, >) to prevent CSP and HTML injection.
  • Switch the Scalar docs page from the declarative data-configuration attribute to the Scalar.createApiReference('#app', {...}) JS API, allowed via a CSP sha256 hash of the static inline script. This makes the full Scalar configuration work through DocsRendererConfig, including multi-document sources; huma injects the default url only when the config declares none of url, sources, or content.
  • Tighten the Scalar CSP: drop 'unsafe-eval' from script-src (verified unused by 1.66.1 in-browser) and add font-src for https://fonts.scalar.com (previously blocked by the default-src 'none' fallback, breaking font loading). style-src 'unsafe-inline' remains — Scalar applies inline style attributes, which no nonce or hash can allow; tracked by the existing TODO.
  • Escape the page title with html.EscapeString and precompute the CSP header once at registration in registerDocsRoute (api.go).
  • Extract the Scalar branch of registerDocsRoute into focused helpers: scalarScriptFor, scalarFontSrc, scalarConfigJSON, scalarCSP, scalarPage.
  • Extend TestDocsRenderers with coverage for custom script URL/integrity, font-src override, multi-document config, config-shape validation, CSP/comma injection rejection, and title escaping.

Breaking Changes

  • DocsRendererConfig for the Scalar renderer must now marshal to a JSON object; any other JSON shape panics at API creation (previously any JSON value was serialized into data-configuration).
  • The Scalar docs HTML no longer contains the <script id="api-reference" data-url=... data-configuration=...> element; anything scraping or post-processing that markup must adapt to the new <div id="app"> + inline Scalar.createApiReference structure.

@wolveix

wolveix commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Hi @Orfeo42, thanks for your contribution. We specifically want to avoid registering renderer-specific docs variables (e.g. Config.DocsScalar), nor do I think the new Scalar change is necessary (and, as you said, will break many existing implementations). This looks very AI-driven, with very little regard for the maintainability or usability of the library if I'm honest (no offense intended) :(

I've admittedly only skimmed through this code and haven't tested it locally, but could you walk through some of the design decisions please?

@Orfeo42

Orfeo42 commented Aug 25, 2026

Copy link
Copy Markdown
Author

Hi @wolveix, and thanks for the review:

I understand the suspicion and yes I used some AI during develop, mostly by making it review some part that i could have missed and some research during develop, not for the full implementation, and the part written by AI are manually reviewed!
I don't want to waste your time and I'm sorry if it looked like this.
This part aside the implementation is in 3 marco parts:

  1. Updated scalar and extracted as a variable to avoid repetitions and make it easier to change in the future (explicit name)
  2. I added a new struct to manage the Scalar config
    a. added ScalarDocsConfig a struct with 3 fields (i could have reduce to 2 if inside it I use scalarScript but i don't like too much nesting. I can do if you prefer this) used for the various parameters used for configuration (if you like it flat no issue I can change it).
    b. added FontSrc (in the previous struct) to fix a console error when scalar was rendering and added default value
    c. extracted some function to reduce the size of function and read them better (is just my taste if you like it can revert it as a bigger function)
    About this point, if you prefer to not have this configuration at all I can remove this part. I thouth it could make it easier to maintain not harder, so let me know.
  3. The issue asks for multi-document support, and Scalar does it with the sources option (an array of {url, title}). The problem is that the old way huma initialized Scalar (the data-configuration attribute on the script tag) ignores sources completely.
    I tested this in the browser, the option is only supported through the JS API Scalar.createApiReference('#app', {...}). So to support multi-document I had to switch the init method, it was not a choice of taste.

The config passed to createApiReference comes from the already existing DocsRendererConfig field, no new config field is needed for this part. I only inject the default "url": "<OpenAPIPath>.json" when the user config doesn't already declare a document (url, sources or content), so for existing single-document users nothing changes and multi-document is opt-in:

config.DocsRendererConfig = map[string]any{
    "sources": []map[string]any{
        {"url": "/openapi.json", "title": "Main API"},
        {"url": "/admin/openapi.json", "title": "Admin API"},
    },
}

Since the init is now an inline script, I didn't want to add 'unsafe-inline' to script-src, so the CSP carries the sha256 hash of the exact script instead (the script is static per config, and json.Marshal sorts map keys so the hash is deterministic, and it escapes <>& so the config can't break out of the script tag).

To be transparent, the breaking parts of this are two: DocsRendererConfig must now marshal to a JSON object (startup panic otherwise, before any JSON value was silently embedded in the attribute), and the docs page HTML changed (no more data-configuration), which only breaks someone parsing the docs HTML itself.

If you like it better i can draft a parallel path or something similar to keep it backward compatible (add more complexity in my opinion)

The non-object panic in my opinion prevent for shipping brokend documentation, but if you prefer i can skip this.

The multi document part is obviously something that I added just for myself, so if you prefer to avoid keeping this part to avoid maintaining it I can understand

I tested it locally in a production project and it worked (multiple huma api and goframe api rendered too in the same doc).

Anyway feel free to push back as match as you like I'm not offended in any way I like to give a contribution if is possible.

Again thank you for your time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update pinned @scalar/api-reference (1.44.20 → 1.65.1) and/or make the docs asset version configurable

2 participants