1use chrono::{Datelike as _, NaiveDate, Utc};
4use serde::{Deserialize, Serialize};
5use serde_aux_ext::field_attributes::deserialize_option_bool_from_anything;
6
7use crate::objects::selector::Selector;
8
9#[derive(Deserialize, Serialize, Debug, Clone)]
11pub struct ReportingRequest {
12 #[serde(with = "reporting_request_date_format")]
13 #[serde(rename = "startTime")]
14 pub start_time: NaiveDate,
15
16 #[serde(with = "reporting_request_date_format")]
17 #[serde(rename = "endTime")]
18 pub end_time: NaiveDate,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub granularity: Option<ReportingRequestGranularity>,
22
23 #[serde(rename = "groupBy", skip_serializing_if = "Option::is_none")]
24 pub group_by: Option<Vec<ReportingRequestGroupBy>>,
25
26 #[serde(
27 default,
28 deserialize_with = "deserialize_option_bool_from_anything",
29 rename = "returnGrandTotals",
30 skip_serializing_if = "Option::is_none"
31 )]
32 pub return_grand_totals: Option<bool>,
33
34 #[serde(
35 default,
36 deserialize_with = "deserialize_option_bool_from_anything",
37 rename = "returnRecordsWithNoMetrics",
38 skip_serializing_if = "Option::is_none"
39 )]
40 pub return_records_with_no_metrics: Option<bool>,
41
42 #[serde(
43 default,
44 deserialize_with = "deserialize_option_bool_from_anything",
45 rename = "returnRowTotals",
46 skip_serializing_if = "Option::is_none"
47 )]
48 pub return_row_totals: Option<bool>,
49
50 pub selector: Selector,
51
52 #[serde(rename = "timeZone", skip_serializing_if = "Option::is_none")]
53 pub time_zone: Option<ReportingRequestTimeZone>,
54}
55
56impl Default for ReportingRequest {
57 fn default() -> Self {
58 let now = Utc::now();
59 let now = NaiveDate::from_ymd_opt(now.year(), now.month(), now.day()).expect("");
60 Self::new(now - chrono::Duration::days(3), now, Selector::default())
61 }
62}
63
64impl ReportingRequest {
65 pub fn new(start_time: NaiveDate, end_time: NaiveDate, selector: Selector) -> Self {
66 Self {
67 start_time,
68 end_time,
69 granularity: None,
70 group_by: None,
71 return_grand_totals: None,
72 return_records_with_no_metrics: None,
73 return_row_totals: None,
74 selector,
75 time_zone: None,
76 }
77 }
78
79 pub fn set_granularity(
80 &mut self,
81 val: impl Into<Option<ReportingRequestGranularity>>,
82 ) -> &mut Self {
83 self.granularity = val.into();
84
85 self.return_row_totals = Some(false);
88 self.return_grand_totals = Some(false);
89
90 self
91 }
92
93 pub fn set_group_by(
94 &mut self,
95 val: impl Into<Option<Vec<ReportingRequestGroupBy>>>,
96 ) -> &mut Self {
97 self.group_by = val.into();
98 self
99 }
100
101 pub fn set_return_grand_totals(&mut self, val: impl Into<Option<bool>>) -> &mut Self {
102 if self.granularity.is_none() {
103 self.return_grand_totals = val.into();
104 }
105 self
106 }
107
108 pub fn set_return_records_with_no_metrics(
109 &mut self,
110 val: impl Into<Option<bool>>,
111 ) -> &mut Self {
112 self.return_records_with_no_metrics = val.into();
113 self
114 }
115
116 pub fn set_return_row_totals(&mut self, val: impl Into<Option<bool>>) -> &mut Self {
117 if self.granularity.is_none() {
118 self.return_row_totals = val.into();
119 }
120 self
121 }
122
123 pub fn set_time_zone(&mut self, val: impl Into<Option<ReportingRequestTimeZone>>) -> &mut Self {
124 self.time_zone = val.into();
125 self
126 }
127}
128
129pub mod reporting_request_date_format {
130 use chrono::NaiveDate;
131 use serde::{self, Deserialize, Deserializer, Serializer};
132
133 const FORMAT: &str = "%Y-%m-%d";
134
135 pub fn serialize<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
136 where
137 S: Serializer,
138 {
139 let s = format!("{}", date.format(FORMAT));
140 serializer.serialize_str(&s)
141 }
142
143 pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
144 where
145 D: Deserializer<'de>,
146 {
147 let s = String::deserialize(deserializer)?;
148 NaiveDate::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)
149 }
150}
151
152#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
153pub enum ReportingRequestGroupBy {
154 #[serde(rename = "deviceClass")]
155 DeviceClass,
156 #[serde(rename = "ageRange")]
157 AgeRange,
158 #[serde(rename = "gender")]
159 Gender,
160 #[serde(rename = "countryCode")]
161 CountryCode,
162 #[serde(rename = "adminArea")]
163 AdminArea,
164 #[serde(rename = "locality")]
165 Locality,
166 #[serde(rename = "countryOrRegion")]
167 CountryOrRegion,
168}
169
170#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
171pub enum ReportingRequestTimeZone {
172 #[allow(clippy::upper_case_acronyms)]
173 UTC,
174 #[allow(clippy::upper_case_acronyms)]
175 ORTZ,
176}
177
178#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
179pub enum ReportingRequestGranularity {
180 #[allow(clippy::upper_case_acronyms)]
181 MONTHLY,
182 #[allow(clippy::upper_case_acronyms)]
183 WEEKLY,
184 #[allow(clippy::upper_case_acronyms)]
185 DAILY,
186 #[allow(clippy::upper_case_acronyms)]
187 HOURLY,
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 use std::error;
195
196 use serde_json::Value;
197
198 use crate::objects::{
199 condition::{Condition, ConditionOperator::*},
200 pagination::Pagination,
201 reporting_request::{
202 ReportingRequestGranularity::*, ReportingRequestGroupBy::*, ReportingRequestTimeZone::*,
203 },
204 sorting::{Sorting, SortingSortOrder::*},
205 };
206
207 #[test]
208 fn test_v4_ser_get_campaign_level_reports() -> Result<(), Box<dyn error::Error>> {
209 let mut pagination = Pagination::new();
210 pagination.set_limit(1000).set_offset(0);
211
212 let mut selector = Selector::new(vec![Sorting::new("countryOrRegion", ASCENDING)]);
213 selector
214 .set_conditions(vec![
215 Condition::new("countriesOrRegions", CONTAINS_ANY, vec!["US", "GB"]),
216 Condition::new("countryOrRegion", IN, vec!["US"]),
217 ])
218 .set_pagination(pagination);
219
220 let mut reporting_request = ReportingRequest::new(
221 "2021-04-08".parse().unwrap(),
222 "2021-04-09".parse().unwrap(),
223 selector,
224 );
225 reporting_request
226 .set_group_by(vec![CountryOrRegion])
227 .set_time_zone(UTC)
228 .set_return_records_with_no_metrics(true)
229 .set_return_row_totals(true)
230 .set_return_grand_totals(true);
231
232 let value1: Value = serde_json::to_value(reporting_request)?;
233
234 let json_content =
235 include_str!("../../tests/v4/request_body_json_files/get_campaign_level_reports_payload_example_1.json");
236 let value2: Value = serde_json::from_str(json_content)?;
237
238 assert_eq!(value1, value2);
239
240 Ok(())
241 }
242
243 #[test]
244 fn test_v4_ser_get_campaign_level_reports_with_granularity() -> Result<(), Box<dyn error::Error>>
245 {
246 let mut pagination = Pagination::new();
247 pagination.set_limit(1000).set_offset(0);
248
249 let mut selector = Selector::new(vec![Sorting::new("countryOrRegion", ASCENDING)]);
250 selector
251 .set_conditions(vec![
252 Condition::new("countriesOrRegions", CONTAINS_ANY, vec!["US", "GB"]),
253 Condition::new("countryOrRegion", IN, vec!["US"]),
254 ])
255 .set_pagination(pagination);
256
257 let mut reporting_request = ReportingRequest::new(
258 "2021-04-08".parse().unwrap(),
259 "2021-04-18".parse().unwrap(),
260 selector,
261 );
262 reporting_request
263 .set_group_by(vec![CountryOrRegion])
264 .set_time_zone(UTC)
265 .set_return_records_with_no_metrics(true)
266 .set_return_row_totals(false)
267 .set_granularity(DAILY)
268 .set_return_grand_totals(false);
269
270 let value1: Value = serde_json::to_value(reporting_request)?;
271
272 let json_content = include_str!(
273 "../../tests/v4/request_body_json_files/get_campaign_level_reports_payload_example_3.json",
274 );
275 let value2: Value = serde_json::from_str(json_content)?;
276
277 assert_eq!(value1, value2);
278
279 Ok(())
280 }
281
282 #[test]
283 fn test_v3_ser_get_campaign_level_reports() -> Result<(), Box<dyn error::Error>> {
284 let mut pagination = Pagination::new();
285 pagination.set_limit(1000).set_offset(0);
286
287 let mut selector = Selector::new(vec![Sorting::new("countryOrRegion", ASCENDING)]);
288 selector
289 .set_conditions(vec![
290 Condition::new("countriesOrRegions", CONTAINS_ANY, vec!["US", "GB"]),
291 Condition::new("countryOrRegion", IN, vec!["US"]),
292 ])
293 .set_pagination(pagination);
294
295 let mut reporting_request = ReportingRequest::new(
296 "2020-08-04".parse().unwrap(),
297 "2020-08-14".parse().unwrap(),
298 selector,
299 );
300 reporting_request
301 .set_group_by(vec![CountryOrRegion])
302 .set_time_zone(UTC)
303 .set_return_records_with_no_metrics(true)
304 .set_return_row_totals(true)
305 .set_return_grand_totals(true);
306
307 let value1: Value = serde_json::to_value(reporting_request)?;
308
309 let json_content =
310 include_str!("../../tests/v3/request_body_json_files/get_campaign_level_reports.json");
311 let value2: Value = serde_json::from_str(json_content)?;
312
313 assert_eq!(value1, value2);
314
315 Ok(())
316 }
317
318 #[test]
319 fn test_v3_ser_get_campaign_level_reports_with_granularity() -> Result<(), Box<dyn error::Error>>
320 {
321 let mut pagination = Pagination::new();
322 pagination.set_limit(1000).set_offset(0);
323
324 let mut selector = Selector::new(vec![Sorting::new("countryOrRegion", ASCENDING)]);
325 selector
326 .set_conditions(vec![
327 Condition::new("countriesOrRegions", CONTAINS_ANY, vec!["US", "GB"]),
328 Condition::new("countryOrRegion", IN, vec!["US"]),
329 ])
330 .set_pagination(pagination);
331
332 let mut reporting_request = ReportingRequest::new(
333 "2020-08-04".parse().unwrap(),
334 "2020-08-14".parse().unwrap(),
335 selector,
336 );
337 reporting_request
338 .set_group_by(vec![CountryOrRegion])
339 .set_time_zone(UTC)
340 .set_return_records_with_no_metrics(true)
341 .set_return_row_totals(false)
342 .set_granularity(DAILY)
343 .set_return_grand_totals(false);
344
345 let value1: Value = serde_json::to_value(reporting_request)?;
346
347 let json_content = include_str!(
348 "../../tests/v3/request_body_json_files/get_campaign_level_reports_with_granularity.json",
349 );
350 let value2: Value = serde_json::from_str(json_content)?;
351
352 assert_eq!(value1, value2);
353
354 Ok(())
355 }
356
357 #[test]
358 fn test_v4_de() {
359 match serde_json::from_str::<ReportingRequest>(include_str!("../../tests/v4/request_body_json_files/get_campaign_level_reports_payload_example_1.json")) {
360 Ok(_) => {},
361 Err(err) => panic!("{}", err)
362 }
363
364 match serde_json::from_str::<ReportingRequest>(include_str!("../../tests/v4/request_body_json_files/get_campaign_level_reports_payload_example_2.json")) {
365 Ok(_) => {},
366 Err(err) => panic!("{}", err)
367 }
368
369 match serde_json::from_str::<ReportingRequest>(include_str!("../../tests/v4/request_body_json_files/get_campaign_level_reports_payload_example_3.json")) {
370 Ok(_) => {},
371 Err(err) => panic!("{}", err)
372 }
373 }
374
375 #[test]
376 fn test_v3_de() {
377 match serde_json::from_str::<ReportingRequest>(include_str!("../../tests/v3/request_body_json_files/get_ad_group_level_reports_using_group_by_field_with_geo_values.json")) {
378 Ok(_) => {},
379 Err(err) => panic!("{}", err)
380 }
381
382 match serde_json::from_str::<ReportingRequest>(include_str!(
383 "../../tests/v3/request_body_json_files/get_ad_group_level_reports.json"
384 )) {
385 Ok(_) => {}
386 Err(err) => panic!("{}", err),
387 }
388
389 match serde_json::from_str::<ReportingRequest>(include_str!("../../tests/v3/request_body_json_files/get_campaign_level_reports_with_granularity.json")) {
390 Ok(_) => {},
391 Err(err) => panic!("{}", err)
392 }
393
394 match serde_json::from_str::<ReportingRequest>(include_str!(
395 "../../tests/v3/request_body_json_files/get_campaign_level_reports.json"
396 )) {
397 Ok(_) => {}
398 Err(err) => panic!("{}", err),
399 }
400
401 match serde_json::from_str::<ReportingRequest>(include_str!(
402 "../../tests/v3/request_body_json_files/get_keyword_level_reports.json"
403 )) {
404 Ok(_) => {}
405 Err(err) => panic!("{}", err),
406 }
407
408 match serde_json::from_str::<ReportingRequest>(include_str!(
409 "../../tests/v3/request_body_json_files/get_search_term_level_reports.json"
410 )) {
411 Ok(_) => {}
412 Err(err) => panic!("{}", err),
413 }
414 }
415}