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}