Add platform abstraction layer with traits and RuntimeServices#545
Add platform abstraction layer with traits and RuntimeServices#545
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()
aram356
left a comment
There was a problem hiding this comment.
Summary
Solid architectural direction — this PR lays the groundwork for decoupling trusted-server-core from the Fastly SDK. The trait design, object-safety assertions, and graceful KV degradation are well done. A few issues to address before merge, mostly around constructor ergonomics and a question about Send bounds.
Blocking
🔧 wrench
RuntimeServices::newtakes 7 args with lint suppression: At the boundary today, will exceed it whencreative_store/counter_storeare added per the TODO. Use a builder pattern instead. (crates/trusted-server-core/src/platform/types.rs:103)- PR description overstates decoupling:
trusted-server-corestill depends onfastlydirectly (geo.rs,backend.rs,fastly_storage.rs,settings.rs). The description says this "enables future non-Fastly adapter support" — should clarify this is step N of M and thefastlydep removal comes in a follow-up.
❓ question
PlatformPendingRequestis!Send— intentional?:Box<dyn Any>is!Send, whilePlatformHttpClient: Send + Sync. Will this asymmetry hold for future adapters usingtokio::spawn? (crates/trusted-server-core/src/platform/http.rs:64)
Non-blocking
🤔 thinking
PlatformBackendSpecduplicatesBackendConfigfields: Both types havescheme,host,port,certificate_check,first_byte_timeout— one owns, the other borrows. Everyensurecall allocates owned Strings immediately borrowed. Consider sharing the type.- Asymmetric key semantics on
PlatformConfigStore:gettakesstore_name,put/deletetakestore_id. Newtype wrappers would make misuse a compile error. (crates/trusted-server-core/src/platform/traits.rs:19)
♻️ refactor
geo_from_fastlyshould live in adapter, not core: Importsfastly::geo::Geoin core, tying it to Fastly. Beingpubmeans new code could accidentally depend on it. (crates/trusted-server-core/src/geo.rs:10)UnavailableKvStoreshould live in core, not adapter: Platform-neutral fallback that any future adapter would duplicate. (crates/trusted-server-adapter-fastly/src/platform.rs:276)
⛏ nitpick
- Prefer
attach_withoverattach(format!(...)): Avoids allocation unless the report is inspected. Appears throughoutplatform.rs.
🌱 seedling
- No
Debugimpl forRuntimeServices: A manualDebugimpl printingclient_infowhile omitting service handles would help debugging.
CI Status
- fmt: PASS
- clippy: PASS
- rust tests: PASS
- js tests: PASS
| /// | ||
| /// The core stores this as an opaque support type. Adapter implementations can | ||
| /// recover their concrete runtime handle through [`Self::downcast`]. | ||
| pub struct PlatformPendingRequest { |
There was a problem hiding this comment.
❓ question — PlatformPendingRequest uses Box<dyn Any> which is !Send
PlatformPendingRequest stores Box<dyn Any> (!Send), while RuntimeServices stores Arc<dyn PlatformHttpClient> requiring Send + Sync. The trait contract says PlatformHttpClient: Send + Sync but async methods' futures are !Send (via #[async_trait(?Send)]).
Is this intentional asymmetry? Will it hold for future adapters (e.g., an Axum adapter using tokio::spawn)?
There was a problem hiding this comment.
Intentional — documented in the struct's doc comment. Box<dyn Any> (not Box<dyn Any + Send>) matches the #[async_trait(?Send)] contract: edgezero_core::body::Body wraps a LocalBoxStream that is !Send by design for wasm32 compatibility. A future multi-threaded adapter (e.g. Axum) would use Arc rather than relying on Send here.
|
Addressing the review findings: ♻️ 🤔 ⛏ All other blocking and non-blocking items have been addressed in the latest commits. |
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
The platform abstraction layer is well-structured and the architecture is sound. All prior review feedback has been addressed. Remaining findings are non-blocking suggestions for follow-up work. Approving.
Re-submitting as comment review
ChristianPavilonis
left a comment
There was a problem hiding this comment.
The platform abstraction layer is well-structured and the architecture is sound. All prior review feedback has been addressed. Remaining findings are non-blocking suggestions for follow-up work.
aram356
left a comment
There was a problem hiding this comment.
Summary
Solid step 2 of the EdgeZero migration — well-structured platform traits with proper object safety, a clean builder pattern for RuntimeServices, and good graceful degradation. One bug in the Fastly secret store implementation needs fixing before merge.
Blocking
🔧 wrench
SecretStore::openpanics instead of returning error:FastlyPlatformSecretStore::get_bytesusesSecretStore::open(panics on failure) instead ofSecretStore::try_open(returnsResult). The config store counterpart correctly usestry_open. (crates/trusted-server-adapter-fastly/src/platform.rs:109)
❓ question
- CLAUDE.md removes
attach_with()from guidance: Was droppingattach_with()from the error handling conventions intentional? It's the lazy variant that avoids allocation when the attachment isn't rendered. (CLAUDE.md:159)
Non-blocking
🤔 thinking
StoreName/StoreIdinner fields arepub: The newtype pattern loses its encapsulation benefit when callers can bypassFrom/AsRefand directly access the innerString. Consider private inner +new()constructor so validation can be added later. (crates/trusted-server-core/src/platform/types.rs:65,92)PlatformBackendSpecowns allStringfields: Fine for now, but worth consideringCow<'a, str>if this becomes a hot path as usage expands. (crates/trusted-server-core/src/platform/types.rs:117-127)PlatformPendingRequestis!SendbutPlatformHttpClientrequiresSend + Sync: Works today on single-threaded WASM, but a future multi-threaded adapter would hit a soundness gap with!Senddata flowing through aSend + Synctrait. Just flagging for awareness. (crates/trusted-server-core/src/platform/http.rs:76)
♻️ refactor
- Duplicate
BackendConfigconstruction:predict_nameandensurebuild identicalBackendConfigfrom the spec — extract a small helper. (crates/trusted-server-adapter-fastly/src/platform.rs:168,177)
🌱 seedling
RuntimeServicesBuilderpanics on missing fields: Idiomatic for infallible startup, but atry_build()variant may be useful if future adapters construct this in fallible contexts.- No raw
KvStoreaccessor:kv_storeis only exposed viakv_handle(). Consider adding akv_store()accessor to mirror the other services if direct access is needed later.
⛏ nitpick
- Byte-by-byte collection in secret plaintext:
.into_iter().collect()on plaintext bytes —to_vec()would be more direct if the SDK type supports it. (crates/trusted-server-adapter-fastly/src/platform.rs:121) - Unused
bytesdep in adapter Cargo.toml:bytesis added but doesn't appear to be used directly in the adapter code. (crates/trusted-server-adapter-fastly/Cargo.toml)
CI Status
- fmt: PASS
- clippy: PASS
- cargo test: PASS
- vitest: PASS
- integration tests: PASS
- browser integration tests: PASS
- CodeQL: PASS
- Make StoreName and StoreId inner fields private; From/AsRef provide all needed construction and access - Add #[deprecated] to GeoInfo::from_request with #[allow(deprecated)] at the three legacy call sites to track migration progress - Enumerate the six platform traits in the platform module doc comment - Extract backend_config_from_spec helper to remove duplicate BackendConfig construction in predict_name and ensure - Replace .into_iter().collect() with .to_vec() on secret plaintext bytes - Remove unused bytes dependency from trusted-server-adapter-fastly - Add comment on SecretStore::open clarifying it already returns Result (unlike ConfigStore::open which panics)
Summary
trusted-server-core::platformmodule with six platform-neutral traits (PlatformConfigStore,PlatformSecretStore,PlatformKvStore,PlatformBackend,PlatformHttpClient,PlatformGeo) as the first step toward non-Fastly adapter support — full decoupling offastlyfrom core lands in Remove fastly from core crate #496ClientInfo(extract-once plain data struct),PlatformErrorenum, andRuntimeServicesaggregate that threads all platform services intoroute_requestGeoInfotoplatform/typesas platform-neutral data and adds ageo_from_fastlymapping function; wires full Fastly implementations in the adapter crateChanges
crates/trusted-server-core/src/platform/mod.rsRuntimeServices, re-exports, object-safety assertions, unit tests; module doc enumerates all six traitscrates/trusted-server-core/src/platform/traits.rsasync_trait+ WASM-conditional?Sendcrates/trusted-server-core/src/platform/types.rsClientInfo,GeoInfo(moved here fromgeo.rs);RuntimeServicesBuilderreplacesnew()constructor;StoreName/StoreIdnewtypes with private inner fieldscrates/trusted-server-core/src/platform/error.rsPlatformErrorenumcrates/trusted-server-core/src/platform/http.rsPlatformHttpClientrequest/response typescrates/trusted-server-core/src/platform/kv.rsUnavailableKvStoreplatform-neutral fallbackcrates/trusted-server-core/src/geo.rsplatform/types, addsgeo_from_fastly;GeoInfo::from_requestmarked#[deprecated]with#[allow(deprecated)]at legacy call sitescrates/trusted-server-core/src/backend.rsPlatformBackendtraitcrates/trusted-server-core/src/fastly_storage.rsPlatformKvStore/PlatformConfigStoreimpl wiringcrates/trusted-server-adapter-fastly/src/platform.rsbackend_config_from_spechelper eliminates duplicateBackendConfigconstruction;SecretStore::opencomment documents SDK Result-returning APIcrates/trusted-server-adapter-fastly/src/main.rsRuntimeServices, threads intoroute_request; KV store failure degrades gracefullycrates/trusted-server-adapter-fastly/Cargo.tomlasync-trait,futures; removes unusedbytesdependencycrates/trusted-server-core/Cargo.tomlasync-traitdependencyCloses
Closes #483
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-wasip1fastly compute serveChecklist
unwrap()in production code — useexpect("should ...")logmacros (notprintln!)