1use serde::{Serialize, Serializer};
10use serde_json::{Map, Value};
11use std::collections::{BTreeMap, HashMap};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
16#[error("serialization failed: {0}")]
17pub struct DumpError(#[from] pub serde_json::Error);
18
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
24pub struct FieldSet(BTreeMap<String, FieldSet>);
25
26impl FieldSet {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn of<I: IntoIterator<Item = S>, S: Into<String>>(fields: I) -> Self {
34 Self(
35 fields
36 .into_iter()
37 .map(|f| (f.into(), Self::new()))
38 .collect(),
39 )
40 }
41
42 #[must_use]
44 pub fn nested(mut self, field: impl Into<String>, children: FieldSet) -> Self {
45 self.0.insert(field.into(), children);
46 self
47 }
48
49 fn get(&self, field: &str) -> Option<&FieldSet> {
50 self.0.get(field)
51 }
52
53 fn is_leaf(&self) -> bool {
54 self.0.is_empty()
55 }
56}
57
58#[derive(Debug, Clone, Default, PartialEq, Eq)]
60pub struct DumpOptions {
61 pub exclude_none: bool,
63 pub exclude_defaults: bool,
65 pub include: Option<FieldSet>,
67 pub exclude: Option<FieldSet>,
69}
70
71impl DumpOptions {
72 pub fn new() -> Self {
74 Self::default()
75 }
76
77 #[must_use]
79 pub fn exclude_none(mut self) -> Self {
80 self.exclude_none = true;
81 self
82 }
83
84 #[must_use]
86 pub fn exclude_defaults(mut self) -> Self {
87 self.exclude_defaults = true;
88 self
89 }
90
91 #[must_use]
93 pub fn include(mut self, fields: FieldSet) -> Self {
94 self.include = Some(fields);
95 self
96 }
97
98 #[must_use]
100 pub fn exclude(mut self, fields: FieldSet) -> Self {
101 self.exclude = Some(fields);
102 self
103 }
104
105 pub fn for_field(&self, field: &str) -> Option<DumpOptions> {
108 let include = match &self.include {
109 None => None,
110 Some(set) => match set.get(field) {
111 None => return None,
112 Some(child) if child.is_leaf() => None,
113 Some(child) => Some(child.clone()),
114 },
115 };
116 let exclude = match &self.exclude {
117 None => None,
118 Some(set) => match set.get(field) {
119 Some(child) if child.is_leaf() => return None,
120 Some(child) => Some(child.clone()),
121 None => None,
122 },
123 };
124 Some(DumpOptions {
125 include,
126 exclude,
127 ..self.clone()
128 })
129 }
130
131 pub fn drops_value(&self, value: &Value) -> bool {
133 self.exclude_none && value.is_null()
134 }
135
136 pub fn apply(&self, value: Value) -> Value {
139 match value {
140 Value::Object(map) => {
141 let mut out = Map::new();
142 for (key, member) in map {
143 let Some(child) = self.for_field(&key) else {
144 continue;
145 };
146 let member = child.apply(member);
147 if !self.drops_value(&member) {
148 out.insert(key, member);
149 }
150 }
151 Value::Object(out)
152 }
153 Value::Array(items) => Value::Array(items.into_iter().map(|v| self.apply(v)).collect()),
154 other => other,
155 }
156 }
157}
158
159pub trait Dump: Serialize {
167 fn serialize_dump<S: Serializer>(
176 &self,
177 serializer: S,
178 opts: &DumpOptions,
179 ) -> Result<S::Ok, S::Error> {
180 self.dump(opts)
181 .map_err(serde::ser::Error::custom)?
182 .serialize(serializer)
183 }
184
185 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
190 Ok(opts.apply(serde_json::to_value(self)?))
191 }
192}
193
194pub struct DumpSerialize<'a, T: ?Sized>(pub &'a T, pub &'a DumpOptions);
196
197impl<T: Dump + ?Sized> Serialize for DumpSerialize<'_, T> {
198 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
199 self.0.serialize_dump(serializer, self.1)
200 }
201}
202
203macro_rules! plain_dump {
204 ($($t:ty),* $(,)?) => {$(
205 impl Dump for $t {
206 fn serialize_dump<S: Serializer>(
207 &self,
208 serializer: S,
209 _: &DumpOptions,
210 ) -> Result<S::Ok, S::Error> {
211 self.serialize(serializer)
212 }
213 }
214 )*};
215}
216plain_dump!(
217 bool,
218 i8,
219 i16,
220 i32,
221 i64,
222 u8,
223 u16,
224 u32,
225 u64,
226 isize,
227 usize,
228 char,
229 String,
230 str,
231 (),
232);
233
234impl Dump for f32 {}
235impl Dump for f64 {}
236impl Dump for i128 {}
237impl Dump for u128 {}
238impl Dump for Value {}
239impl Dump for Map<String, Value> {}
240
241impl<T: Dump + ?Sized> Dump for &T {
242 fn serialize_dump<S: Serializer>(
243 &self,
244 serializer: S,
245 opts: &DumpOptions,
246 ) -> Result<S::Ok, S::Error> {
247 (**self).serialize_dump(serializer, opts)
248 }
249
250 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
251 (**self).dump(opts)
252 }
253}
254
255impl<T: Dump + ?Sized> Dump for Box<T> {
256 fn serialize_dump<S: Serializer>(
257 &self,
258 serializer: S,
259 opts: &DumpOptions,
260 ) -> Result<S::Ok, S::Error> {
261 (**self).serialize_dump(serializer, opts)
262 }
263
264 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
265 (**self).dump(opts)
266 }
267}
268
269impl<T: Dump> Dump for Option<T> {
270 fn serialize_dump<S: Serializer>(
271 &self,
272 serializer: S,
273 opts: &DumpOptions,
274 ) -> Result<S::Ok, S::Error> {
275 match self {
276 Some(value) => value.serialize_dump(serializer, opts),
277 None => serializer.serialize_none(),
278 }
279 }
280
281 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
282 self.as_ref()
283 .map_or(Ok(Value::Null), |inner| inner.dump(opts))
284 }
285}
286
287impl<T: Dump> Dump for [T] {
288 fn serialize_dump<S: Serializer>(
289 &self,
290 serializer: S,
291 opts: &DumpOptions,
292 ) -> Result<S::Ok, S::Error> {
293 use serde::ser::SerializeSeq;
294 let mut sequence = serializer.serialize_seq(Some(self.len()))?;
295 for value in self {
296 sequence.serialize_element(&DumpSerialize(value, opts))?;
297 }
298 sequence.end()
299 }
300
301 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
302 self.iter()
303 .map(|item| item.dump(opts))
304 .collect::<Result<_, _>>()
305 .map(Value::Array)
306 }
307}
308
309impl<T: Dump> Dump for Vec<T> {
310 fn serialize_dump<S: Serializer>(
311 &self,
312 serializer: S,
313 opts: &DumpOptions,
314 ) -> Result<S::Ok, S::Error> {
315 self.as_slice().serialize_dump(serializer, opts)
316 }
317
318 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
319 self.as_slice().dump(opts)
320 }
321}
322
323fn dump_map<'a, T: Dump + 'a>(
324 entries: impl Iterator<Item = (&'a String, &'a T)>,
325 opts: &DumpOptions,
326) -> Result<Value, DumpError> {
327 let mut out = Map::new();
328 for (key, value) in entries {
329 let value = value.dump(opts)?;
330 if !opts.drops_value(&value) {
331 out.insert(key.clone(), value);
332 }
333 }
334 Ok(Value::Object(out))
335}
336
337impl<T: Dump> Dump for BTreeMap<String, T> {
338 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
339 dump_map(self.iter(), opts)
340 }
341}
342
343impl<T: Dump, S: std::hash::BuildHasher> Dump for HashMap<String, T, S> {
344 fn dump(&self, opts: &DumpOptions) -> Result<Value, DumpError> {
345 dump_map(self.iter(), opts)
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use serde_json::json;
353
354 #[test]
355 fn exclude_none_and_nested_exclude() {
356 let opts = DumpOptions::new()
357 .exclude_none()
358 .exclude(FieldSet::new().nested("user", FieldSet::of(["password"])));
359 let out = opts.apply(json!({"a": null, "user": {"name": "x", "password": "p"}, "b": 1}));
360 assert_eq!(out, json!({"user": {"name": "x"}, "b": 1}));
361 }
362
363 #[test]
364 fn include_applies_through_lists() {
365 let opts =
366 DumpOptions::new().include(FieldSet::new().nested("items", FieldSet::of(["id"])));
367 let out = opts.apply(json!({"items": [{"id": 1, "x": 2}], "other": 3}));
368 assert_eq!(out, json!({"items": [{"id": 1}]}));
369 }
370}