Skip to content

Commit 4cabee5

Browse files
Address review: add log guard, startup warning, extract enrich_response_metadata with tests
1 parent 3d752dc commit 4cabee5

File tree

1 file changed

+142
-48
lines changed

1 file changed

+142
-48
lines changed

crates/common/src/integrations/prebid.rs

Lines changed: 142 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,54 @@ impl PrebidAuctionProvider {
668668
}
669669
}
670670

671+
/// Enrich an [`AuctionResponse`] with metadata extracted from the Prebid
672+
/// Server response `ext`.
673+
///
674+
/// Always-on fields: `responsetimemillis`, `errors`, `warnings`.
675+
/// Debug-only fields (gated on `config.debug`): `debug`, `bidstatus`.
676+
fn enrich_response_metadata(
677+
&self,
678+
response_json: &Json,
679+
auction_response: &mut AuctionResponse,
680+
) {
681+
let ext = response_json.get("ext");
682+
683+
// Always attach per-bidder timing and diagnostics.
684+
if let Some(rtm) = ext.and_then(|e| e.get("responsetimemillis")) {
685+
auction_response
686+
.metadata
687+
.insert("responsetimemillis".to_string(), rtm.clone());
688+
}
689+
if let Some(errors) = ext.and_then(|e| e.get("errors")) {
690+
auction_response
691+
.metadata
692+
.insert("errors".to_string(), errors.clone());
693+
}
694+
if let Some(warnings) = ext.and_then(|e| e.get("warnings")) {
695+
auction_response
696+
.metadata
697+
.insert("warnings".to_string(), warnings.clone());
698+
}
699+
700+
// When debug is enabled, surface httpcalls, resolvedrequest, and
701+
// per-bid status from the Prebid Server response.
702+
if self.config.debug {
703+
if let Some(debug) = ext.and_then(|e| e.get("debug")) {
704+
auction_response
705+
.metadata
706+
.insert("debug".to_string(), debug.clone());
707+
}
708+
if let Some(bidstatus) = ext
709+
.and_then(|e| e.get("prebid"))
710+
.and_then(|p| p.get("bidstatus"))
711+
{
712+
auction_response
713+
.metadata
714+
.insert("bidstatus".to_string(), bidstatus.clone());
715+
}
716+
}
717+
}
718+
671719
/// Parse a single bid from `OpenRTB` response.
672720
fn parse_bid(&self, bid_obj: &Json, seat: &str) -> Result<AuctionBid, ()> {
673721
let slot_id = bid_obj
@@ -833,7 +881,7 @@ impl AuctionProvider for PrebidAuctionProvider {
833881

834882
// Log the full response body when debug is enabled to surface
835883
// ext.debug.httpcalls, resolvedrequest, bidstatus, errors, etc.
836-
if self.config.debug {
884+
if self.config.debug && log::log_enabled!(log::Level::Debug) {
837885
match serde_json::to_string_pretty(&response_json) {
838886
Ok(json) => log::debug!("Prebid OpenRTB response:\n{json}"),
839887
Err(e) => {
@@ -864,36 +912,7 @@ impl AuctionProvider for PrebidAuctionProvider {
864912
}
865913

866914
let mut auction_response = self.parse_openrtb_response(&response_json, response_time_ms);
867-
868-
// Attach per-bidder timing and errors from the Prebid Server response.
869-
// `responsetimemillis` contains an entry for every invited bidder, even
870-
// those that returned no bids, making it the canonical source for
871-
// "who was in the auction."
872-
let ext = response_json.get("ext");
873-
if let Some(rtm) = ext.and_then(|e| e.get("responsetimemillis")) {
874-
auction_response = auction_response.with_metadata("responsetimemillis", rtm.clone());
875-
}
876-
if let Some(errors) = ext.and_then(|e| e.get("errors")) {
877-
auction_response = auction_response.with_metadata("errors", errors.clone());
878-
}
879-
if let Some(warnings) = ext.and_then(|e| e.get("warnings")) {
880-
auction_response = auction_response.with_metadata("warnings", warnings.clone());
881-
}
882-
883-
// When debug is enabled, surface the additional Prebid Server debug
884-
// data: httpcalls, resolvedrequest, and per-bid status.
885-
if self.config.debug {
886-
if let Some(debug) = ext.and_then(|e| e.get("debug")) {
887-
auction_response = auction_response.with_metadata("debug", debug.clone());
888-
}
889-
if let Some(bidstatus) = ext
890-
.and_then(|e| e.get("prebid"))
891-
.and_then(|p| p.get("bidstatus"))
892-
{
893-
auction_response =
894-
auction_response.with_metadata("bidstatus", bidstatus.clone());
895-
}
896-
}
915+
self.enrich_response_metadata(&response_json, &mut auction_response);
897916

898917
log::info!(
899918
"Prebid returned {} bids in {}ms",
@@ -939,6 +958,12 @@ pub fn register_auction_provider(settings: &Settings) -> Vec<Arc<dyn AuctionProv
939958
"Registering Prebid auction provider (server_url={})",
940959
config.server_url
941960
);
961+
if config.debug {
962+
log::warn!(
963+
"Prebid debug mode is ON — debug data (httpcalls, resolvedrequest, \
964+
bidstatus) will be included in /auction responses"
965+
);
966+
}
942967
providers.push(Arc::new(PrebidAuctionProvider::new(config)));
943968
}
944969
Ok(None) => {
@@ -1890,10 +1915,9 @@ fixed_bottom = {placementId = "_s2sBottom"}
18901915
}
18911916

18921917
#[test]
1893-
fn parse_response_preserves_responsetimemillis_and_errors_metadata() {
1918+
fn enrich_response_metadata_attaches_always_on_fields() {
18941919
let provider = PrebidAuctionProvider::new(base_config());
18951920

1896-
// Minimal valid OpenRTB response with ext containing diagnostics.
18971921
let response_json = json!({
18981922
"seatbid": [{
18991923
"seat": "kargo",
@@ -1911,39 +1935,109 @@ fixed_bottom = {placementId = "_s2sBottom"}
19111935
},
19121936
"errors": {
19131937
"openx": [{"code": 1, "message": "timeout"}]
1938+
},
1939+
"warnings": {
1940+
"kargo": [{"code": 10, "message": "bid floor"}]
1941+
},
1942+
"debug": {
1943+
"httpcalls": {"kargo": []}
1944+
},
1945+
"prebid": {
1946+
"bidstatus": [{"bidder": "kargo", "status": "bid"}]
19141947
}
19151948
}
19161949
});
19171950

1918-
// Replicate the metadata extraction logic from `run_auction`.
19191951
let mut auction_response = provider.parse_openrtb_response(&response_json, 150);
1952+
provider.enrich_response_metadata(&response_json, &mut auction_response);
19201953

1921-
let ext = response_json.get("ext");
1922-
if let Some(rtm) = ext.and_then(|e| e.get("responsetimemillis")) {
1923-
auction_response = auction_response.with_metadata("responsetimemillis", rtm.clone());
1924-
}
1925-
if let Some(errors) = ext.and_then(|e| e.get("errors")) {
1926-
auction_response = auction_response.with_metadata("errors", errors.clone());
1927-
}
1928-
1929-
// Verify bids were parsed.
19301954
assert_eq!(auction_response.bids.len(), 1, "should parse one bid");
19311955

1932-
// Verify responsetimemillis is preserved.
1956+
// Always-on fields should be present.
19331957
let rtm = auction_response
19341958
.metadata
19351959
.get("responsetimemillis")
1936-
.expect("should have responsetimemillis in metadata");
1960+
.expect("should have responsetimemillis");
19371961
assert_eq!(rtm["kargo"], 98);
19381962
assert_eq!(rtm["appnexus"], 0);
19391963
assert_eq!(rtm["ix"], 120);
19401964

1941-
// Verify errors are preserved.
19421965
let errors = auction_response
19431966
.metadata
19441967
.get("errors")
1945-
.expect("should have errors in metadata");
1968+
.expect("should have errors");
19461969
assert_eq!(errors["openx"][0]["code"], 1);
1947-
assert_eq!(errors["openx"][0]["message"], "timeout");
1970+
1971+
let warnings = auction_response
1972+
.metadata
1973+
.get("warnings")
1974+
.expect("should have warnings");
1975+
assert_eq!(warnings["kargo"][0]["code"], 10);
1976+
1977+
// Debug-gated fields should NOT be present (base_config has debug: false).
1978+
assert!(
1979+
!auction_response.metadata.contains_key("debug"),
1980+
"should not include debug when config.debug is false"
1981+
);
1982+
assert!(
1983+
!auction_response.metadata.contains_key("bidstatus"),
1984+
"should not include bidstatus when config.debug is false"
1985+
);
1986+
}
1987+
1988+
#[test]
1989+
fn enrich_response_metadata_includes_debug_fields_when_enabled() {
1990+
let mut config = base_config();
1991+
config.debug = true;
1992+
let provider = PrebidAuctionProvider::new(config);
1993+
1994+
let response_json = json!({
1995+
"seatbid": [],
1996+
"ext": {
1997+
"responsetimemillis": {"kargo": 50},
1998+
"debug": {
1999+
"httpcalls": {"kargo": [{"uri": "https://pbs.example/bid", "status": 200}]},
2000+
"resolvedrequest": {"id": "resolved-123"}
2001+
},
2002+
"prebid": {
2003+
"bidstatus": [
2004+
{"bidder": "kargo", "status": "bid"},
2005+
{"bidder": "openx", "status": "timeout"}
2006+
]
2007+
}
2008+
}
2009+
});
2010+
2011+
let mut auction_response = provider.parse_openrtb_response(&response_json, 100);
2012+
provider.enrich_response_metadata(&response_json, &mut auction_response);
2013+
2014+
// Always-on field should still be present.
2015+
assert!(
2016+
auction_response.metadata.contains_key("responsetimemillis"),
2017+
"should have responsetimemillis"
2018+
);
2019+
2020+
// Debug-gated fields should now be present.
2021+
let debug = auction_response
2022+
.metadata
2023+
.get("debug")
2024+
.expect("should have debug when config.debug is true");
2025+
assert_eq!(
2026+
debug["httpcalls"]["kargo"][0]["status"], 200,
2027+
"should include httpcalls from PBS debug response"
2028+
);
2029+
assert_eq!(
2030+
debug["resolvedrequest"]["id"], "resolved-123",
2031+
"should include resolvedrequest from PBS debug response"
2032+
);
2033+
2034+
let bidstatus = auction_response
2035+
.metadata
2036+
.get("bidstatus")
2037+
.expect("should have bidstatus when config.debug is true");
2038+
let statuses = bidstatus.as_array().expect("bidstatus should be array");
2039+
assert_eq!(statuses.len(), 2);
2040+
assert_eq!(statuses[0]["bidder"], "kargo");
2041+
assert_eq!(statuses[1]["status"], "timeout");
19482042
}
19492043
}

0 commit comments

Comments
 (0)