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

    Fields

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions