Skip to content

Add PlatformHttpClient and PlatformBackend traits (EdgeZero PR6)#581

Open
prk-Jr wants to merge 49 commits intomainfrom
feature/edgezero-pr6-backend-http-client
Open

Add PlatformHttpClient and PlatformBackend traits (EdgeZero PR6)#581
prk-Jr wants to merge 49 commits intomainfrom
feature/edgezero-pr6-backend-http-client

Conversation

@prk-Jr
Copy link
Copy Markdown
Collaborator

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

Summary

  • Introduce PlatformHttpClient trait with send(), send_async(), and select() for auction fan-out — a superset of EdgeZero's ProxyClient that covers the async/select paths not yet in EdgeZero upstream
  • Introduce PlatformBackend trait with predict_name() and ensure() to decouple backend registration from Fastly-specific APIs
  • Thread RuntimeServices through all proxy-layer handlers (IntegrationProxy::handle, endpoint handlers, proxy_request) so the HTTP client and backend are reachable without global state

Changes

File Change
platform/http.rs (new) PlatformHttpClient trait + PlatformHttpRequest, PlatformResponse, PlatformPendingRequest, PlatformSelectResult types
platform/backend.rs (new) PlatformBackend trait + PlatformBackendSpec
platform/types.rs RuntimeServices extended with http_client and backend fields
platform/test_support.rs StubHttpClient, StubBackend, build_services_with_http_client test helpers
integrations/registry.rs IntegrationProxy::handle + IntegrationRegistry::handle_proxy accept &RuntimeServices
All integration impls Pass _services through IntegrationProxy::handle
proxy.rs Migrate send path to services.http_client().send(); add ProxyRequestHeaders struct to stay under 7-arg limit; add proxy_request_calls_platform_http_client_send test
auction/orchestrator.rs Thread services through auction handler
platform.rs (Fastly adapter) FastlyPlatformHttpClient impl; document Body::Stream limitation with warning log
main.rs (Fastly adapter) Pass runtime_services to all route handlers

Closes

Closes #487

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

Notes for reviewer

This branch carries PRs 2–6 cumulatively (crate rename, platform traits, config store, secret store, HTTP client). The focused diff for this PR is the latest commit (571656c) plus the PR6-specific commits. The PlatformHttpClient::send_async and select methods intentionally return Err(Unsupported) in the Fastly adapter for now — the fan-out path in orchestrator.rs still uses fastly::http::request::select directly and will be migrated in a follow-up once EdgeZero adds upstream fan-out support (issues #147–148).

prk-Jr and others added 30 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.
- 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)
- 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)
Copy link
Copy Markdown
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

Well-structured PR introducing a platform abstraction layer that decouples trusted-server-core from Fastly-specific HTTP, backend, config/secret/KV store, and geo APIs. Design quality, test coverage, and convention compliance are strong. Two issues should be addressed before merge.

Blocking

🔧 wrench

  • backend_name lost on remaining pending requests in select(): When select() returns, remaining requests lose their backend_name metadata. Violates the trait contract and will break non-Fastly adapters. (crates/trusted-server-adapter-fastly/src/platform.rs:350)
  • Duplicated platform_response_to_fastly: Identical copies in proxy.rs:40 and orchestrator.rs:32 that silently drop Body::Stream. Extract to a shared pub(crate) helper now to prevent silent divergence. (crates/trusted-server-core/src/proxy.rs:40)

Non-blocking

🤔 thinking

  • Silent empty string on missing backend name: unwrap_or_default() in select() produces "" when backend name is missing, causing silent correlation failure in the orchestrator. Consider logging a warning. (crates/trusted-server-adapter-fastly/src/platform.rs:358)
  • services parameter vs ProxyRequestConfig: Consider folding services into the existing config struct to keep proxy_request stable as more platform services arrive. (crates/trusted-server-core/src/proxy.rs:429)

🌱 seedling

  • Body::Stream silently dropped: Three conversion sites log a warning but produce empty bodies. Consider debug_assert! in tests. (platform.rs:263, proxy.rs:44, orchestrator.rs:34)
  • StubHttpClient missing send_async/select: Auction orchestrator can't be unit-tested with current stub. Consider queue-based fan-out support.

👍 praise

  • StoreName/StoreId newtypes — excellent compile-time guard
  • RuntimeServicesBuilder with expect("should ...") — follows conventions perfectly
  • Object safety assertions — catches trait changes early
  • Control character validation in BackendConfig — good defensive coding
  • Graceful UnavailableKvStore degradation
  • Strong test coverage across all new code paths

@prk-Jr prk-Jr changed the base branch from main to feature/edgezero-pr4-secret-store March 30, 2026 10:36
@prk-Jr prk-Jr requested a review from aram356 March 30, 2026 11:15
@prk-Jr prk-Jr changed the base branch from feature/edgezero-pr4-secret-store to main April 1, 2026 07:22
Copy link
Copy Markdown
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.

PR #581 Review: Add PlatformHttpClient and PlatformBackend traits

Overall: Well-structured PR with strong abstractions and thorough test coverage. The previous review round's findings were all addressed. Two new findings warrant attention before merge (header handling and the select() ordering assumption); the rest are improvements for follow-ups.

Praise

  • PlatformPendingRequest downcast API returning Err(self) to preserve backend metadata is a thoughtful design
  • !Send rationale documentation on PlatformPendingRequest clearly explains the wasm32 constraint
  • StubHttpClient fan-out test infrastructure with StubPendingResponse is clean and comprehensive
  • RuntimeServicesBuilder with expect("should ...") messages follows project conventions perfectly

Copy link
Copy Markdown
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.

Good PR — the platform abstraction layer is well-designed with thoughtful type erasure, thorough test coverage, and clean RuntimeServices threading. A few items to address before merge:

P0 — Stale doc comment: orchestrator.rs run_providers_parallel doc (line 244) still references fastly::http::request::select() but the implementation now uses services.http_client().select(). (This line is not in the diff, so noting here instead of inline.)

P1 — Silent body truncation in release builds: The debug_assert! + log::warn! pattern for Body::Stream in edge_request_to_fastly, platform_response_to_fastly, and fastly_response_to_platform is a no-op in release. If a streaming body reaches these paths, requests/responses are sent with empty bodies. Recommend upgrading to log::error! or returning Result.

P1 — Downcast failure drops all pending requests: In FastlyPlatformHttpClient::select(), if any single PlatformPendingRequest fails downcast, the entire function errors and all remaining in-flight requests are lost. Consider adding the backend name to the error message for diagnostics.

P2 — Pre-existing: rebuild_response_with_body still uses set_header (drops multi-value headers like Set-Cookie), while the new conversion functions correctly use append_header.

Copy link
Copy Markdown
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

Implements PlatformHttpClient with send/send_async/select and threads RuntimeServices through all proxy-layer handlers. Good test coverage for the new platform abstractions, but the allowed_domains removal from ProxyRequestConfig introduces a correctness regression for integration proxies.

Blocking

🔧 wrench

  • Integration proxies blocked by first-party allowed_domains: Removing allowed_domains from ProxyRequestConfig means GTM, testlight, and other integration proxies now have settings.proxy.allowed_domains enforced on their upstream requests. Previously they operated in open mode (&[]). In production this will block integrations from contacting their upstream domains (crates/trusted-server-core/src/proxy.rs:582, :702)

Non-blocking

♻️ refactor

  • Header forwarding test removed without replacement: The header_copy_copies_curated_set test was deleted but proxy_request_calls_platform_http_client_send uses copy_request_headers: false, leaving the header forwarding path untested (crates/trusted-server-core/src/proxy.rs:1977)

🤔 thinking

  • select() used for single-request mediator wait: Wrapping a single pending request in select(vec![...]) adds unnecessary complexity vs a dedicated wait() method (crates/trusted-server-core/src/auction/orchestrator.rs:161)

⛏ nitpick

  • PROXY_FORWARD_HEADERS const vs inline Accept-Encoding: The 5 forwarded headers are in a const array but Accept-Encoding override is added separately inline — minor inconsistency vs the old unified copy_proxy_forward_headers function

CI Status

  • fmt: PASS
  • clippy: PASS
  • cargo test: PASS
  • vitest: PASS
  • integration tests: PASS
  • CodeQL: PASS

@prk-Jr prk-Jr requested a review from aram356 April 10, 2026 08:45
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.

Backend + HTTP client traits

3 participants