Skip to main content

noyalib/with/
singleton_map_recursive.rs

1//! Recursively serialize enums as single-entry maps.
2//!
3//! This module provides `serialize` and `deserialize` functions that apply
4//! singleton map formatting recursively through nested structures.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use noyalib::with::singleton_map_recursive;
10//!
11//! #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
12//! enum Inner {
13//!     A,
14//!     B { value: i32 },
15//! }
16//!
17//! #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
18//! enum Outer {
19//!     Single(Inner),
20//!     Multiple(Vec<Inner>),
21//! }
22//!
23//! #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
24//! struct Config {
25//!     #[serde(with = "singleton_map_recursive")]
26//!     items: Vec<Outer>,
27//! }
28//! ```
29
30// SPDX-License-Identifier: MIT OR Apache-2.0
31// Copyright (c) 2026 Noyalib. All rights reserved.
32
33use crate::prelude::*;
34
35/// Recursively transform a Value to use singleton map representation for enums.
36fn transform_to_singleton_map(value: crate::Value) -> crate::Value {
37    match value {
38        crate::Value::Sequence(seq) => {
39            crate::Value::Sequence(seq.into_iter().map(transform_to_singleton_map).collect())
40        }
41        crate::Value::Mapping(map) => {
42            let transformed: crate::Mapping = map
43                .into_iter()
44                .map(|(k, v)| (k, transform_to_singleton_map(v)))
45                .collect();
46            crate::Value::Mapping(transformed)
47        }
48        crate::Value::Tagged(tagged) => {
49            let (tag, value) = tagged.into_parts();
50            let inner = transform_to_singleton_map(value);
51            crate::Value::Tagged(Box::new(crate::TaggedValue::new(tag, inner)))
52        }
53        // Scalars pass through unchanged
54        other => other,
55    }
56}
57
58/// Serialize a value with recursive singleton map transformation.
59///
60/// This applies singleton map formatting to all enum variants throughout
61/// the nested structure.
62///
63/// # Examples
64///
65/// ```rust
66/// use noyalib::with::singleton_map_recursive;
67///
68/// #[derive(Debug, serde::Serialize, serde::Deserialize)]
69/// enum Status {
70///     Active,
71///     Pending,
72/// }
73///
74/// #[derive(Debug, serde::Serialize, serde::Deserialize)]
75/// struct Task {
76///     #[serde(with = "singleton_map_recursive")]
77///     statuses: Vec<Status>,
78/// }
79/// ```
80pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
81where
82    T: serde_core::Serialize,
83    S: serde_core::Serializer,
84{
85    // Serialize to Value first
86    let yaml_value = crate::to_value(value).map_err(serde_core::ser::Error::custom)?;
87
88    // Transform recursively
89    let transformed = transform_to_singleton_map(yaml_value);
90
91    // Serialize the transformed value
92    serde_core::Serialize::serialize(&transformed, serializer)
93}
94
95/// Deserialize a value from recursive singleton map format.
96///
97/// This is the counterpart to [`serialize`], deserializing from the
98/// singleton map format back to the original type.
99///
100/// # Examples
101///
102/// ```
103/// use noyalib::with::singleton_map_recursive;
104///
105/// #[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug)]
106/// enum Inner { A }
107///
108/// #[derive(serde::Deserialize, Debug)]
109/// struct Doc {
110///     #[serde(with = "singleton_map_recursive")]
111///     items: Vec<Inner>,
112/// }
113///
114/// let d: Doc = noyalib::from_str("items:\n  - A: null\n").unwrap();
115/// assert_eq!(d.items.len(), 1);
116/// ```
117pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
118where
119    T: serde_core::de::DeserializeOwned + 'static,
120    D: serde_core::Deserializer<'de>,
121{
122    use serde_core::Deserialize as _;
123    // Deserialize as Value first
124    let value = crate::Value::deserialize(deserializer)?;
125
126    // Convert to target type
127    crate::from_value(&value).map_err(serde_core::de::Error::custom)
128}
129
130#[cfg(test)]
131mod tests {
132
133    #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
134    enum Inner {
135        A,
136        B { value: i32 },
137    }
138
139    #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
140    struct Container {
141        name: String,
142        #[serde(with = "crate::with::singleton_map_recursive")]
143        items: Vec<Inner>,
144    }
145
146    #[test]
147    fn test_recursive_vec_of_enums() {
148        let container = Container {
149            name: "test".to_string(),
150            items: vec![Inner::A, Inner::B { value: 42 }],
151        };
152
153        let yaml = crate::to_string(&container).unwrap();
154        let parsed: Container = crate::from_str(&yaml).unwrap();
155        assert_eq!(parsed, container);
156    }
157
158    #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
159    struct Nested {
160        #[serde(with = "crate::with::singleton_map_recursive")]
161        data: std::collections::BTreeMap<String, Inner>,
162    }
163
164    #[test]
165    fn test_recursive_map_of_enums() {
166        let mut data = std::collections::BTreeMap::new();
167        let _ = data.insert("first".to_string(), Inner::A);
168        let _ = data.insert("second".to_string(), Inner::B { value: 100 });
169
170        let container = Nested { data };
171
172        let yaml = crate::to_string(&container).unwrap();
173        let parsed: Nested = crate::from_str(&yaml).unwrap();
174        assert_eq!(parsed, container);
175    }
176}