1use chrono::{DateTime, Utc};
7use serde::{de, Deserialize, Deserializer};
8use std::fmt::Display;
9use std::str::FromStr;
10
11fn is_invalid_query_token(s: &str) -> bool {
13 let t = s.trim();
14 t.is_empty() || t.eq_ignore_ascii_case("nan") || t.eq_ignore_ascii_case("undefined")
15}
16
17fn empty_as_none<'de, T, D>(deserializer: D, _field: &str) -> Result<Option<T>, D::Error>
18where
19 D: Deserializer<'de>,
20 T: FromStr,
21 T::Err: Display,
22{
23 let raw = Option::<String>::deserialize(deserializer)?;
24 match raw {
25 None => Ok(None),
26 Some(s) if is_invalid_query_token(&s) => Ok(None),
27 Some(s) => s.parse().map(Some).map_err(de::Error::custom),
28 }
29}
30
31fn empty_as_i32_default<'de, D>(
32 deserializer: D,
33 default: i32,
34 _field: &str,
35) -> Result<i32, D::Error>
36where
37 D: Deserializer<'de>,
38{
39 let raw = Option::<String>::deserialize(deserializer)?;
40 match raw {
41 None => Ok(default),
42 Some(s) if s.trim().is_empty() => Ok(default),
43 Some(s) => s.parse().map_err(de::Error::custom),
44 }
45}
46
47fn required_i32<'de, D>(deserializer: D, field: &str) -> Result<i32, D::Error>
48where
49 D: Deserializer<'de>,
50{
51 let raw = Option::<String>::deserialize(deserializer)?;
52 match raw {
53 None => Err(de::Error::custom(format!(
54 "missing query parameter `{field}`"
55 ))),
56 Some(s) if s.trim().is_empty() => Err(de::Error::custom(format!(
57 "empty query parameter `{field}`"
58 ))),
59 Some(s) => s.parse().map_err(de::Error::custom),
60 }
61}
62
63fn required_i16<'de, D>(deserializer: D, field: &str) -> Result<i16, D::Error>
64where
65 D: Deserializer<'de>,
66{
67 let raw = Option::<String>::deserialize(deserializer)?;
68 match raw {
69 None => Err(de::Error::custom(format!(
70 "missing query parameter `{field}`"
71 ))),
72 Some(s) if s.trim().is_empty() => Err(de::Error::custom(format!(
73 "empty query parameter `{field}`"
74 ))),
75 Some(s) => s.parse().map_err(de::Error::custom),
76 }
77}
78
79pub fn de_opt_string<'de, D>(d: D) -> Result<Option<String>, D::Error>
81where
82 D: Deserializer<'de>,
83{
84 let raw = Option::<String>::deserialize(d)?;
85 Ok(match raw {
86 None => None,
87 Some(s) if s.trim().is_empty() => None,
88 Some(s) => Some(s),
89 })
90}
91
92pub fn de_opt_i16<'de, D>(d: D) -> Result<Option<i16>, D::Error>
94where
95 D: Deserializer<'de>,
96{
97 empty_as_none(d, "i16")
98}
99
100pub fn de_opt_i32<'de, D>(d: D) -> Result<Option<i32>, D::Error>
102where
103 D: Deserializer<'de>,
104{
105 empty_as_none(d, "i32")
106}
107
108pub fn de_page<'de, D>(d: D) -> Result<i32, D::Error>
110where
111 D: Deserializer<'de>,
112{
113 empty_as_i32_default(d, 1, "page")
114}
115
116pub fn de_page_size<'de, D>(d: D) -> Result<i32, D::Error>
118where
119 D: Deserializer<'de>,
120{
121 empty_as_i32_default(d, 20, "pageSize")
122}
123
124pub fn de_station_page_size<'de, D>(d: D) -> Result<i32, D::Error>
126where
127 D: Deserializer<'de>,
128{
129 empty_as_i32_default(d, 5, "pageSize")
130}
131
132pub fn de_i32<'de, D>(d: D) -> Result<i32, D::Error>
134where
135 D: Deserializer<'de>,
136{
137 required_i32(d, "i32")
138}
139
140pub fn de_i16<'de, D>(d: D) -> Result<i16, D::Error>
142where
143 D: Deserializer<'de>,
144{
145 required_i16(d, "i16")
146}
147
148pub fn de_opt_i32_loose<'de, D>(d: D) -> Result<Option<i32>, D::Error>
150where
151 D: Deserializer<'de>,
152{
153 #[derive(Deserialize)]
154 #[serde(untagged)]
155 enum NumOrStr {
156 Num(i32),
157 Str(String),
158 }
159 match Option::<NumOrStr>::deserialize(d)? {
160 None => Ok(None),
161 Some(NumOrStr::Num(v)) => Ok(Some(v)),
162 Some(NumOrStr::Str(s)) if s.trim().is_empty() => Ok(None),
163 Some(NumOrStr::Str(s)) => s.parse().map(Some).map_err(de::Error::custom),
164 }
165}
166
167pub fn de_opt_i16_loose<'de, D>(d: D) -> Result<Option<i16>, D::Error>
169where
170 D: Deserializer<'de>,
171{
172 #[derive(Deserialize)]
173 #[serde(untagged)]
174 enum NumOrStr {
175 Num(i16),
176 Str(String),
177 }
178 match Option::<NumOrStr>::deserialize(d)? {
179 None => Ok(None),
180 Some(NumOrStr::Num(v)) => Ok(Some(v)),
181 Some(NumOrStr::Str(s)) if s.trim().is_empty() => Ok(None),
182 Some(NumOrStr::Str(s)) => s.parse().map(Some).map_err(de::Error::custom),
183 }
184}
185
186pub fn de_i32_loose<'de, D>(d: D) -> Result<i32, D::Error>
188where
189 D: Deserializer<'de>,
190{
191 #[derive(Deserialize)]
192 #[serde(untagged)]
193 enum NumOrStr {
194 Num(i32),
195 Str(String),
196 }
197 match NumOrStr::deserialize(d)? {
198 NumOrStr::Num(v) => Ok(v),
199 NumOrStr::Str(s) if s.trim().is_empty() => {
200 Err(de::Error::custom("required i32 field is empty"))
201 }
202 NumOrStr::Str(s) => s.parse().map_err(de::Error::custom),
203 }
204}
205
206pub fn de_opt_csv_i32<'de, D>(d: D) -> Result<Option<Vec<i32>>, D::Error>
208where
209 D: Deserializer<'de>,
210{
211 let raw = Option::<String>::deserialize(d)?;
212 Ok(parse_csv_i32(raw.as_deref()))
213}
214
215pub fn de_opt_csv_string<'de, D>(d: D) -> Result<Option<Vec<String>>, D::Error>
217where
218 D: Deserializer<'de>,
219{
220 let raw = Option::<String>::deserialize(d)?;
221 let Some(s) = raw else {
222 return Ok(None);
223 };
224 if is_invalid_query_token(&s) {
225 return Ok(None);
226 }
227 let items: Vec<String> = s
228 .split(',')
229 .map(str::trim)
230 .filter(|p| !p.is_empty())
231 .map(str::to_string)
232 .collect();
233 if items.is_empty() {
234 Ok(None)
235 } else {
236 Ok(Some(items))
237 }
238}
239
240pub fn de_opt_csv_intern<'de, D>(d: D) -> Result<Option<Vec<Option<i32>>>, D::Error>
242where
243 D: Deserializer<'de>,
244{
245 let raw = Option::<String>::deserialize(d)?;
246 let Some(s) = raw else {
247 return Ok(None);
248 };
249 if is_invalid_query_token(&s) {
250 return Ok(None);
251 }
252 let mut items = Vec::new();
253 for part in s.split(',') {
254 let t = part.trim();
255 if t.is_empty() || t.eq_ignore_ascii_case("null") {
256 items.push(None);
257 } else {
258 items.push(Some(t.parse().map_err(de::Error::custom)?));
259 }
260 }
261 if items.is_empty() {
262 Ok(None)
263 } else {
264 Ok(Some(items))
265 }
266}
267
268pub fn de_opt_datetime<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error>
270where
271 D: Deserializer<'de>,
272{
273 let raw = Option::<String>::deserialize(d)?;
274 let Some(s) = raw else {
275 return Ok(None);
276 };
277 if is_invalid_query_token(&s) {
278 return Ok(None);
279 }
280 DateTime::parse_from_rfc3339(&s)
281 .map(|dt| Some(dt.with_timezone(&Utc)))
282 .map_err(de::Error::custom)
283}
284
285fn parse_csv_i32(raw: Option<&str>) -> Option<Vec<i32>> {
286 let s = raw?;
287 if is_invalid_query_token(s) {
288 return None;
289 }
290 let items: Vec<i32> = s
291 .split(',')
292 .map(str::trim)
293 .filter(|p| !p.is_empty())
294 .filter_map(|p| p.parse().ok())
295 .collect();
296 if items.is_empty() {
297 None
298 } else {
299 Some(items)
300 }
301}