Add config store read path and split storage module#548
Add config store read path and split storage module#548
Conversation
Rename crates/common → crates/trusted-server-core and crates/fastly → crates/trusted-server-adapter-fastly following the EdgeZero naming convention. Add EdgeZero workspace dependencies pinned to rev 170b74b. Update all references across docs, CI workflows, scripts, agent files, and configuration.
Introduces trusted-server-core::platform with PlatformConfigStore, PlatformSecretStore, PlatformKvStore, PlatformBackend, PlatformHttpClient, and PlatformGeo traits alongside ClientInfo, PlatformError, and RuntimeServices. Wires the Fastly adapter implementations and threads RuntimeServices into route_request. Moves GeoInfo to platform/types as platform-neutral data and adds geo_from_fastly for field mapping.
…o-pr2-platform-traits
- Defer KV store opening: replace early error return with a local UnavailableKvStore fallback so routes that do not need synthetic ID access succeed when the KV store is missing or temporarily unavailable - Use ConfigStore::try_open + try_get and SecretStore::try_get throughout FastlyPlatformConfigStore and FastlyPlatformSecretStore to honour the Result contract instead of panicking on open/lookup failure - Encapsulate RuntimeServices service fields as pub(crate) with public getter methods (config_store, secret_store, backend, http_client, geo) and a pub new() constructor; adapter updated to use new() - Reference #487 in FastlyPlatformHttpClient stub (PR 6 implements it) - Remove unused KvPage re-export from platform/mod.rs - Use super::KvHandle shorthand in RuntimeServices::kv_handle()
- Split fastly_storage.rs into storage/{config_store,secret_store,api_client,mod}.rs
- Add PlatformConfigStore read path via FastlyPlatformConfigStore::get using ConfigStore::try_open/try_get
- Add PlatformError::NotImplemented variant; stub write methods on FastlyPlatformConfigStore and FastlyPlatformSecretStore
- Add StoreName/StoreId newtypes with From<String>, From<&str>, AsRef<str>
- Add UnavailableKvStore to core platform module
- Add RuntimeServicesBuilder replacing 7-arg constructor
- Migrate get_active_jwks and handle_trusted_server_discovery to use &RuntimeServices
- Update call sites in signing.rs, rotation.rs, main.rs
- Add success-path test for handle_trusted_server_discovery using StubJwksConfigStore
- Fix test_parse_cookies_to_jar_empty typo (was emtpy)
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Review Summary
Well-structured PR — the storage module split is clean, the PlatformConfigStore read path is correctly implemented, and the migration to &RuntimeServices preserves error context properly. No blockers. CI is fully green.
Highlights:
- Strong newtype pattern for
StoreName/StoreIdprevents mix-up bugs RuntimeServicesBuilderwith exhaustiveexpect("should ...")messages- Graceful KV store degradation with
UnavailableKvStorefallback - Excellent
test_handle_trusted_server_discovery_returns_jwks_documenttest
Findings: 0 blockers, 2 high, 4 medium, 3 low
Findings placed in body (line not in diff)
🤔 [P2] Value not URL-encoded (pre-existing) — crates/trusted-server-core/src/storage/api_client.rs line 122
The payload format!("item_value={}", value) sends application/x-www-form-urlencoded content but doesn't actually URL-encode value. If value contains &, =, +, spaces, or JSON characters ({, }, "), the Fastly API may misinterpret it. This is pre-existing code (moved from fastly_storage.rs) but worth flagging since it's used in key rotation.
Consider: let payload = format!("item_value={}", urlencoding::encode(value));
|
|
||
| use crate::error::TrustedServerError; | ||
|
|
||
| trait ConfigStoreReader { |
There was a problem hiding this comment.
🔧 [P1] Duplicated ConfigStoreReader trait and helper
This ConfigStoreReader trait and the load_config_value helper have a near-identical counterpart in crates/trusted-server-adapter-fastly/src/platform.rs (get_config_value). They only differ in error type (TrustedServerError vs PlatformError).
Since the legacy types are documented as "will be removed once all call sites have migrated," this is acceptable as transitional duplication. Consider adding a TODO comment here referencing the adapter's version so the duplication is cleaned up when FastlyConfigStore is removed.
| } | ||
| } | ||
|
|
||
| struct NoopSecretStore; |
There was a problem hiding this comment.
🔧 [P1] Test double boilerplate repeated across 3 files
The Noop* test doubles and build_services_with_config helper are copy-pasted ~120 lines each here, in endpoints.rs, and in platform/mod.rs. As more modules migrate to &RuntimeServices, this will spread further.
Suggestion: Extract a shared crate::platform::test_support module (behind #[cfg(test)]) providing noop_services(), build_services_with_config(), and the Noop stubs. This is consistent with the existing crate::test_support::tests pattern used for create_test_settings().
| /// cannot be read. The underlying [`crate::platform::PlatformError`] is | ||
| /// preserved as context in the error chain. | ||
| pub fn get_active_jwks(services: &RuntimeServices) -> Result<String, Report<TrustedServerError>> { | ||
| let store_name = StoreName::from(JWKS_CONFIG_STORE_NAME); |
There was a problem hiding this comment.
🤔 [P2] Static string allocates on every call
StoreName::from(JWKS_CONFIG_STORE_NAME) allocates a new String each time get_active_jwks is called. Since JWKS_CONFIG_STORE_NAME is &'static str, consider adding StoreName::from_static(&'static str) or accepting AsRef<str> in PlatformConfigStore::get to avoid this. Minor perf impact per-request but noted since project conventions say "minimize allocations."
| /// Returns an error if the secret store cannot be opened, the key is not | ||
| /// found, or the plaintext cannot be retrieved. | ||
| pub fn get(&self, key: &str) -> Result<Vec<u8>, Report<TrustedServerError>> { | ||
| let store = SecretStore::open(&self.store_name).map_err(|_| { |
There was a problem hiding this comment.
🤔 [P2] Original error discarded
.map_err(|_| discards the underlying error from SecretStore::open. The new platform implementation in platform.rs correctly preserves it via attach(format!(...)). Consider changing to .map_err(|error| and including error in the message for debuggability.
| let jwks_json = crate::request_signing::jwks::get_active_jwks(services).change_context( | ||
| TrustedServerError::Configuration { | ||
| message: "Failed to retrieve JWKS".into(), | ||
| message: "failed to retrieve JWKS".into(), |
There was a problem hiding this comment.
🤔 [P2] Inconsistent error message casing
Pre-existing error messages in this file use "Failed to ..." (capital F) while the new code consistently uses "failed to ..." (lowercase). The Rust convention is lowercase error messages. Consider normalizing as these files are touched.
For example, line 39 still has "Failed to parse JWKS JSON" and line 46 has "Failed to serialize discovery document" — both nearby and easily normalized in this PR.
| ); | ||
| } | ||
|
|
||
| #[test] |
There was a problem hiding this comment.
⛏ [P3] Legacy tests use println! and assert nothing
These tests pass regardless of outcome and use println! instead of log::debug!. Since this module is marked legacy, consider either converting to meaningful assertions or marking with #[ignore] noting they require a live Fastly environment.
Summary
fastly_storage.rsintostorage/{config_store,secret_store,api_client,mod}.rsfor better separation of concernsPlatformConfigStoreread path in the Fastly adapter (FastlyPlatformConfigStore::getviaConfigStore::try_open/try_get)get_active_jwksandhandle_trusted_server_discoveryto use&RuntimeServicesinstead of the legacyFastlyConfigStoredirectlyChanges
crates/trusted-server-core/src/storage/mod.rsStoreName,StoreId,UnavailableKvStorecrates/trusted-server-core/src/storage/config_store.rsPlatformConfigStorestub with read support andNotImplementedwrite stubscrates/trusted-server-core/src/storage/secret_store.rsPlatformSecretStorewithNotImplementedwrite stubscrates/trusted-server-core/src/storage/api_client.rsfastly_storage.rs; retains API client helperscrates/trusted-server-core/src/fastly_storage.rsstorage/modulecrates/trusted-server-core/src/lib.rsstoragemodule; removefastly_storageexportcrates/trusted-server-core/src/platform/error.rsPlatformError::NotImplementedvariantcrates/trusted-server-core/src/platform/traits.rsNotImplementedon write methods in trait doc commentscrates/trusted-server-core/src/platform/types.rsStoreName/StoreIdnewtypes; addUnavailableKvStore; addRuntimeServicesBuildercrates/trusted-server-adapter-fastly/src/platform.rsFastlyPlatformConfigStore::get; stub write methods on config/secret store implscrates/trusted-server-adapter-fastly/src/main.rsRuntimeServicesBuilder; update import paths after storage module renamecrates/trusted-server-core/src/request_signing/jwks.rsget_active_jwksto accept&RuntimeServicescrates/trusted-server-core/src/request_signing/endpoints.rshandle_trusted_server_discoveryto accept&RuntimeServices; add success-path test usingStubJwksConfigStorecrates/trusted-server-core/src/request_signing/rotation.rscrates/trusted-server-core/src/request_signing/signing.rsCloses
Closes #484
Test plan
cargo test --workspacecargo clippy --workspace --all-targets --all-features -- -D warningscargo fmt --all -- --checkcd crates/js/lib && npx vitest runcd crates/js/lib && npm run formatcd docs && npm run formatcargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1Checklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!)