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
22 changes: 20 additions & 2 deletions crates/trusted-server-core/src/auction/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ fn parse_client_auction_uid(raw: &JsonValue) -> Option<Uid> {
let atype = uid
.get("atype")
.and_then(JsonValue::as_u64)
.and_then(|atype| u8::try_from(atype).ok());
.and_then(|atype| i32::try_from(atype).ok());

let ext = match uid.get("ext") {
Some(JsonValue::Object(_)) => uid.get("ext").cloned(),
Expand Down Expand Up @@ -1057,6 +1057,24 @@ mod tests {
assert_eq!(parsed[0].uids[0].id, "valid", "should keep valid UID");
}

#[test]
fn parse_client_auction_eids_preserves_pair_atype() {
let raw = json!([
{
"source": "google.com",
"uids": [{ "id": "pair-id", "atype": 571187 }]
}
]);

let parsed = parse_client_auction_eids(Some(&raw)).expect("should parse PAIR EID");

assert_eq!(
parsed[0].uids[0].atype,
Some(571187),
"should preserve PAIR's vendor-specific atype"
);
}

#[test]
fn parse_client_auction_eids_preserves_uid_ext_and_sanitizes_invalid_atype() {
let raw = json!([
Expand All @@ -1070,7 +1088,7 @@ mod tests {
},
{
"id": "uid-bad-atype",
"atype": 999,
"atype": 2_147_483_648_u64,
"ext": { "keep": true }
},
{
Expand Down
16 changes: 14 additions & 2 deletions crates/trusted-server-core/src/ec/eids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct ResolvedPartnerId {
/// The synced user ID value.
pub uid: String,
/// `OpenRTB` agent type for this partner's identifiers.
pub openrtb_atype: u8,
pub openrtb_atype: i32,
}

/// Resolves source-domain keyed IDs from a KV entry against the partner registry.
Expand Down Expand Up @@ -214,17 +214,29 @@ mod tests {
source_domain: "id5-sync.com".to_owned(),
openrtb_atype: 1,
},
ResolvedPartnerId {
uid: "pair-id".to_owned(),
source_domain: "google.com".to_owned(),
openrtb_atype: 571187,
},
];

let eids = to_eids(&resolved);

assert_eq!(eids.len(), 2, "should produce one EID per resolved partner");
assert_eq!(eids.len(), 3, "should produce one EID per resolved partner");
assert_eq!(eids[0].source, "liveramp.com");
assert_eq!(eids[0].uids[0].id, "LR_xyz");
assert_eq!(eids[0].uids[0].atype, Some(3));
assert_eq!(eids[1].source, "id5-sync.com");
assert_eq!(eids[1].uids[0].id, "ID5_abc");
assert_eq!(eids[1].uids[0].atype, Some(1));
assert_eq!(eids[2].source, "google.com", "should preserve PAIR source");
assert_eq!(eids[2].uids[0].id, "pair-id", "should preserve PAIR ID");
assert_eq!(
eids[2].uids[0].atype,
Some(571187),
"should preserve PAIR vendor-specific atype"
);
}

#[test]
Expand Down
48 changes: 41 additions & 7 deletions crates/trusted-server-core/src/ec/prebid_eids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct LegacyCookieEid {
dead_code,
reason = "legacy cookie field is deserialized for compatibility but not emitted"
)]
atype: u8,
atype: i32,
}

/// OpenRTB-style `ts-eids` cookie entry.
Expand All @@ -48,7 +48,7 @@ struct StructuredCookieEid {
struct StructuredCookieUid {
id: String,
#[serde(default)]
atype: Option<u8>,
atype: Option<i32>,
#[serde(default)]
ext: Option<JsonValue>,
}
Expand Down Expand Up @@ -318,14 +318,15 @@ fn structured_cookie_uid_to_openrtb(uid: StructuredCookieUid) -> Option<Uid> {
return None;
}

let atype = uid.atype.filter(|atype| *atype >= 0);
let ext = match uid.ext {
Some(JsonValue::Object(_)) => uid.ext,
_ => None,
};

Some(Uid {
id: uid.id,
atype: uid.atype,
atype,
ext,
})
}
Expand All @@ -338,7 +339,7 @@ fn legacy_cookie_eids_to_openrtb(entries: Vec<LegacyCookieEid>) -> Vec<Eid> {
source: entry.source,
uids: vec![Uid {
id: entry.id,
atype: Some(entry.atype),
atype: (entry.atype >= 0).then_some(entry.atype),
ext: None,
}],
})
Expand Down Expand Up @@ -407,15 +408,25 @@ mod tests {
let eids = vec![
json!({"source": "id5-sync.com", "id": "ID5_abc", "atype": 1}),
json!({"source": "liveramp.com", "id": "LR_xyz", "atype": 3}),
json!({"source": "google.com", "id": "pair-id", "atype": 571187}),
];
let encoded = BASE64.encode(serde_json::to_vec(&eids).expect("should serialize"));

let decoded = parse_prebid_eids_cookie(&encoded).expect("should decode valid payload");
assert_eq!(decoded.len(), 2, "should parse both EIDs");
assert_eq!(decoded.len(), 3, "should parse all EIDs");
assert_eq!(decoded[0].source, "id5-sync.com");
assert_eq!(decoded[0].uids[0].id, "ID5_abc");
assert_eq!(decoded[1].source, "liveramp.com");
assert_eq!(decoded[1].uids[0].id, "LR_xyz");
assert_eq!(
decoded[2].source, "google.com",
"should preserve PAIR source"
);
assert_eq!(
decoded[2].uids[0].atype,
Some(571187),
"should preserve PAIR vendor-specific atype"
);
}

#[test]
Expand All @@ -424,22 +435,45 @@ mod tests {
"source": "sharedid.org",
"uids": [
{"id": "shared_123", "atype": 3},
{"id": "shared_456", "ext": {"provider": "example"}}
{"id": "shared_456", "ext": {"provider": "example"}},
{"id": "shared_invalid", "atype": -1}
]
})];
let encoded = BASE64.encode(serde_json::to_vec(&eids).expect("should serialize"));

let decoded = parse_prebid_eids_cookie(&encoded).expect("should decode valid payload");
assert_eq!(decoded.len(), 1, "should parse one structured EID entry");
assert_eq!(decoded[0].source, "sharedid.org");
assert_eq!(decoded[0].uids.len(), 2, "should preserve multiple UIDs");
assert_eq!(decoded[0].uids.len(), 3, "should preserve multiple UIDs");
assert_eq!(decoded[0].uids[0].id, "shared_123");
assert_eq!(decoded[0].uids[0].atype, Some(3));
assert_eq!(
decoded[0].uids[1].ext,
Some(json!({"provider": "example"})),
"should preserve UID ext objects"
);
assert_eq!(
decoded[0].uids[2].atype, None,
"should drop negative atype values"
);
}

#[test]
fn parse_prebid_eids_cookie_preserves_pair_atype() {
let encoded = encode_json(&json!([
{
"source": "google.com",
"uids": [{ "id": "pair-id", "atype": 571187 }]
}
]));

let decoded = parse_prebid_eids_cookie(&encoded).expect("should decode PAIR EID");

assert_eq!(
decoded[0].uids[0].atype,
Some(571187),
"should preserve PAIR's vendor-specific atype"
);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/trusted-server-core/src/ec/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct PartnerConfig {
/// Canonical `OpenRTB` EID source domain and EC KV `ids` key.
pub source_domain: String,
/// `OpenRTB` `atype` value.
pub openrtb_atype: u8,
pub openrtb_atype: i32,
/// Whether this partner's UIDs appear in auction `user.eids`.
pub bidstream_enabled: bool,
/// SHA-256 hex of the partner's API token (precomputed at startup).
Expand Down
18 changes: 17 additions & 1 deletion crates/trusted-server-core/src/integrations/prebid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4654,6 +4654,14 @@ external_bundle_sri = "sha384-AAAA"
ext: None,
}],
},
crate::openrtb::Eid {
source: "google.com".to_owned(),
uids: vec![crate::openrtb::Uid {
id: "pair-id".to_owned(),
atype: Some(571187),
ext: None,
}],
},
]);

let settings = make_settings();
Expand All @@ -4670,7 +4678,7 @@ external_bundle_sri = "sha384-AAAA"
let serialized = serde_json::to_value(&openrtb).expect("should serialize OpenRTB request");
let ext_eids = &serialized["user"]["ext"]["eids"];
assert!(ext_eids.is_array(), "should populate user.ext.eids");
assert_eq!(ext_eids.as_array().unwrap().len(), 2, "should have 2 EIDs");
assert_eq!(ext_eids.as_array().unwrap().len(), 3, "should have 3 EIDs");
assert_eq!(
ext_eids[0]["source"], "liveramp.com",
"should include liveramp EID"
Expand All @@ -4679,6 +4687,14 @@ external_bundle_sri = "sha384-AAAA"
ext_eids[1]["source"], "id5-sync.com",
"should include id5 EID"
);
assert_eq!(
ext_eids[2]["source"], "google.com",
"should include PAIR EID"
);
assert_eq!(
ext_eids[2]["uids"][0]["atype"], 571187,
"should preserve PAIR's vendor-specific atype"
);
}

#[test]
Expand Down
23 changes: 21 additions & 2 deletions crates/trusted-server-core/src/openrtb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ pub struct Eid {
pub struct Uid {
/// The identifier value.
pub id: String,
/// Agent type: 1 = cookie/device, 2 = person, 3 = user-provided.
/// `OpenRTB` agent type, including vendor-specific values such as PAIR's `571187`.
#[serde(skip_serializing_if = "Option::is_none")]
pub atype: Option<u8>,
pub atype: Option<i32>,
/// Provider-specific extension data.
#[serde(skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
Expand Down Expand Up @@ -400,4 +400,23 @@ mod tests {
"ext should be omitted when None"
);
}

#[test]
fn eid_serializes_vendor_specific_atype() {
let eid = Eid {
source: "google.com".to_owned(),
uids: vec![Uid {
id: "pair-id".to_owned(),
atype: Some(571187),
ext: None,
}],
};

let serialized = serde_json::to_value(&eid).expect("should serialize");

assert_eq!(
serialized["uids"][0]["atype"], 571187,
"should preserve PAIR's vendor-specific atype"
);
}
}
51 changes: 46 additions & 5 deletions crates/trusted-server-core/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,13 @@ pub struct EcPartner {
/// This normalized domain is also the canonical EC KV `ids` map key.
#[validate(custom(function = EcPartner::validate_source_domain))]
pub source_domain: String,
/// `OpenRTB` `atype` value (typically 3).
/// `OpenRTB` `atype` value, including vendor-specific values such as PAIR's `571187`.
#[serde(
default = "EcPartner::default_openrtb_atype",
deserialize_with = "from_value_or_str"
)]
pub openrtb_atype: u8,
#[validate(range(min = 0, message = "must be a non-negative OpenRTB agent type"))]
pub openrtb_atype: i32,
/// Whether this partner's UIDs appear in auction `user.eids`.
#[serde(default, deserialize_with = "from_value_or_str")]
pub bidstream_enabled: bool,
Expand Down Expand Up @@ -417,7 +418,7 @@ impl EcPartner {
}

#[must_use]
pub const fn default_openrtb_atype() -> u8 {
pub const fn default_openrtb_atype() -> i32 {
3
}

Expand Down Expand Up @@ -2808,6 +2809,46 @@ mod tests {
}
}

#[test]
fn validate_accepts_vendor_specific_ec_partner_atype() {
let toml_str = format!(
r#"{}
[[ec.partners]]
name = "PAIR Partner"
source_domain = "google.com"
openrtb_atype = 571187
api_token = "test-vendor-token-32-bytes-minimum"
"#,
crate_test_settings_str(),
);

let settings = Settings::from_toml(&toml_str)
.expect("should accept vendor-specific OpenRTB agent type");

assert_eq!(
settings.ec.partners[0].openrtb_atype, 571187,
"should preserve PAIR's vendor-specific atype"
);
}

#[test]
fn validate_rejects_negative_ec_partner_atype() {
let toml_str = format!(
r#"{}
[[ec.partners]]
name = "Invalid Partner"
source_domain = "partner.example.com"
openrtb_atype = -1
api_token = "test-vendor-token-32-bytes-minimum"
"#,
crate_test_settings_str(),
);

let result = Settings::from_toml(&toml_str);

assert!(result.is_err(), "should reject negative OpenRTB agent type");
}

#[test]
fn validate_accepts_origin_host_header_override() {
let toml_str = crate_test_settings_str().replace(
Expand Down Expand Up @@ -3647,7 +3688,7 @@ origin_host_header_overide = "www.example.com""#,
(origin_key, Some("https://origin.test-publisher.com")),
(partner_0_name_key, Some("Env Partner 0")),
(partner_0_source_domain_key, Some("envpartner0.example.com")),
(partner_0_openrtb_atype_key, Some("1")),
(partner_0_openrtb_atype_key, Some("571187")),
(partner_0_bidstream_enabled_key, Some("true")),
(partner_0_api_token_key, Some("env-token-0")),
(partner_1_name_key, Some("Env Partner 1")),
Expand All @@ -3666,7 +3707,7 @@ origin_host_header_overide = "www.example.com""#,
settings.ec.partners[0].source_domain,
"envpartner0.example.com"
);
assert_eq!(settings.ec.partners[0].openrtb_atype, 1);
assert_eq!(settings.ec.partners[0].openrtb_atype, 571187);
assert!(settings.ec.partners[0].bidstream_enabled);
assert_eq!(settings.ec.partners[0].api_token.expose(), "env-token-0");
assert_eq!(settings.ec.partners[1].name, "Env Partner 1");
Expand Down
Loading
Loading