Skip to content

Add config store support across all adapters#209

Open
prk-Jr wants to merge 12 commits intomainfrom
feat/config-store
Open

Add config store support across all adapters#209
prk-Jr wants to merge 12 commits intomainfrom
feat/config-store

Conversation

@prk-Jr
Copy link
Copy Markdown
Contributor

@prk-Jr prk-Jr commented Mar 9, 2026

Summary

  • Introduces a portable ConfigStore abstraction in edgezero-core that lets handlers read key/value configuration at runtime without coupling to a specific platform
  • Implements platform-native backing stores for Fastly (edge dictionary), Cloudflare (KV / env bindings), and Axum (in-memory map with env-var fallback), all sharing a common contract verified by shared test macros
  • Wires config store injection through the #[app] macro and each adapter's request pipeline so handlers receive a ConfigStoreHandle via RequestContext with no boilerplate

Changes

Crate / File Change
edgezero-core/src/config_store.rs New ConfigStore trait, ConfigStoreHandle wrapper, and shared contract test macro
edgezero-core/src/manifest.rs New manifest module with ConfigStore TOML binding and adapter name resolution
edgezero-core/src/context.rs Added config_store() accessor and injection helpers to RequestContext
edgezero-core/src/app.rs App::build hooks extended to accept config store configuration
edgezero-core/src/lib.rs Re-exported manifest module
edgezero-adapter-axum/src/config_store.rs New: in-memory + env-var AxumConfigStore with defaults support
edgezero-adapter-axum/src/service.rs Inject ConfigStoreHandle into each request before routing
edgezero-adapter-axum/src/dev_server.rs Accept optional config store handle in DevServerConfig
edgezero-adapter-axum/src/lib.rs Re-exported config store types
edgezero-adapter-fastly/src/config_store.rs New: FastlyConfigStore backed by Fastly edge dictionary
edgezero-adapter-fastly/src/lib.rs Re-exported and wired config store into dispatch path
edgezero-adapter-fastly/src/request.rs Inject config store handle during request conversion
edgezero-adapter-fastly/tests/contract.rs Updated contract tests
edgezero-adapter-cloudflare/src/config_store.rs New: CloudflareConfigStore backed by worker::Env bindings
edgezero-adapter-cloudflare/src/lib.rs Re-exported and wired config store into dispatch path
edgezero-adapter-cloudflare/src/request.rs Inject config store handle during request conversion
edgezero-adapter-cloudflare/tests/contract.rs Updated contract tests
edgezero-macros/src/app.rs #[app] macro generates config store setup from manifest
examples/app-demo/ Added config store usage in demo handlers and adapter wiring
docs/guide/ Added config store adapter docs and configuration guide
scripts/smoke_test_config.sh New smoke test script for config store end-to-end verification
.gitignore Excluded generated WASM artifacts

Closes

Closes #51

Test plan

  • cargo test --workspace --all-targets
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo check --workspace --all-targets --features "fastly cloudflare"
  • WASM builds: wasm32-wasip1 (Fastly) / wasm32-unknown-unknown (Cloudflare)
  • Manual testing via edgezero-cli dev
  • Other: shared contract test macro verified against all three adapter implementations

Checklist

  • Changes follow CLAUDE.md conventions
  • No Tokio deps added to core or adapter crates
  • Route params use {id} syntax (not :id)
  • Types imported from edgezero_core (not http crate)
  • New code has tests
  • No secrets or credentials committed

@prk-Jr prk-Jr self-assigned this Mar 9, 2026
Copy link
Copy Markdown
Contributor

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the cross-adapter config store work — the overall direction looks good. I’m requesting changes for one high-severity issue plus follow-ups.

Findings

  1. High — Axum config store can expose full process environment (secret leakage risk)

    • crates/edgezero-adapter-axum/src/config_store.rs:12
    • crates/edgezero-adapter-axum/src/config_store.rs:37
    • examples/app-demo/crates/app-demo-core/src/handlers.rs:119
    • AxumConfigStore::from_env snapshots all env vars, so any handler pattern that accepts user-controlled keys can accidentally expose unrelated secrets.
    • Requested fix: replace blanket std::env::vars() capture with an explicit allowlist (manifest-declared keys only), and avoid arbitrary key-lookup patterns in examples intended for production-like usage.
  2. Medium — Adapter override key casing is inconsistent across resolution paths

    • crates/edgezero-core/src/manifest.rs:352
    • crates/edgezero-macros/src/app.rs:120
    • crates/edgezero-core/src/app.rs:59
    • Mixed-case adapter keys can work in one path and fail in another.
    • Requested fix: normalize keys at parse/finalize time (or enforce lowercase with validation) and add a mixed-case adapter-key test.
  3. Low — Missing positive-path injection coverage in adapter tests

    • crates/edgezero-adapter-fastly/tests/contract.rs:17
    • crates/edgezero-adapter-cloudflare/tests/contract.rs:188
    • Please add success-path assertions that config store injection/retrieval works when bindings are present.

Once the high-severity item is addressed, this should be in good shape.

Copy link
Copy Markdown
Contributor

@aram356 aram356 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Config Store Feature

Overall this is a well-structured feature that follows the existing adapter pattern cleanly. The core trait, contract test macro, per-adapter implementations, and manifest/macro integration are all thoughtfully designed. Test coverage is solid across all three adapters and the docs are thorough.

That said, I found issues across four areas that should be addressed before merge — one high-severity security concern, two medium design issues, and one CI coverage gap.

Summary

Severity Finding
High Axum config-store exposes entire process environment (secret leakage risk)
Medium Case handling for adapter overrides is inconsistent between manifest and metadata paths
Medium dispatch() bypasses config-store injection, diverging from run_app behavior
Medium-Low New WASM adapter code paths are weakly exercised in CI

See inline comments for details and suggested improvements.

Copy link
Copy Markdown
Contributor

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review complete. No new issues were found in the current changeset, and previously noted concerns appear addressed.

Copy link
Copy Markdown
Contributor

@aram356 aram356 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review

Summary

Solid config store abstraction with good test coverage across all three adapters. The contract test macro and error mapping are well-designed. Main concerns are around the public dispatch API silently dropping KV handles, and a dual config resolution path that needs clarification.

Findings

Blocking

  • 🔧 dispatch_with_config / dispatch_with_config_handle silently drop KV: Both Fastly and Cloudflare versions pass None for KV. Users migrating from dispatch or dispatch_with_kv to a config-aware path will lose KV access with no warning. (cloudflare request.rs:106-130, fastly request.rs:78-103)
  • Dual config name resolution in run_app: Both adapters check A::config_store() (compile-time) then fall back to runtime manifest parsing. Since both derive from the same edgezero.toml, when would these diverge? Needs documentation or removal of dead path. (cloudflare lib.rs:86-94, fastly lib.rs:97-107)

Non-blocking

  • 🤔 CloudflareConfigStore::new() silent fallback: A binding typo gives an empty store with only a log::warn. Consider renaming to new_or_empty() or removing in favor of try_new only.
  • ♻️ Duplicate bounded dedup caches: Fastly RecentStringSet and Cloudflare ConfigCache are structurally identical. Candidate for a shared core utility.
  • _ctx prefix on used variable: cloudflare/tests/contract.rs:55 — underscore implies unused but variable is read.
  • 🏕 wasm-bindgen-test in [dependencies]: Pre-existing but file was touched — test-only crate shouldn't be in production deps.
  • 🌱 Consider a ConfigStore extractor: Handlers call ctx.config_store() manually; a ConfigStore(store) extractor (like Kv) would complete the pattern.

📌 Out of Scope

  • wasm-bindgen-test dep placement should be fixed in a separate cleanup PR
  • Bounded cache dedup is a candidate for future core utility extraction

CI Status

  • fmt: PASS
  • clippy: PASS
  • tests: PASS

.stores
.config
.as_ref()
.map(|cfg| cfg.config_store_name(edgezero_core::app::CLOUDFLARE_ADAPTER))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dual config name resolution — when do paths diverge? This first checks A::config_store() (compile-time metadata from the #[app] macro), then falls back to parsing manifest.stores.config at runtime. Since both derive from the same edgezero.toml, these should always agree.

When would A::config_store() return None but the manifest has [stores.config]? Presumably when someone implements Hooks manually without the macro. If so, this should be documented. If not, the fallback is dead code.

Same pattern in edgezero-adapter-fastly/src/lib.rs:97-107.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback is not dead code: it fires when a caller implements Hooks manually without the #[app] macro, in which case A::config_store() returns None while [stores.config] may still be present in the manifest. Added a comment explaining this in both adapters.

dispatch_with_config and dispatch_with_config_handle in both the
Cloudflare and Fastly adapters were passing None for the KV handle,
silently dropping KV access for callers on those paths. Both now
resolve the default KV binding/store (non-required) alongside the
config store.

Additional cleanup from review:
- Document why run_app has two config-name resolution paths
  (macro-generated vs. manual Hooks impls)
- Rename CloudflareConfigStore::new() to new_or_empty() to make
  the silent fallback-to-empty behavior explicit
- Fix _ctx prefix on an actively-read variable in cloudflare
  contract tests
- Move wasm-bindgen-test to [dev-dependencies]
@prk-Jr prk-Jr requested a review from aram356 March 25, 2026 05:01
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.

Config Store Abstraction

3 participants