Skip to content

Commit 8c22cc2

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 8c22cc2

3 files changed

Lines changed: 50 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: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,43 @@ 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);
40+
3641
}
3742
}
3843

3944
impl From<AdRequest> for Request {
4045
fn from(ad_request: AdRequest) -> Self {
41-
let url = ad_request.url.clone();
46+
let url = ad_request.environment.into_url(ENDPOINT);
4247
let mut request = Request::post(url).json(&ad_request);
4348
request.headers.extend(ad_request.headers);
4449
request
@@ -48,20 +53,20 @@ impl From<AdRequest> for Request {
4853
impl AdRequest {
4954
pub fn try_new(
5055
context_id: String,
51-
placements: Vec<AdPlacementRequest>,
52-
url: Url,
56+
environment: Environment,
5357
ohttp: bool,
58+
placements: Vec<AdPlacementRequest>,
5459
) -> Result<Self, BuildRequestError> {
5560
if placements.is_empty() {
5661
return Err(BuildRequestError::EmptyConfig);
5762
};
5863

5964
let mut request = AdRequest {
6065
context_id,
66+
environment,
6167
headers: Headers::new(),
6268
ohttp,
6369
placements: vec![],
64-
url,
6570
};
6671

6772
let mut used_placement_ids: HashSet<String> = HashSet::new();
@@ -177,9 +182,10 @@ mod tests {
177182

178183
#[test]
179184
fn test_build_ad_request_happy() {
180-
let url: Url = "https://example.com/ads".parse().unwrap();
181185
let request = AdRequest::try_new(
182186
TEST_CONTEXT_ID.to_string(),
187+
Environment::Test,
188+
false,
183189
vec![
184190
AdPlacementRequest {
185191
content: Some(AdContentCategory {
@@ -198,13 +204,12 @@ mod tests {
198204
placement: "example_placement_2".to_string(),
199205
},
200206
],
201-
url.clone(),
202-
false,
203207
)
204208
.unwrap();
205209

206210
let expected_request = AdRequest {
207211
context_id: TEST_CONTEXT_ID.to_string(),
212+
environment: Environment::Test,
208213
headers: Headers::new(),
209214
ohttp: false,
210215
placements: vec![
@@ -225,17 +230,17 @@ mod tests {
225230
placement: "example_placement_2".to_string(),
226231
},
227232
],
228-
url,
229233
};
230234

231235
assert_eq!(request, expected_request);
232236
}
233237

234238
#[test]
235239
fn test_build_ad_request_fails_on_duplicate_placement_id() {
236-
let url: Url = "https://example.com/ads".parse().unwrap();
237240
let request = AdRequest::try_new(
238241
TEST_CONTEXT_ID.to_string(),
242+
Environment::Test,
243+
false,
239244
vec![
240245
AdPlacementRequest {
241246
content: Some(AdContentCategory {
@@ -254,24 +259,25 @@ mod tests {
254259
placement: "example_placement_1".to_string(),
255260
},
256261
],
257-
url,
258-
false,
259262
);
260263
assert!(request.is_err());
261264
}
262265

263266
#[test]
264267
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);
268+
let request = AdRequest::try_new(
269+
TEST_CONTEXT_ID.to_string(),
270+
Environment::Test,
271+
false,
272+
vec![],
273+
);
267274
assert!(request.is_err());
268275
}
269276

270277
#[test]
271278
fn test_context_id_ignored_in_hash() {
272279
use crate::http_cache::RequestHash;
273280

274-
let url: Url = "https://example.com/ads".parse().unwrap();
275281
let make_placements = || {
276282
vec![AdPlacementRequest {
277283
content: None,
@@ -283,8 +289,10 @@ mod tests {
283289
let context_id_a = "aaaa-bbbb-cccc".to_string();
284290
let context_id_b = "dddd-eeee-ffff".to_string();
285291

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();
292+
let req1 =
293+
AdRequest::try_new(context_id_a, Environment::Test, false, make_placements()).unwrap();
294+
let req2 =
295+
AdRequest::try_new(context_id_b, Environment::Test, false, make_placements()).unwrap();
288296

289297
assert_eq!(RequestHash::new(&req1), RequestHash::new(&req2));
290298
}
@@ -293,29 +301,27 @@ mod tests {
293301
fn test_different_placements_produce_different_hash() {
294302
use crate::http_cache::RequestHash;
295303

296-
let url: Url = "https://example.com/ads".parse().unwrap();
297-
298304
let req1 = AdRequest::try_new(
299305
"same-id".to_string(),
306+
Environment::Test,
307+
false,
300308
vec![AdPlacementRequest {
301309
content: None,
302310
count: 1,
303311
placement: "tile_1".to_string(),
304312
}],
305-
url.clone(),
306-
false,
307313
)
308314
.unwrap();
309315

310316
let req2 = AdRequest::try_new(
311317
"same-id".to_string(),
318+
Environment::Test,
319+
false,
312320
vec![AdPlacementRequest {
313321
content: None,
314322
count: 3,
315323
placement: "tile_2".to_string(),
316324
}],
317-
url,
318-
false,
319325
)
320326
.unwrap();
321327

@@ -326,7 +332,6 @@ mod tests {
326332
fn test_ohttp_flag_produces_different_hash() {
327333
use crate::http_cache::RequestHash;
328334

329-
let url: Url = "https://example.com/ads".parse().unwrap();
330335
let make_placements = || {
331336
vec![AdPlacementRequest {
332337
content: None,
@@ -335,11 +340,20 @@ mod tests {
335340
}]
336341
};
337342

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();
343+
let req_direct = AdRequest::try_new(
344+
"same-id".to_string(),
345+
Environment::Test,
346+
false,
347+
make_placements(),
348+
)
349+
.unwrap();
350+
let req_ohttp = AdRequest::try_new(
351+
"same-id".to_string(),
352+
Environment::Test,
353+
true,
354+
make_placements(),
355+
)
356+
.unwrap();
343357

344358
assert_ne!(RequestHash::new(&req_direct), RequestHash::new(&req_ohttp));
345359
}

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)