Skip to content

Implement State<T> + nested/array #[secret] + Fastly dispatch fidelity (P0-C) + app! app-state (P0-D) #304

Description

@aram356

Status: implemented in #306 (stacked on #300)

Four independent upstream primitives for the trusted-server -> EdgeZero migration. Delivered in #306; base is the #300 branch and auto-retargets to main when #300 merges.

Specs:

  • docs/superpowers/specs/2026-07-02-edgezero-state-and-nested-secrets-design.md (A + B)
  • docs/superpowers/specs/2026-07-03-edgezero-p0cd-fastly-dispatch-and-appstate-design.md (P0-C + P0-D)

Task-by-task plans under docs/superpowers/plans/.

Workstream A -- State<T> extractor

  • State<T> extractor (FromRequest by type from request extensions; Deref/into_inner; 500 when unregistered); composes in #[action] with no macro change.
  • RouterBuilder::with_state<T> stores state in a single state_extensions: Extensions bag; dispatch extends it into every request. Shipped State<T> + with_state only (no Extension alias, no ctx.state() accessor).

Workstream B -- nested / array #[secret]

  • Owned path-qualified SecretField { kind, path: Vec<SecretPathSegment>, optional }; AppConfigMeta::secret_fields() -> Vec<SecretField> (const -> fn).
  • Derive recurses into #[app_config(nested)] object + Vec<_> fields; accepts Option<String>; path/rename guards; AppConfigRoot assertion.
  • Runtime secret_walk path navigator; nested/list-aware validate_excluding_secrets pruning; CLI path-aware TOML collector for validate/push/diff; inverted nested-AppConfig CI guard.

P0-C -- Fastly run_app dispatch fidelity

  • C1 multi-value Set-Cookie (response + proxy response/request); C2 platform-neutral Hooks::owns_logging() gated across all four adapters + app!(owns_logging = <bool>); C3 run_app_with_request_extensions, a generic pre-dispatch hook for raw-request signals.
  • Note: C3 is only the Fastly-side mechanism. A standard cross-adapter JA4 API (Expose JA4 through a standard interface #283) is NOT delivered here and remains open.

P0-D -- app! app-state injection

  • app!(state = <expr>) makes the macro-generated router call .with_state(...), reusing A's dispatch injection. Macro-only; app-demo example + e2e test.

Open questions -- resolved during implementation

  • A-1 / A-2: ship State<T> + with_state only; no Extension alias, no ctx.state() accessor.
  • B-1 (arrays): implemented ArrayEach from day one (spec's own inventory has partners[*].api_key).
  • B-2 (recursion): explicit #[app_config(nested)] opt-in (type-heuristic rejected).
  • B-3 (const -> fn): AppConfigMeta::secret_fields() -> Vec<SecretField> -- cross-crate const concat was not expressible, so the fn form was taken; every impl (incl. test impls) flipped.
  • Router uses the crate::http::Extensions facade; no lib.rs re-export; validate_excluding_secrets got true nested/list-aware pruning; a purpose-built KeyInNamedStore fixture was added.

Acceptance criteria

  • All 5 CI gates green (fmt, clippy, workspace tests, feature check, spin wasm32) -- plus the nested-AppConfig audit, app-demo (own workspace) tests + clippy, and Fastly wasm32-wasip1 + Viceroy tests.
  • Unit + trybuild UI + integration tests per spec 3.4 / 4.7.
  • app-demo builds/serves on all four adapters; top-level #[secret] api_token still resolves.
  • edgezero-cli config validate/push/diff work over nested + array secrets.
  • Rustdoc + guide updates (docs/guide/handlers.md, docs/guide/configuration.md).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions