Skip to content

State<T> extractor + nested/array #[secret] support (stacked on #300)#306

Draft
aram356 wants to merge 24 commits into
worktree-feature+introspection-routesfrom
worktree-state-nested-secrets-spec-review
Draft

State<T> extractor + nested/array #[secret] support (stacked on #300)#306
aram356 wants to merge 24 commits into
worktree-feature+introspection-routesfrom
worktree-state-nested-secrets-spec-review

Conversation

@aram356

@aram356 aram356 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Two independent upstream primitives, both stacked on #300 (introspection routes; base worktree-feature+introspection-routes, auto-retargets to main when #300 merges):

  • Workstream A — State<T> extractor + RouterBuilder::with_state: hand app-owned shared state (Arc<AppState>) to #[action] handlers.
  • Workstream B — nested / array #[secret]: #[secret] fields resolved by field path below the config root — inside sub-structs and Vec<_> elements — via #[app_config(nested)].

Both are breaking, self-contained changes to edgezero-core / edgezero-macros (+ CLI/adapters for B). Every in-tree consumer is updated in this PR.

Workstream A — State<T>

  • State<T> extractor (FromRequest by type from request extensions; Deref/DerefMut/into_inner; 500 when unregistered).
  • RouterBuilder::with_state<T> stores app state in a single Extensions bag; dispatch does request.extensions_mut().extend(state.clone()) after the introspection injects (last-write-wins by TypeId). No macro change — #[action] composes State<T> via the generic FromRequest path.
  • Docs: docs/guide/handlers.md "Sharing app state".

Workstream B — nested / array #[secret]

  • Metadata: SecretField { kind, path: Vec<SecretPathSegment>, optional } (owned), AppConfigMeta::secret_fields() -> Vec<SecretField> (was const SECRET_FIELDS), dotted_path().
  • Derive: #[app_config(nested)] opt-in recurses into sub-struct / Vec<_> fields, prepending Field/ArrayEach onto each child's secret_fields(); accepts Option<String> (→ optional); rejects Option<String> on #[secret(store_ref)]; AppConfigRoot bound assertion on nested children; #[serde(rename/rename_all)] guards extended along the path.
  • Runtime: secret_walk is a path navigator (nested objects, arrays per-element, optional absent/null skip, KeyInNamedStore sibling resolved in the innermost parent, dotted [n] error paths).
  • Push: validate_excluding_secrets prunes secret-leaf validators through nested Struct/List errors (not a flat remove).
  • CLI: path-aware TOML collector powers config validate/push/diff; TypedSecretEntry.field_name carries the dotted label.
  • CI: the "Nested AppConfig audit" guard is inverted — nesting allowed iff the field opts in via #[app_config(nested)].
  • Docs: docs/guide/configuration.md "Nested and array secrets".

Tests / CI gates (all green locally)

cargo fmt, cargo clippy --workspace --all-targets --all-features -D warnings, cargo test --workspace --all-targets, feature check (fastly cloudflare spin), spin wasm32-wasip2, and the nested-AppConfig audit binary — all pass. app-demo (top-level #[secret]) still resolves. Notable new coverage: State<T> unit/router/#[action] tests; secret_walk nested/array/optional/null/named-store; nested/array validate_excluding_secrets pruning; derive happy-path + 6 trybuild UI fixtures; inverted-guard tests; CLI collector + nested/named-store validate tests; a full end-to-end nested + named-store resolution test through the real AppConfig<C> extractor.

Notes

  • Plans + spec live under docs/superpowers/; the plans supersede stale shapes in the spec body.
  • No workspace-wide lint allows added; the only new #[expect] are 3 documented dead_code suppressions on #[derive(AppConfig)] test fixtures (matching the file's existing pattern).

aram356 added 7 commits July 2, 2026 15:32
Design spec for two upstream edgezero-core/edgezero-macros primitives:
- State<T> extractor + RouterBuilder::with_state for app-owned shared state
- nested/array #[secret] support via path-qualified SecretField metadata

Filed under docs/superpowers/specs/. Includes a maintainer-review appendix
(§8) verifying every current-mechanics claim against origin/main @ 42843b1
and folding in the corrections found: http-facade use in the router plumbing,
the inaccurate lib.rs re-export step, the omitted validate_excluding_secrets
consumer (needs nested-ValidationErrors navigation, not a rename), the
per-struct guard-enforcement rewording, and B-3 being forced to the
secret_fields() fn lowering.
Adds the blockers a follow-up review found (verified against origin/main
@ 42843b1) and a Go/No-Go split:
- nested-AppConfig CI guard (check_no_nested_app_config.rs) must be inverted
- optional-secret metadata (optional: bool) is missing from SecretField
- path model must commit to owned segments (Vec/Cow), not &'static
- register the app_config helper attribute in the derive
- TypedSecretEntry.field_name must be owned for dotted/array paths
- enforce container rename_all on nested-only parents
- settle array scope before implementing
Workstream A is plan-ready now; B waits on the above.
@aram356 aram356 self-assigned this Jul 3, 2026
@aram356 aram356 marked this pull request as draft July 3, 2026 06:08
aram356 added a commit to IABTechLab/trusted-server that referenced this pull request Jul 3, 2026
Switch the six edgezero git deps from branch main to
worktree-state-nested-secrets-spec-review (stackpop/edgezero#306, stacked on
#300) to pick up the Phase 0 State<T> extractor work. cargo check-axum passes.
aram356 added 8 commits July 2, 2026 23:30
…erters

Replaces state_inserters: Vec<Arc<dyn Fn(&mut Extensions)>> with a single
state_extensions: Extensions on RouterBuilder/RouterInner. with_state inserts
by type; dispatch does extensions.extend(state_extensions.clone()). Drops the
StateInserter alias, one closure alloc per registered state, and one vtable
call per state per request. Identical behavior: Clone+Send+Sync+'static bound,
last-write-wins by TypeId.
@aram356 aram356 changed the title State<T> extractor + with_state (stacked on #300) State<T> extractor + nested/array #[secret] support (stacked on #300) Jul 3, 2026
aram356 added 7 commits July 3, 2026 09:58
…T_FIELDS naming

An empty #[app_config()] (or bare #[app_config]) previously returned Ok(false)
from nested_optin, silently NOT recursing the field and dropping the child's
#[secret] metadata. Now errors, matching the documented contract; adds an
app_config_empty trybuild fixture. Also renames stale SECRET_FIELDS references
in comments + the rename_all diagnostic to secret_fields(), and broadens that
diagnostic to mention #[app_config(nested)] children.
… path-aware secret check

Push: build_config_envelope_preserves_nested_and_array_secret_names asserts
nested + array secret key names survive verbatim into envelope.data.
Diff: diff_typed_rejects_empty_nested_secret proves the path-aware
typed_secret_checks catches an empty nested secret (naming integrations.
server_side_key) before the remote-read step.
…intainer-review revised

Adds the P0-C (Fastly run_app dispatch fidelity: Set-Cookie multi-value,
owns_logging opt-out, raw-request pre-dispatch hook) + P0-D (app! state=
injection) design, revised across three review rounds against 47a112c:
P0-D reduced to a macro-only change reusing the router Extensions bag;
C3 Extensions path + before-conversion scratch-bag ordering; C1 proxy
response fidelity; C2 cross-adapter owns_logging + missing_trait_methods
emission; full app! argument grammar.
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