Skip to main content

securitydept_utils/
ser.rs

1use std::borrow::Cow;
2
3use serde::Deserialize;
4use serde_with::DeserializeAs;
5
6/// Deserializes a string into Vec<T> by splitting on comma and/or whitespace.
7/// Used with PickFirst to accept either a delimited string or a sequence
8/// (array).
9pub struct CommaOrSpaceSeparated<T>(std::marker::PhantomData<T>);
10
11impl<'de, T> DeserializeAs<'de, Vec<T>> for CommaOrSpaceSeparated<T>
12where
13    T: serde::de::DeserializeOwned,
14{
15    fn deserialize_as<D>(deserializer: D) -> std::result::Result<Vec<T>, D::Error>
16    where
17        D: serde::Deserializer<'de>,
18    {
19        let s = String::deserialize(deserializer)?;
20        s.split(|c: char| c == ',' || c.is_whitespace())
21            .map(str::trim)
22            .filter(|s| !s.is_empty())
23            .map(try_parse_maybe_json_string::<T, D>)
24            .collect()
25    }
26}
27
28pub struct SpaceSeparated<T>(std::marker::PhantomData<T>);
29
30impl<'de, T> DeserializeAs<'de, Vec<T>> for SpaceSeparated<T>
31where
32    T: serde::de::DeserializeOwned,
33{
34    fn deserialize_as<D>(deserializer: D) -> std::result::Result<Vec<T>, D::Error>
35    where
36        D: serde::Deserializer<'de>,
37    {
38        let s = String::deserialize(deserializer)?;
39        s.split_whitespace()
40            .map(str::trim)
41            .filter(|s| !s.is_empty())
42            .map(try_parse_maybe_json_string::<T, D>)
43            .collect()
44    }
45}
46
47fn try_parse_maybe_json_string<'de, T, D>(s: &str) -> std::result::Result<T, D::Error>
48where
49    T: serde::de::DeserializeOwned,
50    D: serde::Deserializer<'de>,
51{
52    let quoted = if s.starts_with('"') && s.ends_with('"') {
53        Cow::Borrowed(s)
54    } else {
55        Cow::Owned(serde_json::to_string(s).map_err(<D::Error as serde::de::Error>::custom)?)
56    };
57    serde_json::from_str::<T>(quoted.as_ref()).map_err(<D::Error as serde::de::Error>::custom)
58}