Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- **Breaking** — `bid_param_zone_overrides` inner values must now be JSON objects; previously non-object or empty values (`"header" = "x"`, `"header" = {}`) were accepted and silently produced a dead rule at runtime. They now fail at startup with a configuration error. Operators upgrading should audit their `bid_param_zone_overrides` config for non-object zone entries.
- **Breaking** — Integration configuration strings are no longer globally reinterpreted as JSON scalars. Operators upgrading should audit `[integrations.*]` settings and use native TOML/typed-config booleans and numbers (for example, `enabled = true`, not `enabled = "true"`); quoted numeric and boolean scalars now fail validation instead of silently converting.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 note — Accurate for typed fields, but the sentence "quoted numeric and boolean scalars now fail validation" could be misread as applying to the free-form bidder-param maps too. Those are the opposite: bid_param_overrides / bid_param_zone_overrides / rule set values are serde_json::Map<String, Json>, so publisherId = "12345" is now preserved as a string rather than failing — which is the whole fix. Consider a trailing clause, e.g. "… free-form bidder-param override values are preserved verbatim (a quoted "12345" stays a string)." Non-blocking.

- **Breaking** — Sourcepoint browser module inclusion now requires explicit `[integrations.sourcepoint].enabled = true`; operators relying on the previous unconditional Sourcepoint module should enable the integration before upgrading.

### Security
Expand Down
95 changes: 85 additions & 10 deletions crates/trusted-server-core/src/integrations/prebid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, serde_json::Map<String, Json>>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📌 out of scope — Removing these TRUSTED_SERVER__INTEGRATIONS__PREBID__BID_PARAM_OVERRIDES='{...}' doc examples is correct, but equivalent env-var override examples still live in the guide docs and would now mislead operators: docs/guide/integrations-overview.md:293-308, docs/guide/error-reference.md:117-123, and trusted-server.example.toml:163 (TRUSTED_SERVER__CREATIVE_OPPORTUNITIES__SLOT='[{…}]', an array override EdgeZero's overlay rejects). Worth a follow-up issue rather than expanding this PR.

/// Canonical ordered bidder-param override rules.
Expand All @@ -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<BidParamOverrideRule>,
/// How consent signals are forwarded to Prebid Server.
Expand Down Expand Up @@ -5728,6 +5718,91 @@ set = { keywords = { genre = "news" } }
)
}

#[test]
fn bid_param_override_numeric_strings_survive_runtime_config_roundtrip() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 praise — This is the right shape for the regression test. Going through serde_json::to_valueSettings::from_json_value mirrors the real BlobEnvelope / settings_from_config_blob transition instead of only exercising from_toml, and it covers all three override surfaces (static bid_param_overrides, bid_param_zone_overrides, and a canonical bid_param_override_rules entry) in one pass. This is the version of the test that would have caught #932.

let toml_str = format!(
r#"{}

[integrations.prebid]
enabled = true
server_url = "https://prebid.example"
bidders = ["pubmatic"]

[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" }}
"#,
TOML_BASE
);
let settings = Settings::from_toml(&toml_str).expect("should parse TOML settings");

// This mirrors the typed data transition beneath `ts config push`:
// `BlobEnvelope` data is deserialized by `settings_from_config_blob`.
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 config = runtime_settings
.integration_config::<PrebidIntegrationConfig>(PREBID_INTEGRATION_ID)
.expect("should parse Prebid config")
.expect("should enable Prebid config");

let static_request = make_auction_request(vec![make_ts_slot(
"ad-static-0",
&json!({ "pubmatic": { "keep": "client" } }),
None,
)]);
let static_ortb = call_to_openrtb(config.clone(), &static_request);
let static_params = bidder_params(&static_ortb);
assert_eq!(
static_params["pubmatic"]["publisherId"],
json!("12345"),
"static override publisherId should remain a string"
);
assert_eq!(
static_params["pubmatic"]["adSlot"],
json!("67890"),
"static override adSlot should remain a string"
);
assert_eq!(
static_params["pubmatic"]["keep"],
json!("client"),
"static override should preserve client parameters"
);

let header_request = make_auction_request(vec![make_ts_slot(
"ad-header-0",
&json!({ "pubmatic": {} }),
Some("header"),
)]);
let header_ortb = call_to_openrtb(config.clone(), &header_request);
let header_params = bidder_params(&header_ortb);
assert_eq!(
header_params["pubmatic"]["placementId"],
json!("24680"),
"zone override placementId should remain a string"
);

let in_content_request = make_auction_request(vec![make_ts_slot(
"ad-in-content-0",
&json!({ "pubmatic": {} }),
Some("in_content"),
)]);
let in_content_ortb = call_to_openrtb(config, &in_content_request);
let in_content_params = bidder_params(&in_content_ortb);
assert_eq!(
in_content_params["pubmatic"]["placementId"],
json!("13579"),
"canonical rule placementId should remain a string"
);
}

#[test]
fn zone_override_replaces_placement_id() {
let mut config = base_config();
Expand Down
Loading
Loading