Migrate integration and provider HTTP types (PR 13)#626
Conversation
ChristianPavilonis
left a comment
There was a problem hiding this comment.
PR Review — Migrate integration and provider HTTP types (PR 13)
1 blocking finding, 5 non-blocking suggestions.
Blocking
🔧 Missing Content-Type forwarding for Didomi POST/PUT — see inline comment.
Non-blocking (inline)
- 🤔 Prebid still imports from
fastly::http— last remaininguse fastlyin the integration layer - ♻️ Duplicated body collection helpers (
collect_response_body/collect_body_bytes) - 🌱 Unbounded response body collection (matches pre-migration, but could use a size cap)
- ⛏ Dead
"X-"uppercase branch in custom header copy (http crate lowercases names) - ⛏ Duplicated
backend_name_for_urlhelper across 4 integrations
…/edgezero-pr13-integration-provider-type-migration
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Solid migration of the integration and auction provider layers from Fastly-specific HTTP types to platform-agnostic http crate types. No blocking issues found. CI is passing.
Non-blocking
♻️ refactor
- Duplicated
body_as_readerhelper:proxy.rs:24andpublisher.rs:23define identicalbody_as_readerfunctions. Consider extracting into a shared location (e.g., aninto_reader()method onEdgeBody, or a helper inhttp_util.rs).
📝 note
- PR description file table incomplete:
proxy.rsandpublisher.rshave changes in this diff (thebody_as_readerextraction and test type alias cleanups) but are not listed in the PR body's change table.
📌 out of scope
- Orchestrator tests disabled pending mock
PlatformHttpClient: Several provider integration tests and timeout enforcement paths inorchestrator.rs:751-765are disabled pending a mock forPlatformHttpClient::select(). This is acknowledged in the PR, but the scope of untested paths has grown — a follow-up to add thin mock support forselect()would significantly improve coverage of the deadline enforcement logic.
aram356
left a comment
There was a problem hiding this comment.
Summary
The refactor is clean and the ensure_integration_backend / collect_body helpers extracted in the fix commits are a genuine improvement. The main risks are latent panics and missing POST size bounds that were introduced in the base migration commit. None are new in the fix commits, but this PR is the natural owner of the HTTP-type boundary, so they should land before merge.
Blocking
🔧 wrench
EdgeBody::into_bytes()panics on streaming bodies atauction/endpoints.rs:42— the inbound auction POST body is user-controlled.Body::into_bytes()panics onBody::Stream(_)(unit testinto_bytes_panics_for_streamasserts this). Today Fastly's adapter happens to returnBody::Once, so nothing panics — but that invariant is undocumented and the/auctionhandler is the first publicly-reachable crash if it ever changes. Fix: switch toBody::into_bytes_bounded(max).awaitwith a documented cap (e.g. 256KB).EdgeBody::into_bytes()panics across every migrated provider/proxy — same root cause, see inline comments onprebid.rs:1157,permutive.rs:151,lockr.rs:149,datadome.rs:301,aps.rs:554,adserver_mock.rs:373. Makeparse_responseasync and read bodies withinto_bytes_bounded.- Unbounded inbound POST body forwarding on the datadome / didomi / permutive / lockr proxies — only GTM enforces a cap. Promote the GTM size-bounded reader into
integrations/mod.rsascollect_body_boundedand apply it uniformly. Inline comments ondatadome.rs:367,didomi.rs:245,permutive.rs:211,lockr.rs:203. - Lockr
.expect()on user-controlledOriginheader — reachable worker panic atlockr.rs:266. - GTM stream-read errors mis-classified as
PayloadTooLarge— returns 413 for transport errors atgoogle_tag_manager.rs:313. Add aStreamReadvariant and map to 502.
Non-blocking
🤔 thinking
collect_bodyhas no size cap (integrations/mod.rs:101) — silently drains whole bodies to RAM. Same concern applies to the GTM response-rewrite path atgoogle_tag_manager.rs:486.- Orchestrator
select_result.readyerror path drops provider identity inrun_providers_parallel— whenreadyisErr, the provider identity is lost so noAuctionResponse::error(...)is pushed and the failing backend vanishes silently from the result set. Track a(backend_name, provider_index)pair alongsidefastly_pendingso failures are attributable. - Deadline enforcement relies on every provider honoring
backend_name(effective_timeout)— Phase 1 computesremaining_ms.min(provider.timeout_ms())correctly, but nothing guarantees each provider actually threads that timeout through to the backend config. Not a new regression, but the async migration makes it more load-bearing. Add a unit test that asserts the select loop returns beforedeadline + epsilon.
♻️ refactor
aps.rs/adserver_mock.rsstill buildBackendConfiginline — extend the newensure_integration_backendhelper to cover providers too.
🌱 seedling
- Make
parse_responseasync in the provider trait (auction/provider.rs:48) — needed anyway once theinto_bytes_boundedfix lands; zero cost under#[async_trait(?Send)].
📝 note
- Testlight stale
Content-Lengthtest is narrow (testlight.rs:425) — good unit-level coverage, but the twohandle()rebuild call sites would benefit from an end-to-end assertion.
👍 praise
- Clean helper extraction in
integrations/mod.rs— real dedup across six integrations. - Dropping the dead
|| name_str.starts_with("X-")branch inlockr.rs/permutive.rs—HeaderName::as_str()is always lowercase, so it was unreachable. - Adding
CONTENT_TYPEto didomi's forwarded headers — POST bodies can now be interpreted by upstream.
CI Status
- browser integration tests: PASS
- integration tests: PASS
- prepare integration artifacts: PASS
Conflicts resolved by keeping PR13's http-native integration layer throughout — all compat shim insertions from PR12 are superseded by this branch's completed integration type migration.
- Add collect_body_bounded helper with INTEGRATION_MAX_BODY_BYTES (256 KiB) cap to prevent unbounded memory use on streaming bodies - Replace all into_bytes() calls (panic on Body::Stream) with collect_body or collect_body_bounded across integrations and auction endpoint - Make AuctionProvider::parse_response async so implementations can safely drain response bodies without panicking on the Stream variant - Add .await to both parse_response call sites in the orchestrator - Cap inbound request bodies in lockr, permutive, datadome, and didomi proxy handlers using collect_body_bounded before forwarding upstream - Fix lockr Origin header: replace expect() with a warn-and-skip fallback so an invalid user-supplied origin cannot crash the edge worker - Add PayloadSizeError::StreamRead variant to google_tag_manager and return 502 (not 413) when a stream transport error occurs while reading the body - Remove extra blank line before closing brace in google_tag_manager impl block
aram356
left a comment
There was a problem hiding this comment.
Summary
Re-review of the EdgeZero PR13 integration/provider HTTP type migration after the latest "Address review findings" commit. The async parse_response migration and the new collect_body / collect_body_bounded helpers landed cleanly and resolved the prior into_bytes()-on-Stream panic class across all providers. Two blockers remain plus one CI/process question that affects whether the existing test plan can be trusted.
Blocking
🔧 wrench
-
cargo fmt --all -- --checkfails locally in 7+ files. The PR description's[x] cargo fmtcheckbox is incorrect — see the CI question below for why this wasn't caught. Affected files:crates/trusted-server-core/src/auction/endpoints.rs:39-47crates/trusted-server-core/src/auction/orchestrator.rs:12-14, 402-405crates/trusted-server-core/src/integrations/google_tag_manager.rs:16-25, 39-45crates/trusted-server-core/src/integrations/lockr.rs:282-285crates/trusted-server-core/src/integrations/permutive.rs:208-219, 278-281crates/trusted-server-core/src/integrations/prebid.rs:5-13, 1154-1162crates/trusted-server-core/src/integrations/testlight.rs:11-16
Fix: run
cargo fmt --all. -
Testlight POST body is unbounded (inline at
integrations/testlight.rs:181).
❓ question
-
CI gates aren't enforced for this PR.
.github/workflows/format.ymland.github/workflows/test.ymlonly fire onpull_request: branches: [main]. PR 626's base isfeature/edgezero-pr12-handler-layer-types, so on commit94ec9477only "Integration Tests" ran — fmt, clippy, andcargo test --workspacedid not. Combined with the fmt failure above, every stacked EdgeZero PR ships with broken formatting and an unverified test plan. Was this intentional, or should the workflow triggers be widened (or the gates duplicated under the integration-tests umbrella)? Until that's resolved, the PR description's CI checkboxes can't be relied on as evidence.Verified locally on this commit:
cargo test --workspacepasses (821 tests),cargo clippy --workspace --all-targets --all-features -- -D warningspasses,cargo build --release --target wasm32-wasip1passes,cargo fmt --all -- --checkfails.
Non-blocking
🤔 thinking
-
Unbounded response-body reads via
collect_body(inline atintegrations/mod.rs:93for stream-read error fidelity; cross-cutting concern listed here). The bounded variant exists for inbound bodies, but every upstream response site still drains an unbounded body to aVec<u8>:integrations/prebid.rs:1157— Prebid Server response (highest risk: third-party hop, large payloads)integrations/aps.rs:555— APS responseintegrations/adserver_mock.rs:375— Mediator responseintegrations/lockr.rs:150— Lockr SDK fetchintegrations/permutive.rs:152— Permutive SDK fetchintegrations/datadome.rs:302— DataDome SDK fetchintegrations/google_tag_manager.rs:491— GTM script
Provider responses are the highest risk — a misbehaving Prebid Server / mediator can ship a multi-GB body and OOM the worker. Recommend routing all of these through
collect_body_boundedwith a per-callsite cap, or removing the unbounded variant in favor of a single bounded helper.
♻️ refactor
-
Auction providers still construct
BackendConfiginline while integration proxies route throughensure_integration_backend. Reraising the prior-review concern with current locations:integrations/aps.rs:517-528integrations/adserver_mock.rs:338-348integrations/prebid.rs:1131-1135
The two paths differ on timeout (providers need a configurable per-request timeout; the proxy helper hardcodes 15s). Consider widening
ensure_integration_backendto accept a timeout and reusing it from the providers, or at least documenting the divergence.
CI Status
- fmt: FAIL locally (CI did not run for this commit — see ❓ question above)
- clippy: PASS locally (CI did not run)
- rust tests: PASS locally — 821 tests (CI did not run)
- wasm32-wasip1 build: PASS locally
- Integration Tests workflow: PASS (only workflow that ran on
94ec9477)
… compat conflicts
…EADME - Run cargo fmt --all (7+ files had formatting failures) - Cap testlight POST body with collect_body_bounded + INTEGRATION_MAX_BODY_BYTES - Use dedicated AUCTION_MAX_BODY_BYTES (256 KiB) for /auction instead of INTEGRATION_MAX_BODY_BYTES - Update auction/README.md: parse_response is now async, parallel execution via select() is live
…/edgezero-pr13-integration-provider-type-migration
aram356
left a comment
There was a problem hiding this comment.
Summary
Re-review of PR13 after the latest "Address PR review" commit (4b27087e) and the PR12 merge (a2036eb5). The async parse_response migration, the dedicated AUCTION_MAX_BODY_BYTES, the README updates, and the testlight body cap all landed cleanly. One blocker remains plus several non-blocking re-raises from prior reviews that haven't been addressed.
Local verification on this commit:
cargo test --workspace— PASS (952 tests, 0 failures)cargo clippy --workspace --all-targets --all-features -- -D warnings— PASScargo build --release --target wasm32-wasip1 -p trusted-server-adapter-fastly— PASScargo fmt --all -- --check— FAIL (4 PR-introduced sites + 11 inherited from PR12 base)- GitHub CI (integration tests workflow) — PASS
GitHub's format.yml / test.yml still only fire on PRs targeting main, so the fmt/clippy/test gates don't run for this PR's stack. The PR description's [x] cargo fmt / [x] cargo clippy / [x] cargo test checkboxes therefore can't be relied on as evidence — see the fmt blocker below.
Blocking
🔧 wrench
-
fmt failures still present in PR-modified files.
cargo fmt --all -- --checkreports diffs at:crates/trusted-server-adapter-fastly/src/main.rs:209and:236crates/trusted-server-core/src/integrations/prebid.rs:1057(the line at ~1066 is too long inside theif letblock)crates/trusted-server-core/src/integrations/registry.rs:673
These four sites did not exist on the PR12 base — PR13 introduced them. (11 additional fmt diffs are inherited from the PR12 base and are not this PR's responsibility, but the project-wide
cargo fmt --all -- --checkdoes fail.) Fix:cargo fmt --all.
Non-blocking
🤔 thinking
- Provider/integration response bodies still unbounded (re-raise; inline at
integrations/mod.rs:84). 7 callsites still drain upstream responses withcollect_bodyinstead of a bounded helper. Provider responses are highest risk (prebid.rs:1160,aps.rs:555,adserver_mock.rs:375). - Stream-read errors classified as
Integration(re-raise; inline atintegrations/mod.rs:93and the bounded variant at:137). Conflates transport failure with integration semantic error; both surface as 5xx-server with the same shape. GTM has a bespoke 502 path that the shared helper doesn't. - Orchestrator backend-name fallback can silently lose bids (inline at
auction/orchestrator.rs:337). Whenpending.backend_name()isNoneand the platform sets a differentPlatformResponse::backend_name, the lookup at line 392 fails and the bid is dropped with a"Received response from unknown backend"warning. Consider alog::warn!when the fallback fires. collect_body_boundedchecks size after reading the chunk (inline atintegrations/mod.rs:143). The effective bound is "≤ max + one_chunk", not strict ≤ max. Acceptable in practice (chunk sizes are small) but worth a comment on the helper.
♻️ refactor
- Auction providers still construct
BackendConfiginline (re-raise; inline atprebid.rs:1134, alsoaps.rs:517-528,adserver_mock.rs:338-348). Diverges fromensure_integration_backendbecause providers need a per-request timeout and the helper hardcodes 15s. AuctionContext.client_infois redundant withservices.client_info()(re-raise; inline atauction/types.rs:107).run_auctiontakesservicesas a separate arg even thoughcontext.servicesexists (inline atauction/orchestrator.rs:64). They must stay in sync; an inconsistent caller breaks the auction silently.- Inconsistent
services.client_infoaccess (inline atdidomi.rs:259). Two sites use thepub(crate)field (didomi.rs:259,auction/formats.rs:145); the rest use the methodservices.client_info(). Standardize on the method.
📝 note
- Lockr/Permutive/Didomi handlers contain unreachable PUT/PATCH branches (inline at
lockr.rs:202). Routes register only GET/POST. StubHttpClient::send_asyncdoes not capture request headers (inline atplatform/test_support.rs:297). Limits header-shape assertions for prebid/aps/adserver_mock tests.
🏕 camp site
testlight::Defaultimpls can be derived (inline attestlight.rs:271).
👍 praise
compat::shim eliminated from integration dispatch —IntegrationProxy::handleis nowhttp::Request<EdgeBody>end-to-end (main.rs:195,registry.rs:266). Removes a non-trivial conversion overhead per request and a class of header-mapping bugs.- Async
parse_responsemigration withPlatformResponseresolves the priorinto_bytes()-on-Body::Streampanic class for every provider (auction/provider.rs:38). The trait docs explicitly note why it's now async — exactly the right context for future implementors. - GTM 413 vs 502 distinction (
google_tag_manager.rs:511-535). Clean separation between client-error and transport-error; this is the pattern the sharedcollect_body*helpers should follow. - EC-ID header-injection guard (
registry.rs:673) plus regression test atregistry.rs:1406. DefensiveHeaderValue::from_strblocks\r/\n/\0injection on a user-influenced ID before it lands in either the request or the response.
CI Status
- fmt: FAIL locally (CI did not run for this commit because workflow triggers don't match the PR's base branch — see header above)
- clippy: PASS locally (CI did not run)
- rust tests: PASS locally (CI did not run; 952 tests)
- wasm32-wasip1 build: PASS locally
- Integration Tests workflow: PASS
aram356
left a comment
There was a problem hiding this comment.
Inline annotations for the prior REQUEST_CHANGES review
Adding the per-line comments now that I've filtered them against the PR diff (the previous submission was body-only because GitHub rejected the batch when 3 of 17 comment anchors fell outside the changed hunks). These reference the same findings as #626 (review) — no new findings, just inline anchoring.
The three findings that couldn't be anchored to a diff line are tagged below for reference:
- ♻️ refactor —
run_auctiontakesservicesseparately even thoughcontext.servicesexists. Anchored as a top-level note rather than inline becauseauction/orchestrator.rs:64(therun_auctionsignature) is not in the diff, but the dual-pass pattern is visible at the response-handling sites (e.g.orchestrator.rs:337). - 📝 note —
StubHttpClient::send_asyncdoes not capture request headers.crates/trusted-server-core/src/platform/test_support.rsis not in this PR's diff scope at all; the asymmetry betweensend(lines 266–280) andsend_async(lines 297–320) limits header-shape assertions for prebid/aps/adserver_mock tests. Worth a follow-up issue. - 🏕 camp site —
testlight::Defaultimpls can be derived.testlight.rs:271-285is outside the changed hunks; deriving#[derive(Default)]would replace both manual impls.
aram356
left a comment
There was a problem hiding this comment.
Inline annotations for the prior REQUEST_CHANGES review
Adding the per-line comments now that I've filtered them against the PR diff (the previous submission was body-only because GitHub rejected the batch when 3 of 17 comment anchors fell outside the changed hunks). These reference the same findings as #626 (review) — no new findings, just inline anchoring.
The three findings that couldn't be anchored to a diff line are tagged below for reference:
- ♻️ refactor —
run_auctiontakesservicesseparately even thoughcontext.servicesexists. Anchored as a top-level note rather than inline becauseauction/orchestrator.rs:64(therun_auctionsignature) is not in the diff, but the dual-pass pattern is visible at the response-handling sites (e.g.orchestrator.rs:337). - 📝 note —
StubHttpClient::send_asyncdoes not capture request headers.crates/trusted-server-core/src/platform/test_support.rsis not in this PR's diff scope at all; the asymmetry betweensend(lines 266–280) andsend_async(lines 297–320) limits header-shape assertions for prebid/aps/adserver_mock tests. Worth a follow-up issue. - 🏕 camp site —
testlight::Defaultimpls can be derived.testlight.rs:271-285is outside the changed hunks; deriving#[derive(Default)]would replace both manual impls.
…/edgezero-pr13-integration-provider-type-migration
aram356
left a comment
There was a problem hiding this comment.
Summary
Re-review of the EdgeZero PR13 integration/provider HTTP type migration after the latest commit (96949807). The async parse_response, collect_response_bounded, and dedicated AUCTION_MAX_BODY_BYTES constant landed cleanly and resolved several prior findings. Three blocking issues remain, all rooted in the same anti-pattern: change_context over collect_body_bounded discards the RequestTooLarge error variant, so oversized inbound bodies surface as 502 BAD_GATEWAY instead of 413 PAYLOAD_TOO_LARGE at every site except testlight. A test added in this PR (auction_rejects_oversized_body) already proves the bug.
CI status (verified locally on 96949807)
- fmt: PASS
- clippy: FAIL — 3
doc_markdownerrors atintegrations/mod.rs:172, 175 - rust tests: FAIL —
auction_rejects_oversized_bodypanics (asserts 413, gets 502) - wasm32-wasip1 release build: PASS
- JS tests: PASS — 282 tests
- Integration Tests workflow: PASS (only workflow that ran on this commit)
The PR description's [x] cargo clippy and [x] cargo test --workspace checkboxes are not corroborated by CI on this branch — .github/workflows/{format,test}.yml only fire on pull_request: branches: [main], so on commit 96949807 only the integration-tests workflow ran. Both gates fail when run locally.
Blocking
🔧 wrench
cargo clippy --workspace --all-targets --all-features -- -D warningsfails atintegrations/mod.rs:172, 175(3 doc-markdown errors).auction_rejects_oversized_bodyfails becauseauction/endpoints.rs:46change_contextsRequestTooLarge(→ 413) intoAuction(→ 502).- Same bug at every other
collect_body_boundedcaller except testlight:datadome.rs:362-364,didomi.rs:244-246,permutive.rs:213-217,lockr.rs:205-207. All produce 502 instead of 413 for oversized bodies, defeating the bounded-read security goal.testlight.rs:182-184is the only correct caller — its?-only pattern is the model.
Non-blocking
🤔 thinking
- Unbounded upstream-response read in testlight (
testlight.rs:214). Every other RTB integration migrated tocollect_response_bounded; testlight is the last unbounded site. - Predicted-backend-name fallback at
log::debug(orchestrator.rs:339-344). Failure mode is silent bid loss; recommendlog::warn!so operators can see the fallback firing.
♻️ refactor
- Three providers still inline
BackendConfig::from_url_with_first_byte_timeout(aps.rs:519-528,adserver_mock.rs:340-348,prebid.rs:1141-1145). Justification comments are reasonable; wideningensure_integration_backendwith afirst_byte_timeout: Durationparameter would let all four sites share one helper.
📝 note
- Testlight stale-
Content-Lengthtest is unit-only (testlight.rs:388-412). Extendinghandle_uses_platform_http_client_with_http_requestto inject a staleContent-Lengthon the stubbed upstream would close the integration-level gap.
Blocking fixes: - Fix doc_markdown clippy errors in integrations/mod.rs (BAD_GATEWAY, max_bytes, one_chunk wrapped in backticks) - Drop change_context() on collect_body_bounded() calls in auction, datadome, didomi, lockr, permutive — RequestTooLarge (413) was being rewritten to Integration/Auction (502); now propagates unchanged via ? so oversized bodies correctly surface as PAYLOAD_TOO_LARGE - Remove debug_assert! in compat::to_fastly_response (already fixed in PR 12, missed in PR 13 merge) Non-blocking fixes: - Migrate testlight upstream response read from unbounded collect_body to collect_response_bounded(UPSTREAM_RTB_MAX_RESPONSE_BYTES) matching all other RTB integrations; remove now-unused collect_body function - Upgrade orchestrator predicted-backend-name fallback log from debug to warn so operators see silent bid-loss fallback in production
Conflict resolution: keep PR13 http-native integration dispatch (no compat round-trip) and add HandlerOutcome::Buffered wrapping from PR12. Migrate sourcepoint.rs from fastly SDK types to edgezero http types, completing the integration layer HTTP type migration started in PR13. All sourcepoint helper methods and tests updated to use http::Request, http::Response, and HeaderValue instead of fastly::Request/Response.
Summary
RuntimeServicesHTTP client primitives with asyncrequest_bids,PlatformPendingRequest, andPlatformResponsewhile preserving orchestrator deadline handling.Changes
crates/trusted-server-adapter-fastly/src/main.rsRuntimeServicesinto the PR13 entrypoint.crates/trusted-server-core/src/auction/README.mdPlatformPendingRequest,PlatformResponse, and the platform HTTP client flow.crates/trusted-server-core/src/auction/endpoints.rsRuntimeServicesinto the auction entrypoint context.crates/trusted-server-core/src/auction/orchestrator.rsPlatformPendingRequest/select, and update the remaining PR13 migration comments.crates/trusted-server-core/src/auction/provider.rsAuctionProviderto asyncrequest_bidswithPlatformPendingRequest/PlatformResponseand refresh the trait docs.crates/trusted-server-core/src/auction/types.rsRuntimeServicestoAuctionContext.crates/trusted-server-core/src/integrations/adserver_mock.rscrates/trusted-server-core/src/integrations/aps.rscrates/trusted-server-core/src/integrations/datadome.rscrates/trusted-server-core/src/integrations/didomi.rscrates/trusted-server-core/src/integrations/google_tag_manager.rscrates/trusted-server-core/src/integrations/gpt.rscrates/trusted-server-core/src/integrations/lockr.rscrates/trusted-server-core/src/integrations/permutive.rscrates/trusted-server-core/src/integrations/prebid.rsPlatformResponseresults.crates/trusted-server-core/src/integrations/registry.rsIntegrationProxy/routing types tohttptypes and threadRuntimeServicesthrough proxy dispatch.crates/trusted-server-core/src/integrations/testlight.rsContent-Lengthregression test, and drop stale length headers when rebuilding JSON responses.Closes
Closes #494
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 servecargo doc --no-deps --all-features(passes with pre-existing unrelated rustdoc warnings outside this PR's scope)Checklist
unwrap()in production code — useexpect("should ...")println!