1use std::num::NonZeroU32;
2
3use serde::Serialize;
4
5use crate::error::GieError;
6
7use super::date_range::DateRange;
8use super::serde_ext::{serialize_optional_dataset_type, serialize_optional_date};
9use super::types::{DatasetType, DateFilter, GieDate, format_date, parse_dataset_type, parse_date};
10
11#[derive(Debug, Clone, Default)]
13pub struct GieQuery {
14 country: Option<String>,
15 company: Option<String>,
16 facility: Option<String>,
17 dataset_type: Option<DatasetType>,
18 date_filter: Option<DateFilter>,
19 page: Option<NonZeroU32>,
20 size: Option<NonZeroU32>,
21}
22
23impl GieQuery {
24 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn country(mut self, country: impl Into<String>) -> Self {
31 self.country = Some(country.into());
32 self
33 }
34
35 pub fn company(mut self, company: impl Into<String>) -> Self {
37 self.company = Some(company.into());
38 self
39 }
40
41 pub fn facility(mut self, facility: impl Into<String>) -> Self {
43 self.facility = Some(facility.into());
44 self
45 }
46
47 pub fn dataset_type(mut self, dataset_type: DatasetType) -> Self {
49 self.dataset_type = Some(dataset_type);
50 self
51 }
52
53 pub fn try_dataset_type(mut self, dataset_type: impl AsRef<str>) -> Result<Self, GieError> {
55 self.dataset_type = Some(
56 parse_dataset_type(dataset_type.as_ref()).map_err(GieError::InvalidDatasetTypeInput)?,
57 );
58 Ok(self)
59 }
60
61 pub fn without_dataset_type(mut self) -> Self {
63 self.dataset_type = None;
64 self
65 }
66
67 pub fn date(mut self, date: GieDate) -> Self {
69 self.date_filter = Some(DateFilter::Day(date));
70 self
71 }
72
73 pub fn try_date(mut self, date: impl AsRef<str>) -> Result<Self, GieError> {
75 self.date_filter = Some(DateFilter::Day(
76 parse_date(date.as_ref()).map_err(GieError::InvalidDateInput)?,
77 ));
78 Ok(self)
79 }
80
81 pub fn range(mut self, from: GieDate, to: GieDate) -> Result<Self, GieError> {
83 let range = DateRange::new(from, to)?;
84 self.date_filter = Some(DateFilter::Range(range));
85 Ok(self)
86 }
87
88 pub fn try_range(self, from: impl AsRef<str>, to: impl AsRef<str>) -> Result<Self, GieError> {
90 let from = parse_date(from.as_ref()).map_err(GieError::InvalidDateInput)?;
91 let to = parse_date(to.as_ref()).map_err(GieError::InvalidDateInput)?;
92 self.range(from, to)
93 }
94
95 pub fn page(mut self, page: NonZeroU32) -> Self {
97 self.page = Some(page);
98 self
99 }
100
101 pub fn try_page(mut self, page: u32) -> Result<Self, GieError> {
103 self.page = Some(NonZeroU32::new(page).ok_or_else(|| {
104 GieError::InvalidPageInput("page must be greater than zero".to_string())
105 })?);
106 Ok(self)
107 }
108
109 pub fn size(mut self, size: NonZeroU32) -> Self {
111 self.size = Some(size);
112 self
113 }
114
115 pub fn try_size(mut self, size: u32) -> Result<Self, GieError> {
117 self.size = Some(NonZeroU32::new(size).ok_or_else(|| {
118 GieError::InvalidSizeInput("size must be greater than zero".to_string())
119 })?);
120 Ok(self)
121 }
122
123 pub(crate) fn initial_page(&self) -> NonZeroU32 {
124 self.page.unwrap_or_else(default_page)
125 }
126
127 pub(crate) fn as_params_with_page(
128 &self,
129 page_override: Option<NonZeroU32>,
130 ) -> GieQueryParams<'_> {
131 let (date, from, to) = match self.date_filter {
132 Some(DateFilter::Day(value)) => (Some(value), None, None),
133 Some(DateFilter::Range(value)) => (None, Some(value.from()), Some(value.to())),
134 None => (None, None, None),
135 };
136
137 GieQueryParams {
138 country: self.country.as_deref(),
139 company: self.company.as_deref(),
140 facility: self.facility.as_deref(),
141 dataset_type: self.dataset_type,
142 date,
143 from,
144 to,
145 page: page_override.or(self.page),
146 size: self.size,
147 }
148 }
149
150 pub(crate) fn to_debug_pairs(
151 &self,
152 page_override: Option<NonZeroU32>,
153 ) -> Vec<(&'static str, String)> {
154 let mut pairs = Vec::new();
155
156 if let Some(value) = &self.country {
157 pairs.push(("country", value.clone()));
158 }
159 if let Some(value) = &self.company {
160 pairs.push(("company", value.clone()));
161 }
162 if let Some(value) = &self.facility {
163 pairs.push(("facility", value.clone()));
164 }
165 if let Some(value) = self.dataset_type {
166 pairs.push(("type", value.to_string()));
167 }
168
169 match self.date_filter {
170 Some(DateFilter::Day(value)) => pairs.push(("date", format_date(value))),
171 Some(DateFilter::Range(value)) => {
172 pairs.push(("from", format_date(value.from())));
173 pairs.push(("to", format_date(value.to())));
174 }
175 None => {}
176 }
177
178 if let Some(value) = page_override.or(self.page) {
179 pairs.push(("page", value.to_string()));
180 }
181 if let Some(value) = self.size {
182 pairs.push(("size", value.to_string()));
183 }
184
185 pairs
186 }
187}
188
189fn default_page() -> NonZeroU32 {
190 NonZeroU32::new(1).expect("1 is non-zero")
191}
192
193#[derive(Debug, Serialize)]
194pub(crate) struct GieQueryParams<'a> {
195 #[serde(skip_serializing_if = "Option::is_none")]
196 country: Option<&'a str>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 company: Option<&'a str>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 facility: Option<&'a str>,
201 #[serde(
202 rename = "type",
203 skip_serializing_if = "Option::is_none",
204 serialize_with = "serialize_optional_dataset_type"
205 )]
206 dataset_type: Option<DatasetType>,
207 #[serde(
208 skip_serializing_if = "Option::is_none",
209 serialize_with = "serialize_optional_date"
210 )]
211 date: Option<GieDate>,
212 #[serde(
213 skip_serializing_if = "Option::is_none",
214 serialize_with = "serialize_optional_date"
215 )]
216 from: Option<GieDate>,
217 #[serde(
218 skip_serializing_if = "Option::is_none",
219 serialize_with = "serialize_optional_date"
220 )]
221 to: Option<GieDate>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 page: Option<NonZeroU32>,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 size: Option<NonZeroU32>,
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 fn test_date(value: &str) -> GieDate {
233 parse_date(value).unwrap()
234 }
235
236 #[test]
237 fn query_params_are_mapped_to_expected_keys() {
238 let query = GieQuery::new()
239 .country("DE")
240 .company("Comp")
241 .facility("Fac")
242 .dataset_type(DatasetType::Eu)
243 .range(test_date("2026-03-01"), test_date("2026-03-10"))
244 .unwrap()
245 .try_page(2)
246 .unwrap()
247 .try_size(50)
248 .unwrap();
249
250 let pairs = query.to_debug_pairs(None);
251 assert!(pairs.contains(&("country", "DE".to_string())));
252 assert!(pairs.contains(&("company", "Comp".to_string())));
253 assert!(pairs.contains(&("facility", "Fac".to_string())));
254 assert!(pairs.contains(&("type", "eu".to_string())));
255 assert!(pairs.contains(&("from", "2026-03-01".to_string())));
256 assert!(pairs.contains(&("to", "2026-03-10".to_string())));
257 assert!(pairs.contains(&("page", "2".to_string())));
258 assert!(pairs.contains(&("size", "50".to_string())));
259 assert!(!pairs.iter().any(|(key, _)| *key == "date"));
260 }
261
262 #[test]
263 fn date_builder_replaces_range_filter() {
264 let query = GieQuery::new()
265 .range(test_date("2026-03-01"), test_date("2026-03-10"))
266 .unwrap()
267 .date(test_date("2026-03-10"));
268
269 let pairs = query.to_debug_pairs(None);
270 assert!(pairs.contains(&("date", "2026-03-10".to_string())));
271 assert!(!pairs.iter().any(|(key, _)| *key == "from"));
272 assert!(!pairs.iter().any(|(key, _)| *key == "to"));
273 }
274
275 #[test]
276 fn try_range_rejects_invalid_order() {
277 let error = GieQuery::new()
278 .try_range("2026-03-10", "2026-03-01")
279 .unwrap_err();
280
281 assert!(matches!(error, GieError::InvalidDateRangeInput(_)));
282 }
283
284 #[test]
285 fn try_date_rejects_invalid_input() {
286 let error = GieQuery::new().try_date("2026/03/10").unwrap_err();
287 assert!(matches!(error, GieError::InvalidDateInput(_)));
288 }
289
290 #[test]
291 fn try_dataset_type_parses_supported_values() {
292 let query = GieQuery::new().try_dataset_type("NE").unwrap();
293 let pairs = query.to_debug_pairs(None);
294
295 assert!(pairs.contains(&("type", "ne".to_string())));
296 }
297
298 #[test]
299 fn try_dataset_type_rejects_invalid_input() {
300 let error = GieQuery::new().try_dataset_type("country").unwrap_err();
301 assert!(matches!(error, GieError::InvalidDatasetTypeInput(_)));
302 }
303
304 #[test]
305 fn try_page_and_try_size_reject_zero() {
306 assert!(matches!(
307 GieQuery::new().try_page(0).unwrap_err(),
308 GieError::InvalidPageInput(_)
309 ));
310 assert!(matches!(
311 GieQuery::new().try_size(0).unwrap_err(),
312 GieError::InvalidSizeInput(_)
313 ));
314 }
315
316 #[test]
317 fn initial_page_defaults_to_one() {
318 assert_eq!(GieQuery::new().initial_page().get(), 1);
319 }
320}