Skip to main content

tdm_server_rust/utils/
query_deserialize.rs

1//! Query 参数反序列化辅助 (Query Deserialization)
2//!
3//! 兼容前端传空字符串、NaN、undefined 等非法值,
4//! 对齐 Java/Spring 的参数绑定行为。
5
6use chrono::{DateTime, Utc};
7use serde::{de, Deserialize, Deserializer};
8use std::fmt::Display;
9use std::str::FromStr;
10
11/// 前端占位非法值(NaN / undefined / 空串)视为缺失
12fn 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
79/// 可选字符串,空/空白视为 None(对齐 Java `!= ''`)
80pub 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
92/// 可选 i16,空字符串视为 None
93pub 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
100/// 可选 i32,空字符串视为 None
101pub 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
108/// 页码,缺省或空字符串为 1
109pub 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
116/// 每页条数,缺省或空字符串为 20
117pub 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
124/// 常驻组员分页大小,缺省或空字符串为 5
125pub 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
132/// 必填 i32
133pub fn de_i32<'de, D>(d: D) -> Result<i32, D::Error>
134where
135    D: Deserializer<'de>,
136{
137    required_i32(d, "i32")
138}
139
140/// 必填 i16
141pub fn de_i16<'de, D>(d: D) -> Result<i16, D::Error>
142where
143    D: Deserializer<'de>,
144{
145    required_i16(d, "i16")
146}
147
148/// JSON 可选 i32(兼容数字或字符串)
149pub 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
167/// JSON 可选 i16(兼容数字或字符串)
168pub 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
186/// JSON 必填 i32(兼容数字或字符串,对齐前端 form 字符串字段)
187pub 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
206/// 解析逗号分隔整数列表,空/缺失为 None
207pub 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
215/// 解析逗号分隔字符串列表,空/缺失为 None
216pub 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
240/// 解析逗号分隔职阶列表;连续逗号或字面量 `null` 表示职阶为空
241pub 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
268/// 可选 ISO8601 时间,空/缺失为 None
269pub 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}