From e55d98522eaf2a60db5eea0810427310e5fed0a4 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 17 Jul 2026 20:03:27 -0500 Subject: [PATCH 1/3] Preserve Prebid bidder parameter string types --- crates/trusted-server-core/src/settings.rs | 61 ++++++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/crates/trusted-server-core/src/settings.rs b/crates/trusted-server-core/src/settings.rs index c49e9968..c186a650 100644 --- a/crates/trusted-server-core/src/settings.rs +++ b/crates/trusted-server-core/src/settings.rs @@ -191,7 +191,19 @@ impl IntegrationSettings { match value { JsonValue::Object(map) => JsonValue::Object( map.into_iter() - .map(|(key, val)| (key, Self::normalize_env_value(val))) + .map(|(key, value)| { + let value = if matches!( + key.as_str(), + "bid_param_overrides" + | "bid_param_zone_overrides" + | "bid_param_override_rules" + ) { + Self::normalize_opaque_json_value(value) + } else { + Self::normalize_env_value(value) + }; + (key, value) + }) .collect(), ), JsonValue::Array(items) => { @@ -208,6 +220,15 @@ impl IntegrationSettings { } } + fn normalize_opaque_json_value(value: JsonValue) -> JsonValue { + match value { + JsonValue::String(raw) => { + serde_json::from_str::(&raw).unwrap_or_else(|_| JsonValue::String(raw)) + } + other => other, + } + } + /// Normalizes all entries in place, converting JSON-encoded strings from /// environment variables into their proper typed representations. /// Called eagerly after deserialization so that TOML serialization in @@ -3198,6 +3219,38 @@ origin_host_header_overide = "www.example.com""#, ); } + #[test] + fn prebid_numeric_string_bid_param_overrides_survive_config_roundtrip() { + let toml_str = format!( + r#"{} + +[integrations.prebid.bid_param_overrides.pubmatic] +publisherId = "12345" +adSlot = "67890" +"#, + crate_test_settings_str() + ); + let settings = Settings::from_toml(&toml_str).expect("should parse TOML settings"); + let serialized = serde_json::to_value(&settings).expect("should serialize settings"); + let runtime_settings = + Settings::from_json_value(serialized).expect("should parse runtime JSON settings"); + let raw = runtime_settings + .integrations + .get("prebid") + .expect("should contain Prebid settings"); + + assert_eq!( + raw["bid_param_overrides"]["pubmatic"]["publisherId"], + json!("12345"), + "should preserve numeric-looking publisherId as a string" + ); + assert_eq!( + raw["bid_param_overrides"]["pubmatic"]["adSlot"], + json!("67890"), + "should preserve numeric-looking adSlot as a string" + ); + } + #[test] fn test_prebid_bid_param_overrides_override_with_json_env() { let toml_str = crate_test_settings_str(); @@ -3221,7 +3274,7 @@ origin_host_header_overide = "www.example.com""#, || { temp_env::with_var( env_key, - Some(r#"{"criteo":{"networkId":99999,"pubid":"server-pub"}}"#), + Some(r#"{"criteo":{"networkId":99999,"pubid":"24680"}}"#), || { let settings = Settings::from_toml_and_env(&toml_str) .expect("Settings should parse with bidder param override env"); @@ -3239,8 +3292,8 @@ origin_host_header_overide = "www.example.com""#, ); assert_eq!( cfg_json["bid_param_overrides"]["criteo"]["pubid"], - json!("server-pub"), - "should deserialize pubid override from env JSON" + json!("24680"), + "should preserve numeric-looking pubid override from env JSON as a string" ); }, ); From 5eef5f166cd67d3daf645f28760434adbf5e1c30 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 17 Jul 2026 20:07:29 -0500 Subject: [PATCH 2/3] Satisfy Clippy for override normalization --- crates/trusted-server-core/src/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/trusted-server-core/src/settings.rs b/crates/trusted-server-core/src/settings.rs index c186a650..e8743e7c 100644 --- a/crates/trusted-server-core/src/settings.rs +++ b/crates/trusted-server-core/src/settings.rs @@ -223,7 +223,7 @@ impl IntegrationSettings { fn normalize_opaque_json_value(value: JsonValue) -> JsonValue { match value { JsonValue::String(raw) => { - serde_json::from_str::(&raw).unwrap_or_else(|_| JsonValue::String(raw)) + serde_json::from_str::(&raw).unwrap_or(JsonValue::String(raw)) } other => other, } From 02c985fcd67b59033312c1dd61963e08f7f63204 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 21 Jul 2026 09:31:26 -0500 Subject: [PATCH 3/3] Simplify Prebid bid parameter normalization --- .../src/integrations/prebid.rs | 10 -- crates/trusted-server-core/src/settings.rs | 155 +++--------------- 2 files changed, 21 insertions(+), 144 deletions(-) diff --git a/crates/trusted-server-core/src/integrations/prebid.rs b/crates/trusted-server-core/src/integrations/prebid.rs index 903b4f35..cfc73548 100644 --- a/crates/trusted-server-core/src/integrations/prebid.rs +++ b/crates/trusted-server-core/src/integrations/prebid.rs @@ -289,11 +289,6 @@ pub struct PrebidIntegrationConfig { /// param1 = 12345 /// param2 = "value" /// ``` - /// - /// Example via environment variable: - /// ```text - /// TRUSTED_SERVER__INTEGRATIONS__PREBID__BID_PARAM_OVERRIDES='{"bidder-name":{"param1":12345,"param2":"value"}}' - /// ``` #[serde(default)] pub bid_param_overrides: HashMap>, /// Canonical ordered bidder-param override rules. @@ -311,11 +306,6 @@ pub struct PrebidIntegrationConfig { /// when.zone = "header" /// set = { placementId = "_abc" } /// ``` - /// - /// Example via environment variable: - /// ```text - /// TRUSTED_SERVER__INTEGRATIONS__PREBID__BID_PARAM_OVERRIDE_RULES='[{"when":{"bidder":"kargo","zone":"header"},"set":{"placementId":"_abc"}}]' - /// ``` #[serde(default)] pub bid_param_override_rules: Vec, /// How consent signals are forwarded to Prebid Server. diff --git a/crates/trusted-server-core/src/settings.rs b/crates/trusted-server-core/src/settings.rs index e8743e7c..67fff82b 100644 --- a/crates/trusted-server-core/src/settings.rs +++ b/crates/trusted-server-core/src/settings.rs @@ -187,23 +187,12 @@ impl IntegrationSettings { Ok(()) } + #[cfg(test)] fn normalize_env_value(value: JsonValue) -> JsonValue { match value { JsonValue::Object(map) => JsonValue::Object( map.into_iter() - .map(|(key, value)| { - let value = if matches!( - key.as_str(), - "bid_param_overrides" - | "bid_param_zone_overrides" - | "bid_param_override_rules" - ) { - Self::normalize_opaque_json_value(value) - } else { - Self::normalize_env_value(value) - }; - (key, value) - }) + .map(|(key, value)| (key, Self::normalize_env_value(value))) .collect(), ), JsonValue::Array(items) => { @@ -220,20 +209,8 @@ impl IntegrationSettings { } } - fn normalize_opaque_json_value(value: JsonValue) -> JsonValue { - match value { - JsonValue::String(raw) => { - serde_json::from_str::(&raw).unwrap_or(JsonValue::String(raw)) - } - other => other, - } - } - - /// Normalizes all entries in place, converting JSON-encoded strings from - /// environment variables into their proper typed representations. - /// Called eagerly after deserialization so that TOML serialization in - /// build.rs preserves correct types. - pub fn normalize(&mut self) { + #[cfg(test)] + fn normalize_legacy_env(&mut self) { for value in self.entries.values_mut() { *value = Self::normalize_env_value(value.clone()); } @@ -2046,12 +2023,13 @@ impl Settings { .change_context(TrustedServerError::Configuration { message: "Failed to build configuration".to_string(), })?; - let settings: Self = + let mut settings: Self = config .try_deserialize() .change_context(TrustedServerError::Configuration { message: "Failed to deserialize configuration".to_string(), })?; + settings.integrations.normalize_legacy_env(); Self::finalize_deserialized(settings, "Build-time configuration") } @@ -2060,7 +2038,6 @@ impl Settings { mut settings: Self, validation_label: &str, ) -> Result> { - settings.integrations.normalize(); settings.proxy.normalize(); settings.image_optimizer.normalize(); settings.consent.validate(); @@ -3227,6 +3204,13 @@ origin_host_header_overide = "www.example.com""#, [integrations.prebid.bid_param_overrides.pubmatic] publisherId = "12345" adSlot = "67890" + +[integrations.prebid.bid_param_zone_overrides.pubmatic] +header = {{ placementId = "24680" }} + +[[integrations.prebid.bid_param_override_rules]] +when = {{ bidder = "pubmatic", zone = "in_content" }} +set = {{ placementId = "13579" }} "#, crate_test_settings_str() ); @@ -3249,112 +3233,15 @@ adSlot = "67890" json!("67890"), "should preserve numeric-looking adSlot as a string" ); - } - - #[test] - fn test_prebid_bid_param_overrides_override_with_json_env() { - let toml_str = crate_test_settings_str(); - let env_key = format!( - "{}{}INTEGRATIONS{}PREBID{}BID_PARAM_OVERRIDES", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR - ); - - let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR - ); - temp_env::with_var( - origin_key, - Some("https://origin.test-publisher.com"), - || { - temp_env::with_var( - env_key, - Some(r#"{"criteo":{"networkId":99999,"pubid":"24680"}}"#), - || { - let settings = Settings::from_toml_and_env(&toml_str) - .expect("Settings should parse with bidder param override env"); - let cfg = settings - .integration_config::("prebid") - .expect("Prebid config query should succeed") - .expect("Prebid config should exist with env override"); - let cfg_json = - serde_json::to_value(&cfg).expect("should serialize config to JSON"); - - assert_eq!( - cfg_json["bid_param_overrides"]["criteo"]["networkId"], - json!(99999), - "should deserialize networkId override from env JSON" - ); - assert_eq!( - cfg_json["bid_param_overrides"]["criteo"]["pubid"], - json!("24680"), - "should preserve numeric-looking pubid override from env JSON as a string" - ); - }, - ); - }, - ); - } - - #[test] - fn test_prebid_bid_param_override_rules_override_with_json_env() { - let toml_str = crate_test_settings_str(); - let env_key = format!( - "{}{}INTEGRATIONS{}PREBID{}BID_PARAM_OVERRIDE_RULES", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR - ); - - let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + assert_eq!( + raw["bid_param_zone_overrides"]["pubmatic"]["header"]["placementId"], + json!("24680"), + "should preserve numeric-looking zone override parameters as strings" ); - temp_env::with_var( - origin_key, - Some("https://origin.test-publisher.com"), - || { - temp_env::with_var( - env_key, - Some( - r#"[{"when":{"bidder":"kargo","zone":"header"},"set":{"placementId":"server-header","keep":"yes"}}]"#, - ), - || { - let settings = Settings::from_toml_and_env(&toml_str) - .expect("Settings should parse canonical bidder param override rules"); - let cfg = settings - .integration_config::("prebid") - .expect("Prebid config query should succeed") - .expect("Prebid config should exist with env override"); - let cfg_json = - serde_json::to_value(&cfg).expect("should serialize config to JSON"); - - assert_eq!( - cfg_json["bid_param_override_rules"][0]["when"]["bidder"], - json!("kargo"), - "should deserialize bidder matcher from env JSON" - ); - assert_eq!( - cfg_json["bid_param_override_rules"][0]["when"]["zone"], - json!("header"), - "should deserialize zone matcher from env JSON" - ); - assert_eq!( - cfg_json["bid_param_override_rules"][0]["set"]["placementId"], - json!("server-header"), - "should deserialize set object from env JSON" - ); - }, - ); - }, + assert_eq!( + raw["bid_param_override_rules"][0]["set"]["placementId"], + json!("13579"), + "should preserve numeric-looking rule parameters as strings" ); }