Skip to content

Commit 34847d8

Browse files
committed
refactor(ads-client): changed AdRequest.url to be AdRequest.environment instead for the cache hashing to be more stable.
1 parent 06c391e commit 34847d8

3 files changed

Lines changed: 49 additions & 37 deletions

File tree

components/ads-client/src/mars.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ where
6767
where
6868
A: AdResponseValue,
6969
{
70-
let url = self.environment.into_url("ads");
71-
let mut ad_request = AdRequest::try_new(context_id, placements, url, ohttp)?;
70+
let mut ad_request = AdRequest::try_new(context_id, self.environment, ohttp, placements)?;
7271
let request_hash = RequestHash::new(&ad_request);
7372

7473
if ohttp {

components/ads-client/src/mars/ad_request.rs

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,42 @@ use std::collections::HashSet;
77
use std::hash::{Hash, Hasher};
88

99
use serde::{Deserialize, Serialize};
10-
use url::Url;
1110
use viaduct::{Headers, Request};
1211

12+
use crate::mars::Environment;
13+
1314
use super::error::BuildRequestError;
1415

16+
const ENDPOINT: &str = "ads";
17+
1518
#[derive(Debug, PartialEq, Serialize)]
1619
pub struct AdRequest {
1720
pub context_id: String,
21+
/// Skipped to exclude from the request body
22+
#[serde(skip)]
23+
pub environment: Environment,
1824
#[serde(skip)]
1925
pub headers: Headers,
2026
#[serde(skip)]
2127
pub ohttp: bool,
2228
pub placements: Vec<AdPlacementRequest>,
23-
/// Skipped to exclude from the request body
24-
#[serde(skip)]
25-
pub url: Url,
2629
}
2730

2831
/// Hash implementation intentionally excludes `context_id` as it rotates
2932
/// on client re-instantiation and should not invalidate cached responses.
3033
/// `headers` are also excluded as they are request metadata, not cache keys.
34+
/// If response shape ever varies, add a version to this hash for variant tracking.
3135
impl Hash for AdRequest {
3236
fn hash<H: Hasher>(&self, state: &mut H) {
33-
self.url.as_str().hash(state);
34-
self.placements.hash(state);
37+
self.environment.hash(state);
3538
self.ohttp.hash(state);
39+
self.placements.hash(state);
3640
}
3741
}
3842

3943
impl From<AdRequest> for Request {
4044
fn from(ad_request: AdRequest) -> Self {
41-
let url = ad_request.url.clone();
45+
let url = ad_request.environment.into_url(ENDPOINT);
4246
let mut request = Request::post(url).json(&ad_request);
4347
request.headers.extend(ad_request.headers);
4448
request
@@ -48,20 +52,20 @@ impl From<AdRequest> for Request {
4852
impl AdRequest {
4953
pub fn try_new(
5054
context_id: String,
51-
placements: Vec<AdPlacementRequest>,
52-
url: Url,
55+
environment: Environment,
5356
ohttp: bool,
57+
placements: Vec<AdPlacementRequest>,
5458
) -> Result<Self, BuildRequestError> {
5559
if placements.is_empty() {
5660
return Err(BuildRequestError::EmptyConfig);
5761
};
5862

5963
let mut request = AdRequest {
6064
context_id,
65+
environment,
6166
headers: Headers::new(),
6267
ohttp,
6368
placements: vec![],
64-
url,
6569
};
6670

6771
let mut used_placement_ids: HashSet<String> = HashSet::new();
@@ -177,9 +181,10 @@ mod tests {
177181

178182
#[test]
179183
fn test_build_ad_request_happy() {
180-
let url: Url = "https://example.com/ads".parse().unwrap();
181184
let request = AdRequest::try_new(
182185
TEST_CONTEXT_ID.to_string(),
186+
Environment::Test,
187+
false,
183188
vec![
184189
AdPlacementRequest {
185190
content: Some(AdContentCategory {
@@ -198,13 +203,12 @@ mod tests {
198203
placement: "example_placement_2".to_string(),
199204
},
200205
],
201-
url.clone(),
202-
false,
203206
)
204207
.unwrap();
205208

206209
let expected_request = AdRequest {
207210
context_id: TEST_CONTEXT_ID.to_string(),
211+
environment: Environment::Test,
208212
headers: Headers::new(),
209213
ohttp: false,
210214
placements: vec![
@@ -225,17 +229,17 @@ mod tests {
225229
placement: "example_placement_2".to_string(),
226230
},
227231
],
228-
url,
229232
};
230233

231234
assert_eq!(request, expected_request);
232235
}
233236

234237
#[test]
235238
fn test_build_ad_request_fails_on_duplicate_placement_id() {
236-
let url: Url = "https://example.com/ads".parse().unwrap();
237239
let request = AdRequest::try_new(
238240
TEST_CONTEXT_ID.to_string(),
241+
Environment::Test,
242+
false,
239243
vec![
240244
AdPlacementRequest {
241245
content: Some(AdContentCategory {
@@ -254,24 +258,25 @@ mod tests {
254258
placement: "example_placement_1".to_string(),
255259
},
256260
],
257-
url,
258-
false,
259261
);
260262
assert!(request.is_err());
261263
}
262264

263265
#[test]
264266
fn test_build_ad_request_fails_on_empty_request() {
265-
let url: Url = "https://example.com/ads".parse().unwrap();
266-
let request = AdRequest::try_new(TEST_CONTEXT_ID.to_string(), vec![], url, false);
267+
let request = AdRequest::try_new(
268+
TEST_CONTEXT_ID.to_string(),
269+
Environment::Test,
270+
false,
271+
vec![],
272+
);
267273
assert!(request.is_err());
268274
}
269275

270276
#[test]
271277
fn test_context_id_ignored_in_hash() {
272278
use crate::http_cache::RequestHash;
273279

274-
let url: Url = "https://example.com/ads".parse().unwrap();
275280
let make_placements = || {
276281
vec![AdPlacementRequest {
277282
content: None,
@@ -283,8 +288,10 @@ mod tests {
283288
let context_id_a = "aaaa-bbbb-cccc".to_string();
284289
let context_id_b = "dddd-eeee-ffff".to_string();
285290

286-
let req1 = AdRequest::try_new(context_id_a, make_placements(), url.clone(), false).unwrap();
287-
let req2 = AdRequest::try_new(context_id_b, make_placements(), url, false).unwrap();
291+
let req1 =
292+
AdRequest::try_new(context_id_a, Environment::Test, false, make_placements()).unwrap();
293+
let req2 =
294+
AdRequest::try_new(context_id_b, Environment::Test, false, make_placements()).unwrap();
288295

289296
assert_eq!(RequestHash::new(&req1), RequestHash::new(&req2));
290297
}
@@ -293,29 +300,27 @@ mod tests {
293300
fn test_different_placements_produce_different_hash() {
294301
use crate::http_cache::RequestHash;
295302

296-
let url: Url = "https://example.com/ads".parse().unwrap();
297-
298303
let req1 = AdRequest::try_new(
299304
"same-id".to_string(),
305+
Environment::Test,
306+
false,
300307
vec![AdPlacementRequest {
301308
content: None,
302309
count: 1,
303310
placement: "tile_1".to_string(),
304311
}],
305-
url.clone(),
306-
false,
307312
)
308313
.unwrap();
309314

310315
let req2 = AdRequest::try_new(
311316
"same-id".to_string(),
317+
Environment::Test,
318+
false,
312319
vec![AdPlacementRequest {
313320
content: None,
314321
count: 3,
315322
placement: "tile_2".to_string(),
316323
}],
317-
url,
318-
false,
319324
)
320325
.unwrap();
321326

@@ -326,7 +331,6 @@ mod tests {
326331
fn test_ohttp_flag_produces_different_hash() {
327332
use crate::http_cache::RequestHash;
328333

329-
let url: Url = "https://example.com/ads".parse().unwrap();
330334
let make_placements = || {
331335
vec![AdPlacementRequest {
332336
content: None,
@@ -335,11 +339,20 @@ mod tests {
335339
}]
336340
};
337341

338-
let req_direct =
339-
AdRequest::try_new("same-id".to_string(), make_placements(), url.clone(), false)
340-
.unwrap();
341-
let req_ohttp =
342-
AdRequest::try_new("same-id".to_string(), make_placements(), url, true).unwrap();
342+
let req_direct = AdRequest::try_new(
343+
"same-id".to_string(),
344+
Environment::Test,
345+
false,
346+
make_placements(),
347+
)
348+
.unwrap();
349+
let req_ohttp = AdRequest::try_new(
350+
"same-id".to_string(),
351+
Environment::Test,
352+
true,
353+
make_placements(),
354+
)
355+
.unwrap();
343356

344357
assert_ne!(RequestHash::new(&req_direct), RequestHash::new(&req_ohttp));
345358
}

components/ads-client/src/mars/environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ static MARS_API_ENDPOINT_PROD: Lazy<Url> = Lazy::new(|| url!("https://ads.mozill
1111

1212
static MARS_API_ENDPOINT_STAGING: Lazy<Url> = Lazy::new(|| url!("https://ads.allizom.org/v1/"));
1313

14-
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
14+
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
1515
pub enum Environment {
1616
#[default]
1717
Prod,

0 commit comments

Comments
 (0)