Skip to content

Implement State<T> extractor + nested/array #[secret] support (edgezero-core/macros) #304

Description

@aram356

Description

Implement two independent upstream primitives in edgezero-core + edgezero-macros required by the trusted-server → EdgeZero migration. Full design and a maintainer-review appendix (verified against origin/main @ 42843b1) live in the spec:

Spec: docs/superpowers/specs/2026-07-02-edgezero-state-and-nested-secrets-design.md

The two workstreams are independent and can land in either order or in parallel.

Workstream A — State<T> extractor

  • RouterBuilder::with_state<T> registers a Clone + Send + Sync + 'static value (typically Arc<AppState>) that is cloned into each request's extensions before dispatch.
  • New State<T> extractor in extractor.rs resolving T by type from request.extensions(); works inside #[action] with no macro change.
  • Router plumbing: state_inserters: Vec<Arc<dyn Fn(&mut crate::http::Extensions) + Send + Sync>> threaded builder → RouterService::newRouterInner, applied in dispatch before RequestContext::new.
  • Naming: ship State<T> + with_state (optionally Extension<T> alias) — open question A-1.

Workstream B — nested/array #[secret] support

  • Reshape SecretField from { name: &'static str } to a path-qualified { path: &'static [SecretPathSegment] } (Field(name) / ArrayEach).
  • Derive (edgezero-macros) recurses into #[app_config(nested)] fields, prefixing child paths; relax the scalar rule to accept Option<String>.
  • Runtime secret_walk becomes a path navigator; leaf errors report the dotted path via EdgeError::config_out_of_date.
  • CLI path-aware secret reflection in run_adapter_typed_checks / typed_secret_checks.

Open questions to resolve before/while implementing (from spec §6 + review §8)

  • B-1 Are there secrets inside arrays in trusted-server Settings, or only nested objects? Gate ArrayEach implementation on a confirmed need.
  • B-2 Recurse via explicit #[app_config(nested)] opt-in (recommended) vs type heuristic (rejected).
  • B-3 AppConfigMeta::SECRET_FIELDS const → fn secret_fields() -> Cow<'static, [SecretField]>. Review found this is effectively forced (cross-crate const concat of prefixed &'static slices is not expressible); touches every impl incl. ~10 test impls.
  • A-1 / A-2 extractor name; optional ctx.state::<T>() accessor.

Must-not-miss (from review §8)

  • Add validate_excluding_secrets (app_config.rs:204) to the consumer list — for nested secrets it needs nested-ValidationErrors navigation (reuse the first_violating_field walk pattern), not a .name.path rename, or the push/runtime split breaks for nested fields.
  • Use the crate::http facade (not bare http::Extensions) in the router plumbing.
  • The lib.rs "re-export State" step is unnecessary — edgezero_core::extractor::State is reachable once pub.
  • Nested KeyInNamedStore "innermost-parent" store_ref scoping is new behavior with no existing fixture (app-demo has only KeyInDefault + StoreRef).

Acceptance criteria

  • All 5 CI gates green (fmt, clippy, workspace tests, feature check, spin wasm32).
  • Unit + trybuild UI + integration tests per spec §3.4 / §4.7.
  • app-demo still builds/serves on all four adapters; its top-level #[secret] api_token still resolves.
  • edgezero-cli config validate/push/diff work over a config with a nested secret.
  • Rustdoc + guide updates (§3.5, §4.8).

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