use std::{any, any::Any, mem};
use crate::{metadata::ConfigMetadata, utils::JsonObject, value::Pointer, SerializerOptions};
#[doc(hidden)] pub trait ConfigVisitor {
fn visit_tag(&mut self, variant_index: usize);
fn visit_param(&mut self, param_index: usize, value: &dyn any::Any);
fn visit_nested_config(&mut self, config_index: usize, config: &dyn VisitConfig);
fn visit_nested_opt_config(&mut self, config_index: usize, config: Option<&dyn VisitConfig>) {
if let Some(config) = config {
self.visit_nested_config(config_index, config);
}
}
}
pub trait VisitConfig {
fn visit_config(&self, visitor: &mut dyn ConfigVisitor);
}
#[derive(Debug)]
pub(crate) struct Serializer {
metadata: &'static ConfigMetadata,
current_prefix: Option<String>,
json: JsonObject,
options: SerializerOptions,
}
impl Serializer {
pub(crate) fn new(
metadata: &'static ConfigMetadata,
prefix: &str,
options: SerializerOptions,
) -> Self {
Self {
metadata,
current_prefix: options.flat.then(|| prefix.to_owned()),
json: serde_json::Map::new(),
options,
}
}
pub(crate) fn into_inner(self) -> JsonObject {
self.json
}
fn insert(&mut self, param_name: &str, value: serde_json::Value) {
let key = if let Some(prefix) = &self.current_prefix {
Pointer(prefix).join(param_name)
} else {
param_name.to_owned()
};
self.json.insert(key, value);
}
}
impl ConfigVisitor for Serializer {
fn visit_tag(&mut self, variant_index: usize) {
let tag = self.metadata.tag.unwrap();
let tag_variant = &tag.variants[variant_index];
let should_insert = !self.options.diff_with_default
|| !tag
.default_variant
.is_some_and(|default_variant| default_variant.rust_name == tag_variant.rust_name);
if should_insert {
self.insert(tag.param.name, tag_variant.name.into());
}
}
fn visit_param(&mut self, param_index: usize, value: &dyn Any) {
let param = &self.metadata.params[param_index];
let mut value = param.deserializer.serialize_param(value);
let should_insert = !self.options.diff_with_default
|| param.fallback.is_some()
|| param.default_value_json().as_ref() != Some(&value);
if should_insert {
if let (Some(placeholder), true) = (
&self.options.secret_placeholder,
param.type_description().contains_secrets(),
) {
value = placeholder.clone().into();
}
self.insert(param.name, value);
}
}
fn visit_nested_config(&mut self, config_index: usize, config: &dyn VisitConfig) {
let nested_metadata = &self.metadata.nested_configs[config_index];
let prev_metadata = mem::replace(&mut self.metadata, nested_metadata.meta);
if nested_metadata.name.is_empty() {
config.visit_config(self);
} else if let Some(prefix) = &mut self.current_prefix {
let new_prefix = Pointer(prefix).join(nested_metadata.name);
let prev_prefix = mem::replace(prefix, new_prefix);
config.visit_config(self);
self.current_prefix = Some(prev_prefix);
} else {
let mut prev_json = mem::take(&mut self.json);
config.visit_config(self);
let nested_json = mem::take(&mut self.json);
let should_insert = !self.options.diff_with_default || !nested_json.is_empty();
if should_insert {
prev_json.insert(nested_metadata.name.to_owned(), nested_json.into());
}
self.json = prev_json;
}
self.metadata = prev_metadata;
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::{
metadata::ConfigMetadata,
testonly::{ConfigWithNesting, DefaultingConfig, EnumConfig, NestedConfig, SimpleEnum},
DescribeConfig,
};
#[derive(Debug)]
struct PersistingVisitor {
metadata: &'static ConfigMetadata,
tag: Option<&'static str>,
param_values: HashMap<&'static str, serde_json::Value>,
}
impl PersistingVisitor {
fn new(metadata: &'static ConfigMetadata) -> Self {
Self {
metadata,
tag: None,
param_values: HashMap::new(),
}
}
}
impl ConfigVisitor for PersistingVisitor {
fn visit_tag(&mut self, variant_index: usize) {
assert!(self.tag.is_none());
self.tag = Some(self.metadata.tag.unwrap().variants[variant_index].rust_name);
}
fn visit_param(&mut self, param_index: usize, value: &dyn any::Any) {
let param = &self.metadata.params[param_index];
let prev_value = self
.param_values
.insert(param.name, param.deserializer.serialize_param(value));
assert!(
prev_value.is_none(),
"Param value {} is visited twice",
param.name
);
}
fn visit_nested_config(&mut self, _config_index: usize, _config: &dyn VisitConfig) {
}
}
#[test]
fn visiting_struct_config() {
let config = DefaultingConfig::default();
let mut visitor = PersistingVisitor::new(&DefaultingConfig::DESCRIPTION);
config.visit_config(&mut visitor);
assert_eq!(visitor.tag, None);
assert_eq!(
visitor.param_values,
HashMap::from([
("float", serde_json::Value::Null),
("set", serde_json::json!([])),
("int", 12_u32.into()),
("url", "https://example.com/".into())
])
);
}
#[test]
fn visiting_enum_config() {
let config = EnumConfig::First;
let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
config.visit_config(&mut visitor);
assert_eq!(visitor.tag, Some("First"));
assert_eq!(visitor.param_values, HashMap::new());
let config = EnumConfig::Nested(NestedConfig::default_nested());
let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
config.visit_config(&mut visitor);
assert_eq!(visitor.tag, Some("Nested"));
assert_eq!(visitor.param_values, HashMap::new());
let config = EnumConfig::WithFields {
string: Some("test".to_owned()),
flag: true,
set: [1].into(),
};
let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
config.visit_config(&mut visitor);
assert_eq!(visitor.tag, Some("WithFields"));
assert_eq!(
visitor.param_values,
HashMap::from([
("string", "test".into()),
("flag", true.into()),
("set", serde_json::json!([1]))
])
);
}
#[test]
fn serializing_config() {
let config = EnumConfig::WithFields {
string: Some("test".to_owned()),
flag: true,
set: [1].into(),
};
let json = SerializerOptions::default().serialize(&config);
assert_eq!(
serde_json::Value::from(json),
serde_json::json!({
"type": "WithFields",
"string": "test",
"flag": true,
"set": [1]
})
);
}
#[test]
fn serializing_nested_config() {
let config = ConfigWithNesting {
value: 23,
merged: String::new(),
nested: NestedConfig {
simple_enum: SimpleEnum::First,
other_int: 42,
map: HashMap::new(),
},
};
let json = SerializerOptions::default().serialize(&config);
assert_eq!(
serde_json::Value::from(json),
serde_json::json!({
"value": 23,
"merged": "",
"nested": {
"renamed": "first",
"other_int": 42,
"map": {},
},
})
);
let json = SerializerOptions::diff_with_default().serialize(&config);
assert_eq!(
serde_json::Value::from(json),
serde_json::json!({
"value": 23,
"nested": {
"renamed": "first",
},
})
);
let json = SerializerOptions::default().flat(true).serialize(&config);
assert_eq!(
serde_json::Value::from(json),
serde_json::json!({
"value": 23,
"merged": "",
"nested.renamed": "first",
"nested.other_int": 42,
"nested.map": {},
})
);
let json = SerializerOptions::diff_with_default()
.flat(true)
.serialize(&config);
assert_eq!(
serde_json::Value::from(json),
serde_json::json!({
"value": 23,
"nested.renamed": "first",
})
);
}
}