@@ -7,38 +7,42 @@ use std::collections::HashSet;
77use std:: hash:: { Hash , Hasher } ;
88
99use serde:: { Deserialize , Serialize } ;
10- use url:: Url ;
1110use viaduct:: { Headers , Request } ;
1211
12+ use crate :: mars:: Environment ;
13+
1314use super :: error:: BuildRequestError ;
1415
16+ const ENDPOINT : & str = "ads" ;
17+
1518#[ derive( Debug , PartialEq , Serialize ) ]
1619pub 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.
3135impl 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
3943impl 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 {
4852impl 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 }
0 commit comments