Skip to content

Add platform abstraction layer with traits and RuntimeServices#545

Open
prk-Jr wants to merge 15 commits intomainfrom
feature/edgezero-pr2-platform-traits
Open

Add platform abstraction layer with traits and RuntimeServices#545
prk-Jr wants to merge 15 commits intomainfrom
feature/edgezero-pr2-platform-traits

Conversation

@prk-Jr
Copy link
Collaborator

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

Summary

  • Introduces trusted-server-core::platform module with six platform-neutral traits (PlatformConfigStore, PlatformSecretStore, PlatformKvStore, PlatformBackend, PlatformHttpClient, PlatformGeo) as the first step toward non-Fastly adapter support — full decoupling of fastly from core lands in Remove fastly from core crate #496
  • Defines ClientInfo (extract-once plain data struct), PlatformError enum, and RuntimeServices aggregate that threads all platform services into route_request
  • Moves GeoInfo to platform/types as platform-neutral data and adds a geo_from_fastly mapping function; wires full Fastly implementations in the adapter crate

Note: trusted-server-core still depends on the fastly crate directly (via geo.rs, backend.rs, fastly_storage.rs). These are legacy call-sites that will be migrated in follow-up PRs as part of the EdgeZero migration (#480). The new platform/ module is step 2 of that plan — the fastly dep removal from core is step 15 (#496).

Changes

File Change
crates/trusted-server-core/src/platform/mod.rs New module: RuntimeServices, re-exports, object-safety assertions, unit tests; module doc enumerates all six traits
crates/trusted-server-core/src/platform/traits.rs New: six platform traits with async_trait + WASM-conditional ?Send
crates/trusted-server-core/src/platform/types.rs New: ClientInfo, GeoInfo (moved here from geo.rs); RuntimeServicesBuilder replaces new() constructor; StoreName/StoreId newtypes with private inner fields
crates/trusted-server-core/src/platform/error.rs New: PlatformError enum
crates/trusted-server-core/src/platform/http.rs New: PlatformHttpClient request/response types
crates/trusted-server-core/src/platform/kv.rs New: UnavailableKvStore platform-neutral fallback
crates/trusted-server-core/src/geo.rs Refactored: delegates to platform/types, adds geo_from_fastly; GeoInfo::from_request marked #[deprecated] with #[allow(deprecated)] at legacy call sites
crates/trusted-server-core/src/backend.rs Updated to use PlatformBackend trait
crates/trusted-server-core/src/fastly_storage.rs Adds PlatformKvStore / PlatformConfigStore impl wiring
crates/trusted-server-adapter-fastly/src/platform.rs New: Fastly implementations of all platform traits; backend_config_from_spec helper eliminates duplicate BackendConfig construction; SecretStore::open comment documents SDK Result-returning API
crates/trusted-server-adapter-fastly/src/main.rs Constructs RuntimeServices, threads into route_request; KV store failure degrades gracefully
crates/trusted-server-adapter-fastly/Cargo.toml Adds async-trait, futures; removes unused bytes dependency
crates/trusted-server-core/Cargo.toml Adds async-trait dependency

Closes

Closes #483

Test plan

  • cargo test --workspace
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • JS tests: cd crates/js/lib && npx vitest run
  • JS format: cd crates/js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code — use expect("should ...")
  • Uses log macros (not println!)
  • New code has tests
  • No secrets or credentials committed

prk-Jr added 5 commits March 18, 2026 16:54
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.
@prk-Jr prk-Jr self-assigned this Mar 20, 2026
prk-Jr added 4 commits March 20, 2026 21:53
- 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()
Copy link
Collaborator

@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.

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::new takes 7 args with lint suppression: At the boundary today, will exceed it when creative_store/counter_store are added per the TODO. Use a builder pattern instead. (crates/trusted-server-core/src/platform/types.rs:103)
  • PR description overstates decoupling: trusted-server-core still depends on fastly directly (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 the fastly dep removal comes in a follow-up.

❓ question

  • PlatformPendingRequest is !Send — intentional?: Box<dyn Any> is !Send, while PlatformHttpClient: Send + Sync. Will this asymmetry hold for future adapters using tokio::spawn? (crates/trusted-server-core/src/platform/http.rs:64)

Non-blocking

🤔 thinking

  • PlatformBackendSpec duplicates BackendConfig fields: Both types have scheme, host, port, certificate_check, first_byte_timeout — one owns, the other borrows. Every ensure call allocates owned Strings immediately borrowed. Consider sharing the type.
  • Asymmetric key semantics on PlatformConfigStore: get takes store_name, put/delete take store_id. Newtype wrappers would make misuse a compile error. (crates/trusted-server-core/src/platform/traits.rs:19)

♻️ refactor

  • geo_from_fastly should live in adapter, not core: Imports fastly::geo::Geo in core, tying it to Fastly. Being pub means new code could accidentally depend on it. (crates/trusted-server-core/src/geo.rs:10)
  • UnavailableKvStore should 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_with over attach(format!(...)): Avoids allocation unless the report is inspected. Appears throughout platform.rs.

🌱 seedling

  • No Debug impl for RuntimeServices: A manual Debug impl printing client_info while 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

questionPlatformPendingRequest 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)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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.

@prk-Jr
Copy link
Collaborator Author

prk-Jr commented Mar 22, 2026

Addressing the review findings:

♻️ geo_from_fastly should live in adapter
Agreed. Moving it requires also reworking GeoInfo::from_request (which calls it) and removing the fastly dep from core entirely — that's the explicit scope of #496. Tracked there.

🤔 PlatformBackendSpec duplicates BackendConfig fields
Agreed. Unifying the two types is tracked in #487 (Backend + HTTP client traits). Added a note on that issue.

attach_with over attach(format!(...))
Report::attach_with does not exist in error-stack 0.6 — only ResultExt::attach_with does (for Result types). All format!() calls are already inside map_err closures so they are only evaluated on the error path. No allocation saving is possible here with the current API.

All other blocking and non-blocking items have been addressed in the latest commits.

@prk-Jr prk-Jr requested a review from aram356 March 22, 2026 16:46
Copy link
Collaborator

@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.

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.

@ChristianPavilonis ChristianPavilonis dismissed their stale review March 24, 2026 20:29

Re-submitting as comment review

Copy link
Collaborator

@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.

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.

Copy link
Collaborator

@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.

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::open panics instead of returning error: FastlyPlatformSecretStore::get_bytes uses SecretStore::open (panics on failure) instead of SecretStore::try_open (returns Result). The config store counterpart correctly uses try_open. (crates/trusted-server-adapter-fastly/src/platform.rs:109)

❓ question

  • CLAUDE.md removes attach_with() from guidance: Was dropping attach_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/StoreId inner fields are pub: The newtype pattern loses its encapsulation benefit when callers can bypass From/AsRef and directly access the inner String. Consider private inner + new() constructor so validation can be added later. (crates/trusted-server-core/src/platform/types.rs:65,92)
  • PlatformBackendSpec owns all String fields: Fine for now, but worth considering Cow<'a, str> if this becomes a hot path as usage expands. (crates/trusted-server-core/src/platform/types.rs:117-127)
  • PlatformPendingRequest is !Send but PlatformHttpClient requires Send + Sync: Works today on single-threaded WASM, but a future multi-threaded adapter would hit a soundness gap with !Send data flowing through a Send + Sync trait. Just flagging for awareness. (crates/trusted-server-core/src/platform/http.rs:76)

♻️ refactor

  • Duplicate BackendConfig construction: predict_name and ensure build identical BackendConfig from the spec — extract a small helper. (crates/trusted-server-adapter-fastly/src/platform.rs:168,177)

🌱 seedling

  • RuntimeServicesBuilder panics on missing fields: Idiomatic for infallible startup, but a try_build() variant may be useful if future adapters construct this in fallible contexts.
  • No raw KvStore accessor: kv_store is only exposed via kv_handle(). Consider adding a kv_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 bytes dep in adapter Cargo.toml: bytes is 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

prk-Jr and others added 2 commits March 25, 2026 12:34
- 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)
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.

Define platform traits, ClientInfo, PlatformError, RuntimeServices

3 participants