securitydept_utils/
ser.rs1use std::borrow::Cow;
2
3use serde::{Deserialize, Serialize};
4use serde_with::{DeserializeAs, SerializeAs};
5
6pub 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
28impl<T> SerializeAs<Vec<T>> for CommaOrSpaceSeparated<T>
29where
30 T: Serialize,
31{
32 fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
33 where
34 S: serde::Serializer,
35 {
36 serialize_delimited_vec(source, " ", serializer)
37 }
38}
39
40pub struct SpaceSeparated<T>(std::marker::PhantomData<T>);
41
42impl<'de, T> DeserializeAs<'de, Vec<T>> for SpaceSeparated<T>
43where
44 T: serde::de::DeserializeOwned,
45{
46 fn deserialize_as<D>(deserializer: D) -> std::result::Result<Vec<T>, D::Error>
47 where
48 D: serde::Deserializer<'de>,
49 {
50 let s = String::deserialize(deserializer)?;
51 s.split_whitespace()
52 .map(str::trim)
53 .filter(|s| !s.is_empty())
54 .map(try_parse_maybe_json_string::<T, D>)
55 .collect()
56 }
57}
58
59impl<T> SerializeAs<Vec<T>> for SpaceSeparated<T>
60where
61 T: Serialize,
62{
63 fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
64 where
65 S: serde::Serializer,
66 {
67 serialize_delimited_vec(source, " ", serializer)
68 }
69}
70
71#[cfg(feature = "config-schema")]
72impl<T> serde_with::schemars_1::JsonSchemaAs<Vec<T>> for CommaOrSpaceSeparated<T> {
73 fn schema_name() -> Cow<'static, str> {
74 Cow::Borrowed("String")
75 }
76
77 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
78 <String as schemars::JsonSchema>::json_schema(generator)
79 }
80}
81
82#[cfg(feature = "config-schema")]
83impl<T> serde_with::schemars_1::JsonSchemaAs<Vec<T>> for SpaceSeparated<T> {
84 fn schema_name() -> Cow<'static, str> {
85 Cow::Borrowed("String")
86 }
87
88 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
89 <String as schemars::JsonSchema>::json_schema(generator)
90 }
91}
92
93fn serialize_delimited_vec<T, S>(
94 source: &[T],
95 delimiter: &str,
96 serializer: S,
97) -> Result<S::Ok, S::Error>
98where
99 T: Serialize,
100 S: serde::Serializer,
101{
102 let mut serialized_parts = Vec::with_capacity(source.len());
103
104 for item in source {
105 serialized_parts
106 .push(serde_json::to_string(item).map_err(<S::Error as serde::ser::Error>::custom)?);
107 }
108
109 serializer.serialize_str(&serialized_parts.join(delimiter))
110}
111
112fn try_parse_maybe_json_string<'de, T, D>(s: &str) -> std::result::Result<T, D::Error>
113where
114 T: serde::de::DeserializeOwned,
115 D: serde::Deserializer<'de>,
116{
117 let quoted = if s.starts_with('"') && s.ends_with('"') {
118 Cow::Borrowed(s)
119 } else {
120 Cow::Owned(serde_json::to_string(s).map_err(<D::Error as serde::de::Error>::custom)?)
121 };
122 serde_json::from_str::<T>(quoted.as_ref()).map_err(<D::Error as serde::de::Error>::custom)
123}