other_pocket/
serialization.rs

1use chrono::{DateTime, TimeZone, Utc};
2use mime::Mime;
3use serde::de::{DeserializeOwned, Unexpected};
4use serde::{Deserialize, Deserializer, Serializer};
5use serde_json::Value;
6use std::collections::BTreeMap;
7use std::fmt::Display;
8use std::result::Result;
9use std::str::FromStr;
10use url::Url;
11
12pub fn option_from_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
13where
14    T: FromStr,
15    T::Err: Display,
16    D: Deserializer<'de>,
17{
18    let result: Result<T, D::Error> = from_str(deserializer);
19    Ok(result.ok())
20}
21
22// https://github.com/serde-rs/json/issues/317
23pub fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
24where
25    T: FromStr,
26    T::Err: Display,
27    D: Deserializer<'de>,
28{
29    let s = String::deserialize(deserializer)?;
30    T::from_str(&s).map_err(serde::de::Error::custom)
31}
32
33pub fn optional_to_string<T, S>(x: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
34where
35    T: ToString,
36    S: Serializer,
37{
38    match x {
39        Some(ref value) => to_string(value, serializer),
40        None => serializer.serialize_none(),
41    }
42}
43
44pub fn to_string<T, S>(x: &T, serializer: S) -> Result<S::Ok, S::Error>
45where
46    T: ToString,
47    S: Serializer,
48{
49    serializer.serialize_str(&x.to_string())
50}
51
52pub fn to_comma_delimited_string<S>(x: &Option<&[&str]>, serializer: S) -> Result<S::Ok, S::Error>
53where
54    S: Serializer,
55{
56    match x {
57        Some(value) => serializer.serialize_str(&value.join(",")),
58        None => serializer.serialize_none(),
59    }
60}
61
62pub fn try_url_from_string<'de, D>(deserializer: D) -> Result<Option<Url>, D::Error>
63where
64    D: Deserializer<'de>,
65{
66    let o: Option<String> = Option::deserialize(deserializer)?;
67    Ok(o.and_then(|s| Url::parse(&s).ok()))
68}
69
70pub fn optional_vec_from_map<'de, T, D>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
71where
72    T: DeserializeOwned + Clone + std::fmt::Debug,
73    D: Deserializer<'de>,
74{
75    let o: Option<Value> = Option::deserialize(deserializer)?;
76    match o {
77        Some(v) => json_value_to_vec::<T, D>(v).map(Some),
78        None => Ok(None),
79    }
80}
81
82pub fn vec_from_map<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
83where
84    T: DeserializeOwned + Clone + std::fmt::Debug,
85    D: Deserializer<'de>,
86{
87    let value = Value::deserialize(deserializer)?;
88    json_value_to_vec::<T, D>(value)
89}
90
91pub fn json_value_to_vec<'de, T, D>(value: Value) -> Result<Vec<T>, D::Error>
92where
93    T: DeserializeOwned + Clone + std::fmt::Debug,
94    D: Deserializer<'de>,
95{
96    match value {
97        a @ Value::Array(..) => {
98            serde_json::from_value::<Vec<T>>(a).map_err(serde::de::Error::custom)
99        }
100        o @ Value::Object(..) => serde_json::from_value::<BTreeMap<String, T>>(o)
101            .map(map_to_vec)
102            .map_err(serde::de::Error::custom),
103        other => Err(serde::de::Error::invalid_value(
104            Unexpected::Other(format!("{:?}", other).as_str()),
105            &"object or array",
106        )),
107    }
108}
109
110pub fn map_to_vec<T>(map: BTreeMap<String, T>) -> Vec<T> {
111    map.into_iter().map(|(_, v)| v).collect::<Vec<_>>()
112}
113
114// https://github.com/serde-rs/serde/issues/1344
115pub fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
116where
117    D: Deserializer<'de>,
118{
119    match u8::deserialize(deserializer)? {
120        0 => Ok(false),
121        1 => Ok(true),
122        other => Err(serde::de::Error::invalid_value(
123            Unexpected::Unsigned(other as u64),
124            &"zero or one",
125        )),
126    }
127}
128
129pub fn bool_from_int_string<'de, D>(deserializer: D) -> Result<bool, D::Error>
130where
131    D: Deserializer<'de>,
132{
133    match String::deserialize(deserializer)?.as_str() {
134        "0" => Ok(false),
135        "1" => Ok(true),
136        other => Err(serde::de::Error::invalid_value(
137            Unexpected::Str(other),
138            &"zero or one",
139        )),
140    }
141}
142
143#[allow(clippy::trivially_copy_pass_by_ref)]
144pub fn optional_bool_to_int<S>(x: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
145where
146    S: Serializer,
147{
148    match x {
149        Some(ref value) => bool_to_int(value, serializer),
150        None => serializer.serialize_none(),
151    }
152}
153
154pub fn optional_datetime_to_int<S>(
155    x: &Option<DateTime<Utc>>,
156    serializer: S,
157) -> Result<S::Ok, S::Error>
158where
159    S: Serializer,
160{
161    match x {
162        Some(ref value) => string_date_unix_timestamp_format::serialize(value, serializer),
163        None => serializer.serialize_none(),
164    }
165}
166
167pub fn untagged_to_str<S>(serializer: S) -> Result<S::Ok, S::Error>
168where
169    S: Serializer,
170{
171    serializer.serialize_str("_untagged_")
172}
173
174pub fn option_mime_from_string<'de, D>(deserializer: D) -> Result<Option<Mime>, D::Error>
175where
176    D: Deserializer<'de>,
177{
178    Option::deserialize(deserializer).and_then(|o: Option<String>| match o.as_deref() {
179        Some("") | None => Ok(None),
180        Some(str) => str.parse::<Mime>().map(Some).map_err(|other| {
181            serde::de::Error::invalid_value(
182                Unexpected::Other(format!("{:?}", other).as_str()),
183                &"valid mime type",
184            )
185        }),
186    })
187}
188
189pub fn int_date_unix_timestamp_format<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
190where
191    D: Deserializer<'de>,
192{
193    let unix_timestamp = i64::deserialize(deserializer)?;
194    Ok(Utc.timestamp(unix_timestamp, 0))
195}
196
197pub fn option_string_date_unix_timestamp_format<'de, D>(
198    deserializer: D,
199) -> Result<Option<DateTime<Utc>>, D::Error>
200where
201    D: Deserializer<'de>,
202{
203    Option::deserialize(deserializer).and_then(|o: Option<String>| match o.as_deref() {
204        Some("0") | None => Ok(None),
205        Some(str) => str
206            .parse::<i64>()
207            .map(|i| Some(Utc.timestamp(i, 0)))
208            .map_err(serde::de::Error::custom),
209    })
210}
211
212pub const FORMAT: &str = "%Y-%m-%d %H:%M:%S";
213
214pub fn option_string_date_format<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
215where
216    D: Deserializer<'de>,
217{
218    match String::deserialize(deserializer)?.as_str() {
219        "0000-00-00 00:00:00" => Ok(None),
220        str => Utc
221            .datetime_from_str(str, FORMAT)
222            .map_err(serde::de::Error::custom)
223            .map(Option::Some),
224    }
225}
226
227// inspired by https://serde.rs/custom-date-format.html
228pub mod string_date_unix_timestamp_format {
229    use chrono::{DateTime, TimeZone, Utc};
230    use serde::{self, Deserialize, Deserializer, Serializer};
231
232    pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
233    where
234        S: Serializer,
235    {
236        serializer.serialize_str(&date.timestamp().to_string())
237    }
238
239    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
240    where
241        D: Deserializer<'de>,
242    {
243        let s = String::deserialize(deserializer)?;
244        s.parse::<i64>()
245            .map(|i| Utc.timestamp(i, 0))
246            .map_err(serde::de::Error::custom)
247    }
248}
249
250#[allow(clippy::trivially_copy_pass_by_ref)]
251pub fn bool_to_int<S>(x: &bool, serializer: S) -> Result<S::Ok, S::Error>
252where
253    S: Serializer,
254{
255    let output = match x {
256        true => "1",
257        false => "0",
258    };
259    serializer.serialize_str(output)
260}
261
262pub fn borrow_url<S>(x: &Url, serializer: S) -> Result<S::Ok, S::Error>
263where
264    S: Serializer,
265{
266    serializer.serialize_str(x.as_str())
267}
268
269pub fn true_to_unit_variant<'de, D>(deserializer: D) -> Result<(), D::Error>
270where
271    D: Deserializer<'de>,
272{
273    if bool::deserialize(deserializer)? {
274        Ok(())
275    } else {
276        Err(serde::de::Error::invalid_value(
277            Unexpected::Bool(false),
278            &r#"true"#,
279        ))
280    }
281}
282
283pub fn false_to_unit_variant<'de, D>(deserializer: D) -> Result<(), D::Error>
284where
285    D: Deserializer<'de>,
286{
287    if !bool::deserialize(deserializer)? {
288        Ok(())
289    } else {
290        Err(serde::de::Error::invalid_value(
291            Unexpected::Bool(false),
292            &r#"false"#,
293        ))
294    }
295}