Skip to content

Commit 21ac5f9

Browse files
committed
feat(rust): custom serde deserializers (wip)
1 parent d48e800 commit 21ac5f9

File tree

8 files changed

+614
-1
lines changed

8 files changed

+614
-1
lines changed

schema_salad/rust/salad-core/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ compact_str = { version = "0.9" }
1212
fxhash = { version = "0.2" }
1313
proc-macro2 = { version = "1.0" }
1414
quote = { version = "1.0" }
15+
salad-serde = { path = "crates/serde" }
1516
salad-types = { path = "crates/types" }
1617
serde = { version = "1.0" }
1718
serde_yaml_ng = { version = "0.10" }
@@ -30,5 +31,5 @@ name = "salad_core"
3031
path = "src/lib.rs"
3132

3233
[dependencies]
34+
salad-serde.workspace = true
3335
salad-types.workspace = true
34-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "salad-serde"
3+
version = "0.1.0"
4+
license.workspace = true
5+
authors.workspace = true
6+
edition.workspace = true
7+
publish = false
8+
9+
[dependencies]
10+
salad-types.workspace = true
11+
serde.workspace = true
12+
13+
[dev-dependencies]
14+
serde_yaml_ng.workspace = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// WIP
2+
pub struct SeedData;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
use std::{fmt, marker::PhantomData};
2+
3+
use salad_types::SaladType;
4+
use serde::de;
5+
6+
use super::{IntoDeserializeSeed, SeedData};
7+
8+
/// A list helper deserializer for handling both a single value or a list of values
9+
/// of type `T`.
10+
///
11+
/// This is useful for configurations where a field might accept either a single value
12+
/// or a list of values with the same semantics.
13+
pub struct SingleOrManySeed<'sd, T> {
14+
pub(super) data: &'sd SeedData,
15+
pub(super) _phant: PhantomData<T>,
16+
}
17+
18+
impl<'de, 'sd, T> de::DeserializeSeed<'de> for SingleOrManySeed<'sd, T>
19+
where
20+
T: SaladType + IntoDeserializeSeed<'de, 'sd>,
21+
{
22+
type Value = Box<[T]>;
23+
24+
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
25+
where
26+
D: de::Deserializer<'de>,
27+
{
28+
struct SingleOrManySeedVisitor<'sd, T> {
29+
data: &'sd SeedData,
30+
_phant: PhantomData<T>,
31+
}
32+
33+
impl<'de, 'sd, T> SingleOrManySeedVisitor<'sd, T>
34+
where
35+
T: SaladType + IntoDeserializeSeed<'de, 'sd>,
36+
{
37+
// Private helper method to reduce duplication
38+
#[inline]
39+
fn visit_single_value<V, E>(&self, value: V) -> Result<Box<[T]>, E>
40+
where
41+
E: de::Error,
42+
V: de::IntoDeserializer<'de, E>,
43+
{
44+
let deserializer = de::IntoDeserializer::into_deserializer(value);
45+
de::DeserializeSeed::deserialize(T::deserialize_seed(self.data), deserializer)
46+
.map(|t| Box::from([t]))
47+
}
48+
}
49+
50+
impl<'de, 'sd, T> de::Visitor<'de> for SingleOrManySeedVisitor<'sd, T>
51+
where
52+
T: SaladType + IntoDeserializeSeed<'de, 'sd>,
53+
{
54+
type Value = Box<[T]>;
55+
56+
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
57+
f.write_str("one or a list of values")
58+
}
59+
60+
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
61+
where
62+
E: de::Error,
63+
{
64+
self.visit_single_value(v)
65+
}
66+
67+
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
68+
where
69+
E: de::Error,
70+
{
71+
self.visit_i32(v as i32)
72+
}
73+
74+
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
75+
where
76+
E: de::Error,
77+
{
78+
self.visit_i32(v as i32)
79+
}
80+
81+
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
82+
where
83+
E: de::Error,
84+
{
85+
self.visit_single_value(v)
86+
}
87+
88+
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
89+
where
90+
E: de::Error,
91+
{
92+
self.visit_single_value(v)
93+
}
94+
95+
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
96+
where
97+
E: de::Error,
98+
{
99+
self.visit_i32(v as i32)
100+
}
101+
102+
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
103+
where
104+
E: de::Error,
105+
{
106+
self.visit_i32(v as i32)
107+
}
108+
109+
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
110+
where
111+
E: de::Error,
112+
{
113+
self.visit_single_value(v)
114+
}
115+
116+
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
117+
where
118+
E: de::Error,
119+
{
120+
self.visit_single_value(v)
121+
}
122+
123+
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
124+
where
125+
E: de::Error,
126+
{
127+
self.visit_single_value(v)
128+
}
129+
130+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
131+
where
132+
E: de::Error,
133+
{
134+
self.visit_single_value(v)
135+
}
136+
137+
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
138+
where
139+
E: de::Error,
140+
{
141+
self.visit_single_value(v)
142+
}
143+
144+
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
145+
where
146+
E: de::Error,
147+
{
148+
self.visit_single_value(v)
149+
}
150+
151+
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
152+
where
153+
E: de::Error,
154+
{
155+
self.visit_single_value(v)
156+
}
157+
158+
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
159+
where
160+
A: de::MapAccess<'de>,
161+
{
162+
let deserializer = de::value::MapAccessDeserializer::new(map);
163+
de::DeserializeSeed::deserialize(T::deserialize_seed(self.data), deserializer)
164+
.map(|t| Box::from([t]))
165+
}
166+
167+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
168+
where
169+
A: de::SeqAccess<'de>,
170+
{
171+
let capacity = seq.size_hint().unwrap_or(8);
172+
let mut entries = Vec::with_capacity(capacity);
173+
174+
while let Some(entry) = seq.next_element_seed(T::deserialize_seed(self.data))? {
175+
entries.push(entry);
176+
}
177+
178+
Ok(entries.into_boxed_slice())
179+
}
180+
}
181+
182+
deserializer.deserialize_any(SingleOrManySeedVisitor {
183+
data: self.data,
184+
_phant: PhantomData,
185+
})
186+
}
187+
}
188+
189+
#[cfg(test)]
190+
mod tests {
191+
use salad_types::SaladAny;
192+
use serde::__private::de::{Content, ContentDeserializer};
193+
194+
use super::*;
195+
196+
#[test]
197+
fn single_object_entry() {
198+
let input = r#"
199+
type: object
200+
key: value
201+
"#;
202+
203+
let deserializer: ContentDeserializer<'_, serde_yaml_ng::Error> = {
204+
let content: Content<'static> = serde_yaml_ng::from_str::<Content>(input).unwrap();
205+
ContentDeserializer::new(content)
206+
};
207+
208+
let object = de::DeserializeSeed::deserialize(
209+
<Box<[SaladAny]>>::deserialize_seed(&SeedData),
210+
deserializer,
211+
);
212+
213+
assert!(object.is_ok_and(|r| matches!(r[0], SaladAny::Object(_))))
214+
}
215+
216+
#[test]
217+
fn single_primitive_entry() {
218+
let input = r#"Hello, World!"#;
219+
220+
let deserializer: ContentDeserializer<'_, serde_yaml_ng::Error> = {
221+
let content: Content<'static> = serde_yaml_ng::from_str::<Content>(input).unwrap();
222+
ContentDeserializer::new(content)
223+
};
224+
225+
let string = de::DeserializeSeed::deserialize(
226+
<Box<[SaladAny]>>::deserialize_seed(&SeedData),
227+
deserializer,
228+
);
229+
230+
assert!(string.is_ok_and(|r| matches!(r[0], SaladAny::String(_))))
231+
}
232+
233+
#[test]
234+
fn multiple_entries() {
235+
let input = r#"
236+
- 1
237+
- 2.0
238+
- true
239+
- Hello, World!
240+
- type: object
241+
key: value
242+
"#;
243+
244+
let deserializer: ContentDeserializer<'_, serde_yaml_ng::Error> = {
245+
let content: Content<'static> = serde_yaml_ng::from_str::<Content>(input).unwrap();
246+
ContentDeserializer::new(content)
247+
};
248+
249+
let string = de::DeserializeSeed::deserialize(
250+
<Box<[SaladAny]>>::deserialize_seed(&SeedData),
251+
deserializer,
252+
);
253+
254+
assert!(string.is_ok_and(
255+
|r| matches!(r[1], SaladAny::Float(_)) && matches!(r[3], SaladAny::String(_))
256+
))
257+
}
258+
}

0 commit comments

Comments
 (0)