Skip to content

Commit 12f6cc0

Browse files
authored
Ads client cleanup (#7057)
* refactor(ads-client): reorganize client * refactor(ads-client): remove dead code and sort derives
1 parent 47a5db1 commit 12f6cc0

File tree

11 files changed

+708
-780
lines changed

11 files changed

+708
-780
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
*/
5+
6+
use std::collections::HashMap;
7+
use std::time::Duration;
8+
9+
use crate::client::config::MozAdsClientConfig;
10+
use crate::error::{RecordClickError, RecordImpressionError, ReportAdError, RequestAdsError};
11+
use crate::http_cache::HttpCache;
12+
use crate::mars::{DefaultMARSClient, MARSClient};
13+
use crate::MozAdsRequestOptions;
14+
use ad_request::{AdPlacementRequest, AdRequest};
15+
use ad_response::MozAd;
16+
use uuid::Uuid;
17+
18+
use crate::error::CallbackRequestError;
19+
use crate::http_cache::{ByteSize, HttpCacheError};
20+
21+
pub mod ad_request;
22+
pub mod ad_response;
23+
pub mod config;
24+
25+
const DEFAULT_TTL_SECONDS: u64 = 300;
26+
const DEFAULT_MAX_CACHE_SIZE_MIB: u64 = 10;
27+
28+
pub struct MozAdsClientInner {
29+
client: Box<dyn MARSClient>,
30+
}
31+
32+
impl MozAdsClientInner {
33+
pub fn new(client_config: Option<MozAdsClientConfig>) -> Self {
34+
let context_id = Uuid::new_v4().to_string();
35+
36+
let client_config = client_config.unwrap_or_default();
37+
38+
// Configure the cache if a path is provided.
39+
// Defaults for ttl and cache size are also set if unspecified.
40+
if let Some(cache_cfg) = client_config.cache_config {
41+
let default_cache_ttl = Duration::from_secs(
42+
cache_cfg
43+
.default_cache_ttl_seconds
44+
.unwrap_or(DEFAULT_TTL_SECONDS),
45+
);
46+
let max_cache_size =
47+
ByteSize::mib(cache_cfg.max_size_mib.unwrap_or(DEFAULT_MAX_CACHE_SIZE_MIB));
48+
49+
let http_cache = HttpCache::builder(cache_cfg.db_path)
50+
.max_size(max_cache_size)
51+
.default_ttl(default_cache_ttl)
52+
.build()
53+
.ok(); // TODO: handle error with telemetry
54+
55+
let client = Box::new(DefaultMARSClient::new(
56+
context_id,
57+
client_config.environment,
58+
http_cache,
59+
));
60+
return Self { client };
61+
}
62+
63+
let client = Box::new(DefaultMARSClient::new(
64+
context_id,
65+
client_config.environment,
66+
None,
67+
));
68+
Self { client }
69+
}
70+
71+
pub fn request_ads(
72+
&self,
73+
ad_placement_requests: Vec<AdPlacementRequest>,
74+
options: Option<MozAdsRequestOptions>,
75+
) -> Result<HashMap<String, Vec<MozAd>>, RequestAdsError> {
76+
let ad_request = AdRequest::build(self.client.get_context_id()?, ad_placement_requests)?;
77+
let options = options.unwrap_or_default();
78+
let cache_policy = options.cache_policy.unwrap_or_default();
79+
let response = self.client.fetch_ads(&ad_request, &cache_policy)?;
80+
let placements = response.build_placements(&ad_request)?;
81+
Ok(placements)
82+
}
83+
84+
pub fn record_impression(&self, placement: &MozAd) -> Result<(), RecordImpressionError> {
85+
self.client
86+
.record_impression(placement.callbacks.impression.clone())
87+
}
88+
89+
pub fn record_click(&self, placement: &MozAd) -> Result<(), RecordClickError> {
90+
self.client.record_click(placement.callbacks.click.clone())
91+
}
92+
93+
pub fn report_ad(&self, placement: &MozAd) -> Result<(), ReportAdError> {
94+
let report_ad_callback = placement.callbacks.report.clone();
95+
96+
match report_ad_callback {
97+
Some(callback) => self.client.report_ad(callback)?,
98+
None => {
99+
return Err(ReportAdError::CallbackRequest(
100+
CallbackRequestError::MissingCallback {
101+
message: "Report callback url empty.".to_string(),
102+
},
103+
));
104+
}
105+
}
106+
Ok(())
107+
}
108+
109+
pub fn cycle_context_id(&mut self) -> context_id::ApiResult<String> {
110+
self.client.cycle_context_id()
111+
}
112+
113+
pub fn clear_cache(&self) -> Result<(), HttpCacheError> {
114+
self.client.clear_cache()
115+
}
116+
}
117+
118+
#[cfg(test)]
119+
mod tests {
120+
use url::Url;
121+
122+
use crate::{
123+
client::ad_request::{AdContentCategory, IABContentTaxonomy},
124+
mars::MockMARSClient,
125+
test_utils::{
126+
get_example_happy_ad_response, get_example_happy_placements,
127+
make_happy_placement_requests,
128+
},
129+
};
130+
131+
use super::*;
132+
133+
#[test]
134+
fn test_request_ads_happy() {
135+
let mut mock = MockMARSClient::new();
136+
mock.expect_fetch_ads()
137+
.returning(|_req, _| Ok(get_example_happy_ad_response()));
138+
mock.expect_get_context_id()
139+
.returning(|| Ok("mock-context-id".to_string()));
140+
141+
mock.expect_get_mars_endpoint()
142+
.return_const(Url::parse("https://mock.endpoint/ads").unwrap());
143+
144+
let component = MozAdsClientInner {
145+
client: Box::new(mock),
146+
};
147+
148+
let ad_placement_requests = make_happy_placement_requests();
149+
150+
let result = component.request_ads(ad_placement_requests, None);
151+
152+
assert!(result.is_ok());
153+
}
154+
155+
#[test]
156+
fn test_request_ads_multiset_happy() {
157+
let mut mock = MockMARSClient::new();
158+
mock.expect_fetch_ads()
159+
.returning(|_req, _| Ok(get_example_happy_ad_response()));
160+
mock.expect_get_context_id()
161+
.returning(|| Ok("mock-context-id".to_string()));
162+
163+
mock.expect_get_mars_endpoint()
164+
.return_const(Url::parse("https://mock.endpoint/ads").unwrap());
165+
166+
let component = MozAdsClientInner {
167+
client: Box::new(mock),
168+
};
169+
170+
let ad_placement_requests: Vec<AdPlacementRequest> = vec![
171+
AdPlacementRequest {
172+
placement: "example_placement_1".to_string(),
173+
count: 1,
174+
content: Some(AdContentCategory {
175+
taxonomy: IABContentTaxonomy::IAB2_1,
176+
categories: vec!["entertainment".to_string()],
177+
}),
178+
},
179+
AdPlacementRequest {
180+
placement: "example_placement_2".to_string(),
181+
count: 2,
182+
content: Some(AdContentCategory {
183+
taxonomy: IABContentTaxonomy::IAB3_0,
184+
categories: vec![],
185+
}),
186+
},
187+
];
188+
189+
let result = component.request_ads(ad_placement_requests, None);
190+
191+
assert!(result.is_ok());
192+
assert_eq!(result.unwrap(), get_example_happy_placements());
193+
}
194+
}

0 commit comments

Comments
 (0)