From a15063e6fc581ddd40079923d9852a238833037f Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Mon, 17 Aug 2026 17:24:09 -0700 Subject: [PATCH 1/6] feat: Moves over ads-store --- components/ads-client/src/ads_store.rs | 100 +++++++++++++++++++++++++ components/ads-client/src/client.rs | 14 ++++ components/ads-client/src/lib.rs | 2 + 3 files changed, 116 insertions(+) create mode 100644 components/ads-client/src/ads_store.rs diff --git a/components/ads-client/src/ads_store.rs b/components/ads-client/src/ads_store.rs new file mode 100644 index 00000000000..70724bb82f2 --- /dev/null +++ b/components/ads-client/src/ads_store.rs @@ -0,0 +1,100 @@ +use crate::mars::ad_response::{AdImage, AdSpoc, AdTile}; +use std::{collections::HashMap, time::Duration}; + +// TODO: This is an intentionally naive in-memory cache implementation of the ads cache. +// It functions as a skeleton to store ads fetched in the background, and has a naive expiration mechanism. +// The subsequent vertical slice will replace this in its entirety with the http_cache sqlite database instead, with TTLs, persistent storage, etc. +const DEFAULT_TTL: Duration = Duration::from_secs(300); + +#[derive(Debug)] +pub struct AdsStore { + image_ads: HashMap, + spoc_ads: HashMap)>, + tile_ads: HashMap, +} + +impl Default for AdsStore { + fn default() -> Self { + Self::new() + } +} + +impl AdsStore { + pub fn new() -> Self { + AdsStore { + image_ads: HashMap::new(), + spoc_ads: HashMap::new(), + tile_ads: HashMap::new(), + } + } + + pub fn cache_ads( + &mut self, + ads: HashMap, + timestamp: u64, + ) { + T::cache_ads(ads, self, timestamp); + } + + pub fn get_cached_ads<'a, T: AdsStorable>( + &'a self, + placement: &str, + ) -> Option<&'a T::StorageType> { + T::fetch_cached_ads(self, placement) + } +} + +pub trait AdsStorable: Sized { + // The ad(s) to store (eg: this may be a single ad, or an array of ads) + type StorageType; + + fn cache_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: u64); + fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Self::StorageType>; +} + +impl AdsStorable for AdImage { + type StorageType = AdImage; + fn cache_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: u64) { + ads_cache + .image_ads + .extend(ads.into_iter().map(|(key, ad)| (key, (timestamp, ad)))); + ads_cache + .image_ads + .retain(|_, (x, _)| *x - timestamp < DEFAULT_TTL.as_secs()); + + } + + fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdImage> { + ads_cache.image_ads.get(id).map(|(_, ads)| ads) + } +} + +impl AdsStorable for AdSpoc { + type StorageType = Vec; + fn cache_ads(ads: HashMap>, ads_cache: &mut AdsStore, timestamp: u64) { + ads_cache + .spoc_ads + .extend(ads.into_iter().map(|(key, ad)| (key, (timestamp, ad)))); + ads_cache + .spoc_ads + .retain(|_, (x, _)| *x - timestamp < DEFAULT_TTL.as_secs()); + } + fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Vec> { + ads_cache.spoc_ads.get(id).map(|(_, ads)| ads) + } +} + +impl AdsStorable for AdTile { + type StorageType = AdTile; + fn cache_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: u64) { + ads_cache + .tile_ads + .extend(ads.into_iter().map(|(key, ad)| (key, (timestamp, ad)))); + ads_cache + .tile_ads + .retain(|_, (x, _)| *x - timestamp < DEFAULT_TTL.as_secs()); + } + fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdTile> { + ads_cache.tile_ads.get(id).map(|(_, ads)| ads) + } +} \ No newline at end of file diff --git a/components/ads-client/src/client.rs b/components/ads-client/src/client.rs index 6f510c55617..30ee06f4ad2 100644 --- a/components/ads-client/src/client.rs +++ b/components/ads-client/src/client.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use std::time::Duration; +use crate::ads_store::{AdsStorable, AdsStore}; use crate::http_cache::{ByteSize, CachePolicy, HttpCache}; use crate::mars::ad_request::{AdPlacementRequest, AdRequestFlags}; use crate::mars::ad_response::{AdImage, AdResponse, AdResponseValue, AdSpoc, AdTile}; @@ -42,6 +43,7 @@ where client: MARSClient, context_id_provider: Box, telemetry: T, + ads_store: AdsStore, } impl AdsClient @@ -91,6 +93,7 @@ where client, context_id_provider, telemetry: telemetry.clone(), + ads_store: AdsStore::new(), } } @@ -110,6 +113,16 @@ where Ok(()) } + pub fn cache_ads(&mut self, ads: HashMap) { + let now = chrono::Utc::now().timestamp().unsigned_abs(); + self.ads_store.cache_ads::(ads, now); + } + + pub fn get_cached_ads(&self, placement_id: &str) -> Option<&A::StorageType> { + self.ads_store.get_cached_ads::(placement_id) + } + + pub fn get_context_id(&self) -> context_id::ApiResult { self.context_id_provider.context_id() } @@ -301,6 +314,7 @@ mod tests { Box::new(DefaultContextIdCallback), )), telemetry, + ads_store: AdsStore::new(), } } diff --git a/components/ads-client/src/lib.rs b/components/ads-client/src/lib.rs index 87cecb2a5f8..b3aab4381d1 100644 --- a/components/ads-client/src/lib.rs +++ b/components/ads-client/src/lib.rs @@ -15,6 +15,8 @@ use client::AdsClient; use error_support::error; use http_cache::CachePolicy; use mars::ad_request::{AdPlacementRequest, AdRequestFlags}; + +pub mod ads_store; mod client; mod ffi; pub mod http_cache; From ce78d10874079693137794ef895f92e4661e715a Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Mon, 17 Aug 2026 19:04:46 -0700 Subject: [PATCH 2/6] fix: Ads tests, some refactor --- components/ads-client/src/ads_store.rs | 228 +++++++++++++++++++++---- components/ads-client/src/client.rs | 13 +- 2 files changed, 200 insertions(+), 41 deletions(-) diff --git a/components/ads-client/src/ads_store.rs b/components/ads-client/src/ads_store.rs index 70724bb82f2..0d2009b2170 100644 --- a/components/ads-client/src/ads_store.rs +++ b/components/ads-client/src/ads_store.rs @@ -1,5 +1,8 @@ use crate::mars::ad_response::{AdImage, AdSpoc, AdTile}; -use std::{collections::HashMap, time::Duration}; +use std::{ + collections::HashMap, + time::{Duration, Instant}, +}; // TODO: This is an intentionally naive in-memory cache implementation of the ads cache. // It functions as a skeleton to store ads fetched in the background, and has a naive expiration mechanism. @@ -8,9 +11,11 @@ const DEFAULT_TTL: Duration = Duration::from_secs(300); #[derive(Debug)] pub struct AdsStore { - image_ads: HashMap, - spoc_ads: HashMap)>, - tile_ads: HashMap, + ttl: Duration, + + image_ads: HashMap>, + spoc_ads: HashMap>>, + tile_ads: HashMap>, } impl Default for AdsStore { @@ -21,26 +26,31 @@ impl Default for AdsStore { impl AdsStore { pub fn new() -> Self { + AdsStore::new_with_ttl(DEFAULT_TTL) + } + + pub fn new_with_ttl(ttl: Duration) -> Self { AdsStore { + ttl, image_ads: HashMap::new(), spoc_ads: HashMap::new(), tile_ads: HashMap::new(), } } - pub fn cache_ads( + pub fn store_ads( &mut self, ads: HashMap, - timestamp: u64, + timestamp: Instant, ) { - T::cache_ads(ads, self, timestamp); + T::store_ads(ads, self, timestamp); } - pub fn get_cached_ads<'a, T: AdsStorable>( + pub fn get_stored_ads<'a, T: AdsStorable>( &'a self, placement: &str, ) -> Option<&'a T::StorageType> { - T::fetch_cached_ads(self, placement) + T::fetch_stored_ads(self, placement) } } @@ -48,53 +58,201 @@ pub trait AdsStorable: Sized { // The ad(s) to store (eg: this may be a single ad, or an array of ads) type StorageType; - fn cache_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: u64); - fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Self::StorageType>; + fn store_ads( + ads: HashMap, + ads_cache: &mut AdsStore, + timestamp: Instant, + ); + fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Self::StorageType>; } impl AdsStorable for AdImage { type StorageType = AdImage; - fn cache_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: u64) { + fn store_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: Instant) { + ads_cache.image_ads.extend( + ads.into_iter() + .map(|(key, ad)| (key, CacheEntry::new(ad, timestamp))), + ); ads_cache .image_ads - .extend(ads.into_iter().map(|(key, ad)| (key, (timestamp, ad)))); - ads_cache - .image_ads - .retain(|_, (x, _)| *x - timestamp < DEFAULT_TTL.as_secs()); - - } + .retain(|_, x| !x.is_expired(ads_cache.ttl)); + } - fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdImage> { - ads_cache.image_ads.get(id).map(|(_, ads)| ads) + fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdImage> { + ads_cache.image_ads.get(id).map(|ads| ads.get_value()) } } impl AdsStorable for AdSpoc { type StorageType = Vec; - fn cache_ads(ads: HashMap>, ads_cache: &mut AdsStore, timestamp: u64) { - ads_cache - .spoc_ads - .extend(ads.into_iter().map(|(key, ad)| (key, (timestamp, ad)))); + fn store_ads(ads: HashMap>, ads_cache: &mut AdsStore, timestamp: Instant) { + ads_cache.spoc_ads.extend( + ads.into_iter() + .map(|(key, ad)| (key, CacheEntry::new(ad, timestamp))), + ); ads_cache .spoc_ads - .retain(|_, (x, _)| *x - timestamp < DEFAULT_TTL.as_secs()); + .retain(|_, x| !x.is_expired(ads_cache.ttl)); } - fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Vec> { - ads_cache.spoc_ads.get(id).map(|(_, ads)| ads) + fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Vec> { + ads_cache.spoc_ads.get(id).map(|ads| ads.get_value()) } } impl AdsStorable for AdTile { type StorageType = AdTile; - fn cache_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: u64) { - ads_cache - .tile_ads - .extend(ads.into_iter().map(|(key, ad)| (key, (timestamp, ad)))); + fn store_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: Instant) { + ads_cache.tile_ads.extend( + ads.into_iter() + .map(|(key, ad)| (key, CacheEntry::new(ad, timestamp))), + ); ads_cache .tile_ads - .retain(|_, (x, _)| *x - timestamp < DEFAULT_TTL.as_secs()); + .retain(|_, x| !x.is_expired(ads_cache.ttl)); + } + fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdTile> { + ads_cache.tile_ads.get(id).map(|ads| ads.get_value()) + } +} + +#[derive(Debug)] +struct CacheEntry { + inserted_at: Instant, + value: T, +} + +impl CacheEntry { + fn new(value: T, instant: Instant) -> CacheEntry { + CacheEntry { + inserted_at: instant, + value, + } + } + + fn is_expired(&self, ttl: Duration) -> bool { + self.inserted_at.elapsed() >= ttl } - fn fetch_cached_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdTile> { - ads_cache.tile_ads.get(id).map(|(_, ads)| ads) + + fn get_value(&self) -> &T { + &self.value } -} \ No newline at end of file +} + +#[cfg(test)] +mod tests { + use std::{ + collections::HashMap, + time::{Duration, Instant}, + }; + + use crate::{ + ads_store::AdsStore, + mars::ad_response::{AdImage, AdSpoc, AdTile}, + test_utils, + }; + + #[test] + fn test_store_image_ad() { + let five_min_ago = Instant::now() + .checked_sub(Duration::from_mins(5)) + .expect("Could not create `Instant` for 5 minutes ago"); + let one_min_ago = Instant::now() + .checked_sub(Duration::from_mins(1)) + .expect("Could not create `Instant` for 1 minute ago"); + let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); + + let demo_ads = test_utils::get_example_happy_image_response().data; + let first_key = demo_ads + .iter() + .next() + .expect("No test data in `get_example_happy_image_response`") + .0; + // TODO: Remove this bit. + let demo_ads: HashMap = demo_ads + .clone() + .into_iter() + .filter_map(|(k, v)| Some((k, v.into_iter().next()?))) + .collect(); + + ads_store.store_ads::(demo_ads.clone(), five_min_ago); + assert!( + ads_store.get_stored_ads::(first_key).is_none(), + "Old data past TTL date must not be returned." + ); + + ads_store.store_ads::(demo_ads, one_min_ago); + assert!( + ads_store.get_stored_ads::(first_key).is_some(), + "Could not fetch fresh ad from ads store." + ); + } + + #[test] + fn test_store_spocs_ad() { + let five_min_ago = Instant::now() + .checked_sub(Duration::from_mins(5)) + .expect("Could not create `Instant` for 5 minutes ago"); + let one_min_ago = Instant::now() + .checked_sub(Duration::from_mins(1)) + .expect("Could not create `Instant` for 1 minute ago"); + let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); + + let demo_ads = test_utils::get_example_happy_spoc_response().data; + let first_key = demo_ads + .iter() + .next() + .expect("No test data in `get_example_happy_spoc_response`") + .0; + // TODO: Remove this bit. + // let demo_ads: HashMap> = demo_ads.clone().into_iter().filter_map(|(k,v)| Some((k, v.into_iter().next()?))).collect(); + + ads_store.store_ads::(demo_ads.clone(), five_min_ago); + assert!( + ads_store.get_stored_ads::(first_key).is_none(), + "Old data past TTL date must not be returned." + ); + + ads_store.store_ads::(demo_ads.clone(), one_min_ago); + assert!( + ads_store.get_stored_ads::(first_key).is_some(), + "Could not fetch fresh ad from ads store." + ); + } + + #[test] + + fn test_store_tiles_ad() { + let five_min_ago = Instant::now() + .checked_sub(Duration::from_mins(5)) + .expect("Could not create `Instant` for 5 minutes ago"); + let one_min_ago = Instant::now() + .checked_sub(Duration::from_mins(1)) + .expect("Could not create `Instant` for 1 minute ago"); + let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); + + let demo_ads = test_utils::get_example_happy_uatile_response().data; + let first_key = demo_ads + .iter() + .next() + .expect("No test data in `get_example_happy_uatile_response`") + .0; + // TODO: Remove this bit. + let demo_ads: HashMap = demo_ads + .clone() + .into_iter() + .filter_map(|(k, v)| Some((k, v.into_iter().next()?))) + .collect(); + + ads_store.store_ads::(demo_ads.clone(), five_min_ago); + assert!( + ads_store.get_stored_ads::(first_key).is_none(), + "Old data past TTL date must not be returned." + ); + + ads_store.store_ads::(demo_ads, one_min_ago); + assert!( + ads_store.get_stored_ads::(first_key).is_some(), + "Could not fetch fresh ad from ads store." + ); + } +} diff --git a/components/ads-client/src/client.rs b/components/ads-client/src/client.rs index 30ee06f4ad2..639fde28ee8 100644 --- a/components/ads-client/src/client.rs +++ b/components/ads-client/src/client.rs @@ -113,16 +113,17 @@ where Ok(()) } - pub fn cache_ads(&mut self, ads: HashMap) { - let now = chrono::Utc::now().timestamp().unsigned_abs(); - self.ads_store.cache_ads::(ads, now); + #[allow(dead_code)] + pub fn store_ads(&mut self, ads: HashMap) { + let now = std::time::Instant::now(); + self.ads_store.store_ads::(ads, now); } - pub fn get_cached_ads(&self, placement_id: &str) -> Option<&A::StorageType> { - self.ads_store.get_cached_ads::(placement_id) + #[allow(dead_code)] + pub fn get_stored_ads(&self, placement_id: &str) -> Option<&A::StorageType> { + self.ads_store.get_stored_ads::(placement_id) } - pub fn get_context_id(&self) -> context_id::ApiResult { self.context_id_provider.context_id() } From 3d0cec122d8ca18ec5c654f84ab62cad009a95d9 Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Tue, 18 Aug 2026 00:47:00 -0700 Subject: [PATCH 3/6] fix: placement_id --- components/ads-client/src/ads_store.rs | 90 +++++++++++++++----------- components/ads-client/src/client.rs | 9 ++- 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/components/ads-client/src/ads_store.rs b/components/ads-client/src/ads_store.rs index 0d2009b2170..4d6af9d738f 100644 --- a/components/ads-client/src/ads_store.rs +++ b/components/ads-client/src/ads_store.rs @@ -4,20 +4,24 @@ use std::{ time::{Duration, Instant}, }; +const DEFAULT_TTL: Duration = Duration::from_secs(300); + // TODO: This is an intentionally naive in-memory cache implementation of the ads cache. // It functions as a skeleton to store ads fetched in the background, and has a naive expiration mechanism. // The subsequent vertical slice will replace this in its entirety with the http_cache sqlite database instead, with TTLs, persistent storage, etc. -const DEFAULT_TTL: Duration = Duration::from_secs(300); - #[derive(Debug)] pub struct AdsStore { ttl: Duration, - image_ads: HashMap>, - spoc_ads: HashMap>>, - tile_ads: HashMap>, + image_ads: HashMap>, + spoc_ads: HashMap>>, + tile_ads: HashMap>, } +/// Identification of placement sent and returned from MARS (eg: `mock_spoc_1`) +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct PlacementId(String); + impl Default for AdsStore { fn default() -> Self { Self::new() @@ -40,7 +44,7 @@ impl AdsStore { pub fn store_ads( &mut self, - ads: HashMap, + ads: HashMap, timestamp: Instant, ) { T::store_ads(ads, self, timestamp); @@ -48,7 +52,7 @@ impl AdsStore { pub fn get_stored_ads<'a, T: AdsStorable>( &'a self, - placement: &str, + placement: &PlacementId, ) -> Option<&'a T::StorageType> { T::fetch_stored_ads(self, placement) } @@ -59,16 +63,19 @@ pub trait AdsStorable: Sized { type StorageType; fn store_ads( - ads: HashMap, + ads: HashMap, ads_cache: &mut AdsStore, timestamp: Instant, ); - fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Self::StorageType>; + fn fetch_stored_ads<'a>( + ads_cache: &'a AdsStore, + id: &PlacementId, + ) -> Option<&'a Self::StorageType>; } impl AdsStorable for AdImage { type StorageType = AdImage; - fn store_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: Instant) { + fn store_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: Instant) { ads_cache.image_ads.extend( ads.into_iter() .map(|(key, ad)| (key, CacheEntry::new(ad, timestamp))), @@ -78,14 +85,18 @@ impl AdsStorable for AdImage { .retain(|_, x| !x.is_expired(ads_cache.ttl)); } - fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdImage> { + fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &PlacementId) -> Option<&'a AdImage> { ads_cache.image_ads.get(id).map(|ads| ads.get_value()) } } impl AdsStorable for AdSpoc { type StorageType = Vec; - fn store_ads(ads: HashMap>, ads_cache: &mut AdsStore, timestamp: Instant) { + fn store_ads( + ads: HashMap>, + ads_cache: &mut AdsStore, + timestamp: Instant, + ) { ads_cache.spoc_ads.extend( ads.into_iter() .map(|(key, ad)| (key, CacheEntry::new(ad, timestamp))), @@ -94,14 +105,14 @@ impl AdsStorable for AdSpoc { .spoc_ads .retain(|_, x| !x.is_expired(ads_cache.ttl)); } - fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a Vec> { + fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &PlacementId) -> Option<&'a Vec> { ads_cache.spoc_ads.get(id).map(|ads| ads.get_value()) } } impl AdsStorable for AdTile { type StorageType = AdTile; - fn store_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: Instant) { + fn store_ads(ads: HashMap, ads_cache: &mut AdsStore, timestamp: Instant) { ads_cache.tile_ads.extend( ads.into_iter() .map(|(key, ad)| (key, CacheEntry::new(ad, timestamp))), @@ -110,7 +121,7 @@ impl AdsStorable for AdTile { .tile_ads .retain(|_, x| !x.is_expired(ads_cache.ttl)); } - fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &str) -> Option<&'a AdTile> { + fn fetch_stored_ads<'a>(ads_cache: &'a AdsStore, id: &PlacementId) -> Option<&'a AdTile> { ads_cache.tile_ads.get(id).map(|ads| ads.get_value()) } } @@ -146,7 +157,7 @@ mod tests { }; use crate::{ - ads_store::AdsStore, + ads_store::{AdsStore, PlacementId}, mars::ad_response::{AdImage, AdSpoc, AdTile}, test_utils, }; @@ -162,27 +173,26 @@ mod tests { let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); let demo_ads = test_utils::get_example_happy_image_response().data; + let demo_ads: HashMap = demo_ads + .clone() + .into_iter() + .filter_map(|(k, v)| Some((PlacementId(k), v.into_iter().next()?))) + .collect(); let first_key = demo_ads .iter() .next() .expect("No test data in `get_example_happy_image_response`") .0; - // TODO: Remove this bit. - let demo_ads: HashMap = demo_ads - .clone() - .into_iter() - .filter_map(|(k, v)| Some((k, v.into_iter().next()?))) - .collect(); ads_store.store_ads::(demo_ads.clone(), five_min_ago); assert!( - ads_store.get_stored_ads::(first_key).is_none(), + ads_store.get_stored_ads::(&first_key).is_none(), "Old data past TTL date must not be returned." ); - ads_store.store_ads::(demo_ads, one_min_ago); + ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( - ads_store.get_stored_ads::(first_key).is_some(), + ads_store.get_stored_ads::(&first_key).is_some(), "Could not fetch fresh ad from ads store." ); } @@ -198,23 +208,27 @@ mod tests { let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); let demo_ads = test_utils::get_example_happy_spoc_response().data; + // TODO: Remove this bit. + let demo_ads: HashMap> = demo_ads + .clone() + .into_iter() + .filter_map(|(k, v)| Some((PlacementId(k), v))) + .collect(); let first_key = demo_ads .iter() .next() .expect("No test data in `get_example_happy_spoc_response`") .0; - // TODO: Remove this bit. - // let demo_ads: HashMap> = demo_ads.clone().into_iter().filter_map(|(k,v)| Some((k, v.into_iter().next()?))).collect(); ads_store.store_ads::(demo_ads.clone(), five_min_ago); assert!( - ads_store.get_stored_ads::(first_key).is_none(), + ads_store.get_stored_ads::(&first_key).is_none(), "Old data past TTL date must not be returned." ); ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( - ads_store.get_stored_ads::(first_key).is_some(), + ads_store.get_stored_ads::(&first_key).is_some(), "Could not fetch fresh ad from ads store." ); } @@ -231,27 +245,27 @@ mod tests { let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); let demo_ads = test_utils::get_example_happy_uatile_response().data; + // TODO: Remove this bit. + let demo_ads: HashMap = demo_ads + .clone() + .into_iter() + .filter_map(|(k, v)| Some((PlacementId(k), v.into_iter().next()?))) + .collect(); let first_key = demo_ads .iter() .next() .expect("No test data in `get_example_happy_uatile_response`") .0; - // TODO: Remove this bit. - let demo_ads: HashMap = demo_ads - .clone() - .into_iter() - .filter_map(|(k, v)| Some((k, v.into_iter().next()?))) - .collect(); ads_store.store_ads::(demo_ads.clone(), five_min_ago); assert!( - ads_store.get_stored_ads::(first_key).is_none(), + ads_store.get_stored_ads::(&first_key).is_none(), "Old data past TTL date must not be returned." ); - ads_store.store_ads::(demo_ads, one_min_ago); + ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( - ads_store.get_stored_ads::(first_key).is_some(), + ads_store.get_stored_ads::(&first_key).is_some(), "Could not fetch fresh ad from ads store." ); } diff --git a/components/ads-client/src/client.rs b/components/ads-client/src/client.rs index 639fde28ee8..4c43a30665e 100644 --- a/components/ads-client/src/client.rs +++ b/components/ads-client/src/client.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use std::time::Duration; -use crate::ads_store::{AdsStorable, AdsStore}; +use crate::ads_store::{AdsStorable, AdsStore, PlacementId}; use crate::http_cache::{ByteSize, CachePolicy, HttpCache}; use crate::mars::ad_request::{AdPlacementRequest, AdRequestFlags}; use crate::mars::ad_response::{AdImage, AdResponse, AdResponseValue, AdSpoc, AdTile}; @@ -114,13 +114,16 @@ where } #[allow(dead_code)] - pub fn store_ads(&mut self, ads: HashMap) { + pub fn store_ads(&mut self, ads: HashMap) { let now = std::time::Instant::now(); self.ads_store.store_ads::(ads, now); } #[allow(dead_code)] - pub fn get_stored_ads(&self, placement_id: &str) -> Option<&A::StorageType> { + pub fn get_stored_ads( + &self, + placement_id: &PlacementId, + ) -> Option<&A::StorageType> { self.ads_store.get_stored_ads::(placement_id) } From 62669ad211d827e86c2cc03af846892befe6c188 Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Tue, 18 Aug 2026 01:14:08 -0700 Subject: [PATCH 4/6] fix: fmt clippy --- components/ads-client/src/ads_store.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/ads-client/src/ads_store.rs b/components/ads-client/src/ads_store.rs index 4d6af9d738f..7f65a28f35a 100644 --- a/components/ads-client/src/ads_store.rs +++ b/components/ads-client/src/ads_store.rs @@ -186,13 +186,13 @@ mod tests { ads_store.store_ads::(demo_ads.clone(), five_min_ago); assert!( - ads_store.get_stored_ads::(&first_key).is_none(), + ads_store.get_stored_ads::(first_key).is_none(), "Old data past TTL date must not be returned." ); ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( - ads_store.get_stored_ads::(&first_key).is_some(), + ads_store.get_stored_ads::(first_key).is_some(), "Could not fetch fresh ad from ads store." ); } @@ -212,7 +212,7 @@ mod tests { let demo_ads: HashMap> = demo_ads .clone() .into_iter() - .filter_map(|(k, v)| Some((PlacementId(k), v))) + .map(|(k, v)| (PlacementId(k), v)) .collect(); let first_key = demo_ads .iter() @@ -222,13 +222,13 @@ mod tests { ads_store.store_ads::(demo_ads.clone(), five_min_ago); assert!( - ads_store.get_stored_ads::(&first_key).is_none(), + ads_store.get_stored_ads::(first_key).is_none(), "Old data past TTL date must not be returned." ); ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( - ads_store.get_stored_ads::(&first_key).is_some(), + ads_store.get_stored_ads::(first_key).is_some(), "Could not fetch fresh ad from ads store." ); } @@ -259,13 +259,13 @@ mod tests { ads_store.store_ads::(demo_ads.clone(), five_min_ago); assert!( - ads_store.get_stored_ads::(&first_key).is_none(), + ads_store.get_stored_ads::(first_key).is_none(), "Old data past TTL date must not be returned." ); ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( - ads_store.get_stored_ads::(&first_key).is_some(), + ads_store.get_stored_ads::(first_key).is_some(), "Could not fetch fresh ad from ads store." ); } From b7fd6321ef553fa785bda98457b5aab0095e03b7 Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Tue, 18 Aug 2026 01:58:52 -0700 Subject: [PATCH 5/6] fix: Removes todos --- components/ads-client/src/ads_store.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/ads-client/src/ads_store.rs b/components/ads-client/src/ads_store.rs index 7f65a28f35a..7b5afdc1e9a 100644 --- a/components/ads-client/src/ads_store.rs +++ b/components/ads-client/src/ads_store.rs @@ -208,7 +208,6 @@ mod tests { let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); let demo_ads = test_utils::get_example_happy_spoc_response().data; - // TODO: Remove this bit. let demo_ads: HashMap> = demo_ads .clone() .into_iter() @@ -245,7 +244,6 @@ mod tests { let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); let demo_ads = test_utils::get_example_happy_uatile_response().data; - // TODO: Remove this bit. let demo_ads: HashMap = demo_ads .clone() .into_iter() From 38e696c6c3e4fd9b461f9dc6a0ee46ce3c006797 Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Tue, 18 Aug 2026 02:13:17 -0700 Subject: [PATCH 6/6] fix: Old rust version missing stabilized from_min --- components/ads-client/src/ads_store.rs | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/components/ads-client/src/ads_store.rs b/components/ads-client/src/ads_store.rs index 7b5afdc1e9a..8b52783f1d4 100644 --- a/components/ads-client/src/ads_store.rs +++ b/components/ads-client/src/ads_store.rs @@ -164,13 +164,13 @@ mod tests { #[test] fn test_store_image_ad() { - let five_min_ago = Instant::now() - .checked_sub(Duration::from_mins(5)) - .expect("Could not create `Instant` for 5 minutes ago"); let one_min_ago = Instant::now() - .checked_sub(Duration::from_mins(1)) + .checked_sub(Duration::from_secs(60)) + .expect("Could not create `Instant` for 5 minutes ago"); + let five_sec_ago = Instant::now() + .checked_sub(Duration::from_secs(5)) .expect("Could not create `Instant` for 1 minute ago"); - let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); + let mut ads_store = AdsStore::new_with_ttl(Duration::from_secs(30)); let demo_ads = test_utils::get_example_happy_image_response().data; let demo_ads: HashMap = demo_ads @@ -184,13 +184,13 @@ mod tests { .expect("No test data in `get_example_happy_image_response`") .0; - ads_store.store_ads::(demo_ads.clone(), five_min_ago); + ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( ads_store.get_stored_ads::(first_key).is_none(), "Old data past TTL date must not be returned." ); - ads_store.store_ads::(demo_ads.clone(), one_min_ago); + ads_store.store_ads::(demo_ads.clone(), five_sec_ago); assert!( ads_store.get_stored_ads::(first_key).is_some(), "Could not fetch fresh ad from ads store." @@ -199,13 +199,13 @@ mod tests { #[test] fn test_store_spocs_ad() { - let five_min_ago = Instant::now() - .checked_sub(Duration::from_mins(5)) - .expect("Could not create `Instant` for 5 minutes ago"); let one_min_ago = Instant::now() - .checked_sub(Duration::from_mins(1)) + .checked_sub(Duration::from_secs(60)) + .expect("Could not create `Instant` for 5 minutes ago"); + let five_sec_ago = Instant::now() + .checked_sub(Duration::from_secs(5)) .expect("Could not create `Instant` for 1 minute ago"); - let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); + let mut ads_store = AdsStore::new_with_ttl(Duration::from_secs(30)); let demo_ads = test_utils::get_example_happy_spoc_response().data; let demo_ads: HashMap> = demo_ads @@ -219,13 +219,13 @@ mod tests { .expect("No test data in `get_example_happy_spoc_response`") .0; - ads_store.store_ads::(demo_ads.clone(), five_min_ago); + ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( ads_store.get_stored_ads::(first_key).is_none(), "Old data past TTL date must not be returned." ); - ads_store.store_ads::(demo_ads.clone(), one_min_ago); + ads_store.store_ads::(demo_ads.clone(), five_sec_ago); assert!( ads_store.get_stored_ads::(first_key).is_some(), "Could not fetch fresh ad from ads store." @@ -235,13 +235,13 @@ mod tests { #[test] fn test_store_tiles_ad() { - let five_min_ago = Instant::now() - .checked_sub(Duration::from_mins(5)) - .expect("Could not create `Instant` for 5 minutes ago"); let one_min_ago = Instant::now() - .checked_sub(Duration::from_mins(1)) + .checked_sub(Duration::from_secs(60)) + .expect("Could not create `Instant` for 5 minutes ago"); + let five_sec_ago = Instant::now() + .checked_sub(Duration::from_secs(5)) .expect("Could not create `Instant` for 1 minute ago"); - let mut ads_store = AdsStore::new_with_ttl(Duration::from_mins(3)); + let mut ads_store = AdsStore::new_with_ttl(Duration::from_secs(30)); let demo_ads = test_utils::get_example_happy_uatile_response().data; let demo_ads: HashMap = demo_ads @@ -255,13 +255,13 @@ mod tests { .expect("No test data in `get_example_happy_uatile_response`") .0; - ads_store.store_ads::(demo_ads.clone(), five_min_ago); + ads_store.store_ads::(demo_ads.clone(), one_min_ago); assert!( ads_store.get_stored_ads::(first_key).is_none(), "Old data past TTL date must not be returned." ); - ads_store.store_ads::(demo_ads.clone(), one_min_ago); + ads_store.store_ads::(demo_ads.clone(), five_sec_ago); assert!( ads_store.get_stored_ads::(first_key).is_some(), "Could not fetch fresh ad from ads store."