1use std::{any, any::Any, mem};
4
5use crate::{SerializerOptions, metadata::ConfigMetadata, utils::JsonObject, value::Pointer};
6
7#[doc(hidden)] pub trait ConfigVisitor {
10 fn visit_tag(&mut self, variant_index: usize);
13
14 fn visit_param(&mut self, param_index: usize, value: &dyn any::Any);
17
18 fn visit_nested_config(&mut self, config_index: usize, config: &dyn VisitConfig);
21
22 fn visit_nested_opt_config(&mut self, config_index: usize, config: Option<&dyn VisitConfig>) {
27 if let Some(config) = config {
28 self.visit_nested_config(config_index, config);
29 }
30 }
31}
32
33pub trait VisitConfig {
38 fn visit_config(&self, visitor: &mut dyn ConfigVisitor);
40}
41
42#[derive(Debug)]
44pub(crate) struct Serializer {
45 metadata: &'static ConfigMetadata,
46 current_prefix: Option<String>,
48 json: JsonObject,
49 options: SerializerOptions,
50}
51
52impl Serializer {
53 pub(crate) fn new(
55 metadata: &'static ConfigMetadata,
56 prefix: &str,
57 options: SerializerOptions,
58 ) -> Self {
59 Self {
60 metadata,
61 current_prefix: options.flat.then(|| prefix.to_owned()),
62 json: serde_json::Map::new(),
63 options,
64 }
65 }
66
67 pub(crate) fn into_inner(self) -> JsonObject {
69 self.json
70 }
71
72 fn insert(&mut self, param_name: &str, value: serde_json::Value) {
73 let key = if let Some(prefix) = &self.current_prefix {
74 Pointer(prefix).join(param_name)
75 } else {
76 param_name.to_owned()
77 };
78 self.json.insert(key, value);
79 }
80}
81
82impl ConfigVisitor for Serializer {
83 fn visit_tag(&mut self, variant_index: usize) {
84 let tag = self.metadata.tag.unwrap();
85 let tag_variant = &tag.variants[variant_index];
86
87 let should_insert = !self.options.diff_with_default
88 || tag
89 .default_variant
90 .is_none_or(|default_variant| default_variant.rust_name != tag_variant.rust_name);
91 if should_insert {
92 self.insert(tag.param.name, tag_variant.name.into());
93 }
94 }
95
96 fn visit_param(&mut self, param_index: usize, value: &dyn Any) {
97 let param = &self.metadata.params[param_index];
98 let mut value = param.deserializer.serialize_param(value);
100
101 let should_insert = !self.options.diff_with_default
105 || param.fallback.is_some()
106 || param.default_value_json().as_ref() != Some(&value);
107 if should_insert {
108 if let (Some(placeholder), true) = (
109 &self.options.secret_placeholder,
110 param.type_description().contains_secrets(),
111 ) {
112 value = placeholder.clone().into();
113 }
114 self.insert(param.name, value);
115 }
116 }
117
118 fn visit_nested_config(&mut self, config_index: usize, config: &dyn VisitConfig) {
119 let nested_metadata = &self.metadata.nested_configs[config_index];
120 let prev_metadata = mem::replace(&mut self.metadata, nested_metadata.meta);
121
122 if nested_metadata.name.is_empty() {
123 config.visit_config(self);
124 } else if let Some(prefix) = &mut self.current_prefix {
125 let new_prefix = Pointer(prefix).join(nested_metadata.name);
126 let prev_prefix = mem::replace(prefix, new_prefix);
127 config.visit_config(self);
128 self.current_prefix = Some(prev_prefix);
129 } else {
130 let mut prev_json = mem::take(&mut self.json);
131 config.visit_config(self);
132
133 let nested_json = mem::take(&mut self.json);
134 let should_insert = !self.options.diff_with_default || !nested_json.is_empty();
135 if should_insert {
136 prev_json.insert(nested_metadata.name.to_owned(), nested_json.into());
137 }
138 self.json = prev_json;
139 }
140
141 self.metadata = prev_metadata;
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use std::collections::HashMap;
148
149 use super::*;
150 use crate::{
151 DescribeConfig,
152 metadata::ConfigMetadata,
153 testonly::{ConfigWithNesting, DefaultingConfig, EnumConfig, NestedConfig, SimpleEnum},
154 };
155
156 #[derive(Debug)]
157 struct PersistingVisitor {
158 metadata: &'static ConfigMetadata,
159 tag: Option<&'static str>,
160 param_values: HashMap<&'static str, serde_json::Value>,
161 }
162
163 impl PersistingVisitor {
164 fn new(metadata: &'static ConfigMetadata) -> Self {
165 Self {
166 metadata,
167 tag: None,
168 param_values: HashMap::new(),
169 }
170 }
171 }
172
173 impl ConfigVisitor for PersistingVisitor {
174 fn visit_tag(&mut self, variant_index: usize) {
175 assert!(self.tag.is_none());
176 self.tag = Some(self.metadata.tag.unwrap().variants[variant_index].rust_name);
177 }
178
179 fn visit_param(&mut self, param_index: usize, value: &dyn any::Any) {
180 let param = &self.metadata.params[param_index];
181 let prev_value = self
182 .param_values
183 .insert(param.name, param.deserializer.serialize_param(value));
184 assert!(
185 prev_value.is_none(),
186 "Param value {} is visited twice",
187 param.name
188 );
189 }
190
191 fn visit_nested_config(&mut self, _config_index: usize, _config: &dyn VisitConfig) {
192 }
194 }
195
196 #[test]
197 fn visiting_struct_config() {
198 let config = DefaultingConfig::default();
199 let mut visitor = PersistingVisitor::new(&DefaultingConfig::DESCRIPTION);
200 config.visit_config(&mut visitor);
201
202 assert_eq!(visitor.tag, None);
203 assert_eq!(
204 visitor.param_values,
205 HashMap::from([
206 ("float", serde_json::Value::Null),
207 ("set", serde_json::json!([])),
208 ("int", 12_u32.into()),
209 ("url", "https://example.com/".into())
210 ])
211 );
212 }
213
214 #[test]
215 fn visiting_enum_config() {
216 let config = EnumConfig::First;
217 let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
218 config.visit_config(&mut visitor);
219 assert_eq!(visitor.tag, Some("First"));
220 assert_eq!(visitor.param_values, HashMap::new());
221
222 let config = EnumConfig::Nested(NestedConfig::default_nested());
223 let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
224 config.visit_config(&mut visitor);
225 assert_eq!(visitor.tag, Some("Nested"));
226 assert_eq!(visitor.param_values, HashMap::new());
227
228 let config = EnumConfig::WithFields {
229 string: Some("test".to_owned()),
230 flag: true,
231 set: [1].into(),
232 };
233 let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
234 config.visit_config(&mut visitor);
235 assert_eq!(visitor.tag, Some("WithFields"));
236 assert_eq!(
237 visitor.param_values,
238 HashMap::from([
239 ("string", "test".into()),
240 ("flag", true.into()),
241 ("set", serde_json::json!([1]))
242 ])
243 );
244 }
245
246 #[test]
247 fn serializing_config() {
248 let config = EnumConfig::WithFields {
249 string: Some("test".to_owned()),
250 flag: true,
251 set: [1].into(),
252 };
253 let json = SerializerOptions::default().serialize(&config);
254 assert_eq!(
255 serde_json::Value::from(json),
256 serde_json::json!({
257 "type": "WithFields",
258 "string": "test",
259 "flag": true,
260 "set": [1]
261 })
262 );
263 }
264
265 #[test]
266 fn serializing_nested_config() {
267 let config = ConfigWithNesting {
268 value: 23,
269 merged: String::new(),
270 nested: NestedConfig {
271 simple_enum: SimpleEnum::First,
272 other_int: 42,
273 map: HashMap::new(),
274 },
275 };
276
277 let json = SerializerOptions::default().serialize(&config);
278 assert_eq!(
279 serde_json::Value::from(json),
280 serde_json::json!({
281 "value": 23,
282 "merged": "",
283 "nested": {
284 "renamed": "first",
285 "other_int": 42,
286 "map": {},
287 },
288 })
289 );
290
291 let json = SerializerOptions::diff_with_default().serialize(&config);
292 assert_eq!(
293 serde_json::Value::from(json),
294 serde_json::json!({
295 "value": 23,
296 "nested": {
297 "renamed": "first",
298 },
299 })
300 );
301
302 let json = SerializerOptions::default().flat(true).serialize(&config);
303 assert_eq!(
304 serde_json::Value::from(json),
305 serde_json::json!({
306 "value": 23,
307 "merged": "",
308 "nested.renamed": "first",
309 "nested.other_int": 42,
310 "nested.map": {},
311 })
312 );
313
314 let json = SerializerOptions::diff_with_default()
315 .flat(true)
316 .serialize(&config);
317 assert_eq!(
318 serde_json::Value::from(json),
319 serde_json::json!({
320 "value": 23,
321 "nested.renamed": "first",
322 })
323 );
324 }
325}