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