@@ -7,38 +7,43 @@ 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) ;
40+
3641 }
3742}
3843
3944impl 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 {
4853impl 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 }
0 commit comments