serde_helpers/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Serde de/serialization helpers. To use one of these helpers, use the `with`
5//! serde attribute: `#[serde(with = "serde_helpers::foo")]`
6
7#![expect(missing_docs)]
8#![forbid(unsafe_code)]
9
10mod prelude {
11    pub use serde::Deserialize;
12    pub use serde::Deserializer;
13    pub use serde::Serialize;
14    pub use serde::Serializer;
15}
16
17/// de/serialize a `Vec<u8>` to/from a base64 encoded string.
18pub mod base64_vec {
19    use crate::prelude::*;
20    use base64::Engine;
21
22    pub fn serialize<S: Serializer>(v: &Vec<u8>, ser: S) -> Result<S::Ok, S::Error> {
23        ser.serialize_str(&base64::engine::general_purpose::STANDARD.encode(v))
24    }
25
26    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
27        let s: &str = Deserialize::deserialize(d)?;
28        base64::engine::general_purpose::STANDARD
29            .decode(s)
30            .map_err(serde::de::Error::custom)
31    }
32}
33
34/// de/serialize a `T: Display + FromStr`
35pub mod as_string {
36    use crate::prelude::*;
37    use std::fmt::Display;
38    use std::str::FromStr;
39
40    pub fn serialize<T: Display, S: Serializer>(value: &T, ser: S) -> Result<S::Ok, S::Error> {
41        ser.collect_str(value)
42    }
43
44    pub fn deserialize<'de, T, D: Deserializer<'de>>(d: D) -> Result<T, D::Error>
45    where
46        T: FromStr,
47        T::Err: std::error::Error,
48    {
49        let s: &str = Deserialize::deserialize(d)?;
50        s.parse().map_err(serde::de::Error::custom)
51    }
52}
53
54/// serialize a `T: Debug`
55pub mod as_debug {
56    use crate::prelude::*;
57    use std::fmt::Debug;
58
59    pub fn serialize<T: Debug, S: Serializer>(value: &T, ser: S) -> Result<S::Ok, S::Error> {
60        ser.collect_str(&format_args!("{:?}", value))
61    }
62}
63
64/// de/serialize an [`Option<Guid>`](guid::Guid) to/from a possibly-missing GUID
65/// string. Make sure to also specify `#[serde(default)]`.
66pub mod opt_guid_str {
67    use crate::prelude::*;
68    use guid::Guid;
69
70    pub fn serialize<S: Serializer>(guid: &Option<Guid>, ser: S) -> Result<S::Ok, S::Error> {
71        match guid {
72            Some(guid) => ser.serialize_some(&guid.to_string()),
73            None => ser.serialize_none(),
74        }
75    }
76
77    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Guid>, D::Error> {
78        let s: Option<&str> = Deserialize::deserialize(d)?;
79        Ok(match s {
80            None => None,
81            Some(s) => Some(s.parse().map_err(serde::de::Error::custom)?),
82        })
83    }
84}
85
86/// de/serialize an `Option<T>` (where `T` implements is de/serializable)
87/// to/from a `base64` encoded string containing a JSON payload.
88/// Make sure to also specify `#[serde(default)]`.
89pub mod opt_base64_json {
90    use crate::prelude::*;
91    use base64::Engine;
92
93    pub fn serialize<S: Serializer, T: Serialize>(
94        t: &Option<T>,
95        ser: S,
96    ) -> Result<S::Ok, S::Error> {
97        match t {
98            Some(t) => ser.serialize_str(
99                &base64::engine::general_purpose::STANDARD
100                    .encode(serde_json::to_string(t).map_err(serde::ser::Error::custom)?),
101            ),
102            None => ser.serialize_none(),
103        }
104    }
105    pub fn deserialize<'de, D: Deserializer<'de>, T: serde::de::DeserializeOwned>(
106        d: D,
107    ) -> Result<Option<T>, D::Error> {
108        let s: Option<&str> = Deserialize::deserialize(d)?;
109        Ok(match s {
110            None => None,
111            Some(s) => {
112                let s = base64::engine::general_purpose::STANDARD
113                    .decode(s)
114                    .map_err(serde::de::Error::custom)?;
115                Some(serde_json::from_slice(&s).map_err(serde::de::Error::custom)?)
116            }
117        })
118    }
119}
120
121pub mod opt_base64_vec {
122    use base64::Engine;
123    use serde::*;
124
125    pub fn serialize<S: Serializer>(v: &Option<Vec<u8>>, ser: S) -> Result<S::Ok, S::Error> {
126        match v {
127            Some(v) => ser.serialize_str(&base64::engine::general_purpose::STANDARD.encode(v)),
128            None => ser.serialize_none(),
129        }
130    }
131
132    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<u8>>, D::Error> {
133        let s: Option<&str> = Deserialize::deserialize(d)?;
134        Ok(match s {
135            Some(s) => Some(
136                base64::engine::general_purpose::STANDARD
137                    .decode(s)
138                    .map_err(de::Error::custom)?,
139            ),
140            None => None,
141        })
142    }
143}
144
145pub mod vec_base64_vec {
146    use base64::Engine;
147    use ser::SerializeSeq;
148    use serde::*;
149
150    pub fn serialize<S: Serializer>(v: &Vec<Vec<u8>>, ser: S) -> Result<S::Ok, S::Error> {
151        let mut seq = ser.serialize_seq(Some(v.len()))?;
152        for element in v {
153            seq.serialize_element(&base64::engine::general_purpose::STANDARD.encode(element))?;
154        }
155        seq.end()
156    }
157
158    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Vec<u8>>, D::Error> {
159        let ss: Vec<&str> = Deserialize::deserialize(d)?;
160        let mut vs = Vec::new();
161        for s in ss {
162            vs.push(
163                base64::engine::general_purpose::STANDARD
164                    .decode(s)
165                    .map_err(de::Error::custom)?,
166            );
167        }
168        Ok(vs)
169    }
170}
171
172#[cfg(test)]
173mod test {
174    use super::*;
175    use guid::Guid;
176    use serde::Deserialize;
177
178    #[test]
179    fn opt_guid() {
180        #[derive(Deserialize)]
181        struct OptGuidSample {
182            field1: u32,
183            #[serde(default)]
184            #[serde(with = "opt_guid_str")]
185            field2: Option<Guid>,
186        }
187
188        let json = r#"
189        {
190            "field1": 123,
191            "field2": "12345678-9abc-def0-1234-56789abcdef0"
192        }"#;
193        let guid_sample: OptGuidSample = serde_json::from_str(json).expect("deserialization");
194        assert_eq!(guid_sample.field1, 123);
195        assert_eq!(
196            guid_sample.field2.expect("field2 set"),
197            guid::guid!("12345678-9abc-def0-1234-56789abcdef0")
198        );
199
200        let json = r#"
201        {
202            "field1": 123
203        }"#;
204        let guid_sample: OptGuidSample = serde_json::from_str(json).expect("deserialization");
205        assert_eq!(guid_sample.field1, 123);
206        assert!(guid_sample.field2.is_none());
207    }
208}