Skip to content

refactor(assets-sync): move Content-Type into _headers, drop assets.toml#66

Draft
lwshang wants to merge 1 commit into
lwshang/perffrom
lwshang/drop_assets_toml
Draft

refactor(assets-sync): move Content-Type into _headers, drop assets.toml#66
lwshang wants to merge 1 commit into
lwshang/perffrom
lwshang/drop_assets_toml

Conversation

@lwshang
Copy link
Copy Markdown
Collaborator

@lwshang lwshang commented May 29, 2026

Summary

Removes the assets.toml config introduced in #64 and folds the only feature it carried — per-glob Content-Type overrides — back into _headers. The plugin now recognises Content-Type: inside any _headers block, parses the value as a MIME, and routes it to CreateAssetArguments.content_type rather than the appended response headers — so the canister still emits exactly one certified Content-Type per response.

Net diff: −634 / +287 across 21 files. Drops a config format, a parser module, an e2e fixture, a top-level design doc, and the toml workspace dep.

Why Content-Type belongs in _headers, not its own file

#64 sat on the position that Content-Type was asset metadata, not a response header, and therefore deserved its own file. That framing was driven by the canister's wire shape (appends _headers without dedup, so a Content-Type: rule would produce two on the wire) — i.e. an implementation concern, not a user concern.

  • Users' mental model wins. Every static-host tool (Netlify, Cloudflare Pages) lets users set Content-Type in _headers. To a user, it's just a header — splitting it across two files to satisfy our pipeline is the kind of design that makes them say "why is this weird."
  • The wire problem is plumbing, not architecture. Solving it in the plugin (extract Content-Type from _headers, route to content_type, exclude from the appended list) keeps the canister's append-without-dedup invariant intact and gives the user the familiar one-file experience.
  • The other speculative assets.toml fields didn't survive review. ignore belongs in the build step (don't put the file in dist/ if it shouldn't deploy). encodings has no driving use case — the current text/* + js/html → identity+gzip, everything else → identity policy covers real projects. allow_raw_access is being dropped on the canister side (selective certification via the HTTP gateway spec covers what it was for). With no remaining fields, the config file has no reason to exist.

The pivot rests on a principle: adding a config format later when a real need shows up is cheaper than carrying an unused format now.

Other design points

  • Parser shape. HeaderRule gains content_type: Option<Mime>. Parsing a Content-Type: line is case-insensitive, validates as Mime, rejects empty values, and rejects duplicates within the same block. Other headers in the same block continue to flow through headers as usual (assets-sync/src/headers.rs).
  • Resolver. New content_type_for(key, rules) walks rules in declaration order and returns the first matching content_type — first-match-wins because Content-Type is single-valued (accumulation semantics make no sense for it). Other matching rules still contribute their non-Content-Type headers (assets-sync/src/headers.rs).
  • Pipeline. prepare_asset now takes &[HeaderRule] (instead of the deleted &AssetConfig) and applies content_type_for before encoders_for — so a .did declared as text/plain still picks up gzip and still triggers drift detection on re-deploy. The header_content_type_override_applies_to_prepare_asset unit test pins this end-to-end (assets-sync/src/sync.rs).
  • sync() signature. Lost its files: &[(String, String)] parameter — no consumer remains. The plugin entry no longer threads input.files through; if a future feature needs inline files, it just reads them.
  • Deletions. assets-sync/src/asset_config.rs, ASSETS-TOML.md, the toml workspace dep, the assets-toml e2e fixture, and the assets-toml-only fields from the developer-docs cargo-cult example (the e2e fixture now demonstrates .did only, the one extension mime_guess genuinely can't classify).

Test plan

New coverage in headers.rs tests: Content-Type routes to dedicated field not headers, case-insensitive, coexists with other headers, block-with-only-Content-Type is valid, rejects duplicate / invalid / empty values; content_type_for returns None / first-match-wins / skips rules without a content_type declaration.

🤖 Generated with Claude Code

`Content-Type` is now declared inside any `_headers` block: the parser
routes it to a dedicated `HeaderRule.content_type` field (Mime-validated)
and the sync pipeline feeds it into `CreateAssetArguments.content_type`
instead of the appended response headers — so the canister still emits
exactly one certified Content-Type per response.

Removes the `assets.toml` config file introduced in 4788ef6. From a
user's mental model Content-Type is just a header, and the only
remaining justification for a second config file (sketched v2 fields
`ignore` / `encodings` / `allow_raw_access`) is either covered by the
build pipeline, satisfied by current defaults, or no longer in scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant