use std::vec::Vec;
use facet_core::{Facet, Shape, Type, UserType};
use facet_format::{
ContainerKind, FieldKey, FieldLocationHint, FormatDeserializer, FormatParser, ParseError,
ParseEvent, ParseEventKind, SavePoint, ScalarValue,
};
use facet_reflect::Span;
use indexmap::IndexMap;
use crate::config_value::{ConfigValue, Sourced};
use crate::provenance::Provenance;
pub fn from_config_value<T>(value: &ConfigValue) -> Result<T, ConfigValueDeserializeError>
where
T: Facet<'static>,
{
tracing::trace!(
shape = T::SHAPE.type_identifier,
?value,
"from_config_value: starting"
);
let value_with_defaults = fill_defaults_from_shape(value, T::SHAPE);
tracing::trace!(
?value_with_defaults,
"from_config_value: after fill_defaults_from_shape"
);
let value_coerced = crate::reflection::coerce_types_from_shape(&value_with_defaults, T::SHAPE);
tracing::trace!(
?value_coerced,
"from_config_value: after coerce_types_from_shape"
);
let mut parser = ConfigValueParser::new(&value_coerced);
let mut deserializer = FormatDeserializer::new_owned(&mut parser);
deserializer
.deserialize()
.map_err(ConfigValueDeserializeError::Deserialize)
}
pub(crate) fn fill_defaults_from_shape(value: &ConfigValue, shape: &'static Shape) -> ConfigValue {
fill_defaults_from_shape_recursive(value, shape, "")
}
use crate::schema::{
ArgLevelSchema, ConfigEnumSchema, ConfigStructSchema, ConfigValueSchema, Schema, Subcommand,
ValueSchema,
};
pub(crate) fn fill_defaults_from_schema(value: &ConfigValue, schema: &Schema) -> ConfigValue {
let ConfigValue::Object(sourced) = value else {
return value.clone();
};
let mut new_map = sourced.value.clone();
fill_defaults_from_arg_level(&mut new_map, schema.args(), "");
if let Some(config_schema) = schema.config() {
let config_field_name = config_schema.field_name().unwrap_or("config").to_string();
if let Some(config_value) = new_map.get(&config_field_name) {
let filled = fill_defaults_from_config_struct(config_value, config_schema, "");
new_map.insert(config_field_name, filled);
} else {
let filled = fill_defaults_from_config_struct(
&ConfigValue::Object(Sourced::new(IndexMap::default())),
config_schema,
"",
);
new_map.insert(config_field_name, filled);
}
}
ConfigValue::Object(Sourced {
value: new_map,
span: sourced.span,
provenance: sourced.provenance.clone(),
})
}
fn fill_defaults_from_arg_level(
map: &mut IndexMap<String, ConfigValue, std::hash::RandomState>,
arg_level: &ArgLevelSchema,
path_prefix: &str,
) {
for (field_name, arg_schema) in arg_level.args() {
if !map.contains_key(field_name) {
if let Some(explicit_default) = arg_schema.default() {
tracing::trace!(
field = field_name,
?explicit_default,
"fill_defaults_from_arg_level: inserting explicit default for missing field"
);
map.insert(field_name.clone(), explicit_default.clone());
} else if let Some(implicit_default) =
get_default_from_value_schema(arg_schema.value(), path_prefix)
{
tracing::trace!(
field = field_name,
?implicit_default,
"fill_defaults_from_arg_level: inserting implicit default for missing field"
);
map.insert(field_name.clone(), implicit_default);
}
}
}
if let Some(subcommand_field_name) = arg_level.subcommand_field_name()
&& let Some(ConfigValue::Enum(enum_sourced)) = map.get(subcommand_field_name)
&& let Some(subcommand) = arg_level.subcommands().get(&enum_sourced.value.variant)
{
let mut new_fields = enum_sourced.value.fields.clone();
fill_defaults_from_subcommand(&mut new_fields, subcommand, path_prefix);
let new_enum = ConfigValue::Enum(Sourced {
value: crate::config_value::EnumValue {
variant: enum_sourced.value.variant.clone(),
fields: new_fields,
},
span: enum_sourced.span,
provenance: enum_sourced.provenance.clone(),
});
map.insert(subcommand_field_name.to_string(), new_enum);
}
for (field_name, arg_schema) in arg_level.args() {
if let Some(val) = map.get(field_name) {
let field_path = if path_prefix.is_empty() {
field_name.clone()
} else {
format!("{}.{}", path_prefix, field_name)
};
let filled = fill_defaults_from_value_schema(val, arg_schema.value(), &field_path);
map.insert(field_name.clone(), filled);
}
}
}
fn fill_defaults_from_subcommand(
fields: &mut IndexMap<String, ConfigValue, std::hash::RandomState>,
subcommand: &Subcommand,
path_prefix: &str,
) {
for (arg_name, arg_schema) in subcommand.args().args() {
if !fields.contains_key(arg_name) {
if let Some(explicit_default) = arg_schema.default() {
fields.insert(arg_name.clone(), explicit_default.clone());
} else if let Some(implicit_default) =
get_default_from_value_schema(arg_schema.value(), path_prefix)
{
fields.insert(arg_name.clone(), implicit_default);
}
}
}
if subcommand.args().has_subcommands()
&& let Some(nested_subcommand_field) = subcommand.args().subcommand_field_name()
&& let Some(ConfigValue::Enum(enum_sourced)) = fields.get(nested_subcommand_field)
&& let Some(nested_subcommand) = subcommand
.args()
.subcommands()
.get(&enum_sourced.value.variant)
{
let mut new_fields = enum_sourced.value.fields.clone();
fill_defaults_from_subcommand(&mut new_fields, nested_subcommand, path_prefix);
let new_enum = ConfigValue::Enum(Sourced {
value: crate::config_value::EnumValue {
variant: enum_sourced.value.variant.clone(),
fields: new_fields,
},
span: enum_sourced.span,
provenance: enum_sourced.provenance.clone(),
});
fields.insert(nested_subcommand_field.to_string(), new_enum);
}
for (arg_name, arg_schema) in subcommand.args().args() {
if let Some(val) = fields.get(arg_name) {
let field_path = if path_prefix.is_empty() {
arg_name.clone()
} else {
format!("{}.{}", path_prefix, arg_name)
};
let filled = fill_defaults_from_value_schema(val, arg_schema.value(), &field_path);
fields.insert(arg_name.clone(), filled);
}
}
}
fn fill_defaults_from_value_schema(
value: &ConfigValue,
schema: &ValueSchema,
path_prefix: &str,
) -> ConfigValue {
match schema {
ValueSchema::Option { value: inner, .. } => {
fill_defaults_from_value_schema(value, inner, path_prefix)
}
ValueSchema::Vec { element, .. } => {
if let ConfigValue::Array(sourced) = value {
let items: Vec<_> = sourced
.value
.iter()
.map(|item| fill_defaults_from_value_schema(item, element, path_prefix))
.collect();
ConfigValue::Array(Sourced {
value: items,
span: sourced.span,
provenance: sourced.provenance.clone(),
})
} else {
value.clone()
}
}
ValueSchema::Struct { fields, .. } => {
fill_defaults_from_config_struct(value, fields, path_prefix)
}
ValueSchema::Leaf(_) => value.clone(),
}
}
fn get_default_from_value_schema(schema: &ValueSchema, _path_prefix: &str) -> Option<ConfigValue> {
match schema {
ValueSchema::Option { .. } => {
None
}
ValueSchema::Vec { .. } => {
None
}
ValueSchema::Struct { .. } => {
Some(ConfigValue::Object(Sourced {
value: IndexMap::default(),
span: None,
provenance: Some(Provenance::Default),
}))
}
ValueSchema::Leaf(leaf) => {
let shape = leaf.shape;
use facet_core::ScalarType;
if let Some(ScalarType::Bool) = shape.scalar_type() {
return Some(ConfigValue::Bool(Sourced {
value: false,
span: None,
provenance: Some(Provenance::Default),
}));
}
None
}
}
}
fn fill_defaults_from_config_struct(
value: &ConfigValue,
struct_schema: &ConfigStructSchema,
path_prefix: &str,
) -> ConfigValue {
let ConfigValue::Object(sourced) = value else {
return value.clone();
};
let mut new_map = sourced.value.clone();
for (field_name, field_schema) in struct_schema.fields() {
if !new_map.contains_key(field_name) {
if let Some(explicit_default) = field_schema.default() {
tracing::trace!(
field = field_name,
?explicit_default,
"fill_defaults_from_config_struct: inserting explicit default for missing field"
);
new_map.insert(field_name.clone(), explicit_default.clone());
} else if let Some(implicit_default) =
get_default_from_config_value_schema(&field_schema.value, path_prefix)
{
tracing::trace!(
field = field_name,
?implicit_default,
"fill_defaults_from_config_struct: inserting implicit default for missing field"
);
new_map.insert(field_name.clone(), implicit_default);
}
}
}
for (key, val) in new_map.iter_mut() {
if let Some(field_schema) = struct_schema.fields().get(key) {
let field_path = if path_prefix.is_empty() {
key.clone()
} else {
format!("{}.{}", path_prefix, key)
};
*val = fill_defaults_from_config_value_schema(val, &field_schema.value, &field_path);
}
}
ConfigValue::Object(Sourced {
value: new_map,
span: sourced.span,
provenance: sourced.provenance.clone(),
})
}
fn fill_defaults_from_config_value_schema(
value: &ConfigValue,
schema: &ConfigValueSchema,
path_prefix: &str,
) -> ConfigValue {
match schema {
ConfigValueSchema::Option { value: inner, .. } => {
fill_defaults_from_config_value_schema(value, inner, path_prefix)
}
ConfigValueSchema::Struct(struct_schema) => {
fill_defaults_from_config_struct(value, struct_schema, path_prefix)
}
ConfigValueSchema::Vec(vec_schema) => {
if let ConfigValue::Array(sourced) = value {
let items: Vec<_> = sourced
.value
.iter()
.map(|item| {
fill_defaults_from_config_value_schema(
item,
vec_schema.element(),
path_prefix,
)
})
.collect();
ConfigValue::Array(Sourced {
value: items,
span: sourced.span,
provenance: sourced.provenance.clone(),
})
} else {
value.clone()
}
}
ConfigValueSchema::Enum(enum_schema) => {
fill_defaults_from_config_enum(value, enum_schema, path_prefix)
}
ConfigValueSchema::Leaf(_) => {
value.clone()
}
}
}
fn fill_defaults_from_config_enum(
value: &ConfigValue,
enum_schema: &ConfigEnumSchema,
path_prefix: &str,
) -> ConfigValue {
let ConfigValue::Enum(sourced) = value else {
return value.clone();
};
let Some(variant_schema) = enum_schema.get_variant(&sourced.value.variant) else {
return value.clone();
};
let mut new_fields = sourced.value.fields.clone();
for (field_name, field_schema) in variant_schema.fields() {
if !new_fields.contains_key(field_name) {
if let Some(explicit_default) = field_schema.default() {
new_fields.insert(field_name.clone(), explicit_default.clone());
} else {
let field_path = if path_prefix.is_empty() {
field_name.clone()
} else {
format!("{}.{}", path_prefix, field_name)
};
if let Some(implicit_default) =
get_default_from_config_value_schema(&field_schema.value, &field_path)
{
new_fields.insert(field_name.clone(), implicit_default);
}
}
}
}
for (key, val) in new_fields.iter_mut() {
if let Some(field_schema) = variant_schema.fields().get(key) {
let field_path = if path_prefix.is_empty() {
key.clone()
} else {
format!("{}.{}", path_prefix, key)
};
*val = fill_defaults_from_config_value_schema(val, &field_schema.value, &field_path);
}
}
ConfigValue::Enum(Sourced {
value: crate::config_value::EnumValue {
variant: sourced.value.variant.clone(),
fields: new_fields,
},
span: sourced.span,
provenance: sourced.provenance.clone(),
})
}
fn get_default_from_config_value_schema(
schema: &ConfigValueSchema,
_path_prefix: &str,
) -> Option<ConfigValue> {
match schema {
ConfigValueSchema::Option { .. } => {
None
}
ConfigValueSchema::Struct(_) => {
Some(ConfigValue::Object(Sourced {
value: IndexMap::default(),
span: None,
provenance: Some(Provenance::Default),
}))
}
ConfigValueSchema::Vec(_) => {
None
}
ConfigValueSchema::Enum(_) => {
None
}
ConfigValueSchema::Leaf(leaf_schema) => {
let shape = leaf_schema.shape;
use facet_core::ScalarType;
if let Some(ScalarType::Bool) = shape.scalar_type() {
return Some(ConfigValue::Bool(Sourced {
value: false,
span: None,
provenance: Some(Provenance::Default),
}));
}
None
}
}
}
fn fill_defaults_from_shape_recursive(
value: &ConfigValue,
shape: &'static Shape,
path_prefix: &str,
) -> ConfigValue {
tracing::trace!(
shape = shape.type_identifier,
path_prefix,
"fill_defaults_from_shape_recursive: entering"
);
if let Ok(option_def) = shape.def.into_option() {
return fill_defaults_from_shape_recursive(value, option_def.t, path_prefix);
}
match value {
ConfigValue::Object(sourced) => {
let fields = match &shape.ty {
Type::User(UserType::Struct(s)) => &s.fields,
_ => return value.clone(),
};
let mut new_map = sourced.value.clone();
fn collect_flattened_fields<'a>(
fields: &'a [facet_core::Field],
result: &mut Vec<&'a facet_core::Field>,
) {
for field in fields.iter() {
if field.is_flattened() {
let inner_shape = field.shape.get();
if let Type::User(UserType::Struct(s)) = &inner_shape.ty {
collect_flattened_fields(s.fields, result);
}
} else {
result.push(field);
}
}
}
for field in fields.iter() {
if field.is_flattened() {
let inner_shape = field.shape.get();
let inner_fields = match &inner_shape.ty {
Type::User(UserType::Struct(s)) => &s.fields,
_ => continue, };
let mut all_inner_fields = Vec::new();
collect_flattened_fields(inner_fields, &mut all_inner_fields);
for inner_field in all_inner_fields {
if !new_map.contains_key(inner_field.name) {
let inner_path = if path_prefix.is_empty() {
inner_field.name.to_string()
} else {
format!("{}.{}", path_prefix, inner_field.name)
};
if let Some(default_value) =
get_default_config_value(inner_field, &inner_path)
{
tracing::trace!(
field = inner_field.name,
parent_field = field.name,
shape = shape.type_identifier,
?default_value,
"fill_defaults_from_shape_recursive: inserting default for flattened field"
);
new_map.insert(inner_field.name.to_string(), default_value);
}
}
}
continue;
}
if !new_map.contains_key(field.name) {
if let Some(default_value) = get_default_config_value(field, path_prefix) {
tracing::trace!(
field = field.name,
shape = shape.type_identifier,
?default_value,
"fill_defaults_from_shape_recursive: inserting default for missing field"
);
new_map.insert(field.name.to_string(), default_value);
} else {
tracing::trace!(
field = field.name,
shape = shape.type_identifier,
"fill_defaults_from_shape_recursive: field has unserializable default, letting facet handle it"
);
}
}
}
for (key, val) in new_map.iter_mut() {
if let Some(field) = fields.iter().find(|f| f.name == key && !f.is_flattened()) {
let field_path = if path_prefix.is_empty() {
key.to_string()
} else {
format!("{}.{}", path_prefix, key)
};
*val = fill_defaults_from_shape_recursive(val, field.shape.get(), &field_path);
} else {
for field in fields.iter().filter(|f| f.is_flattened()) {
let inner_shape = field.shape.get();
if let Type::User(UserType::Struct(s)) = &inner_shape.ty
&& let Some(inner_field) = s.fields.iter().find(|f| f.name == key)
{
let field_path = if path_prefix.is_empty() {
key.to_string()
} else {
format!("{}.{}", path_prefix, key)
};
*val = fill_defaults_from_shape_recursive(
val,
inner_field.shape.get(),
&field_path,
);
break;
}
}
}
}
let result = ConfigValue::Object(Sourced {
value: new_map,
span: sourced.span,
provenance: sourced.provenance.clone(),
});
tracing::trace!(
shape = shape.type_identifier,
"fill_defaults_from_shape: completed Object"
);
result
}
ConfigValue::Array(sourced) => {
let items: Vec<_> = sourced.value.to_vec();
ConfigValue::Array(Sourced {
value: items,
span: sourced.span,
provenance: sourced.provenance.clone(),
})
}
ConfigValue::Enum(sourced) => {
let enum_type = match &shape.ty {
Type::User(UserType::Enum(e)) => *e,
_ => return value.clone(),
};
let variant = enum_type
.variants
.iter()
.find(|v| v.effective_name() == sourced.value.variant);
let Some(variant) = variant else {
return value.clone();
};
let variant_fields = variant.data.fields;
if variant_fields.is_empty() {
return value.clone();
}
let is_effectively_flattened = |field: &facet_core::Field| -> bool {
if field.is_flattened() {
return true;
}
if field.name.chars().all(|c| c.is_ascii_digit())
&& let Type::User(UserType::Struct(_)) = &field.shape.get().ty
{
return true;
}
false
};
let mut new_fields = sourced.value.fields.clone();
fn collect_flattened_fields_enum<'a>(
fields: &'a [facet_core::Field],
result: &mut Vec<&'a facet_core::Field>,
is_effectively_flattened: &impl Fn(&facet_core::Field) -> bool,
) {
for field in fields.iter() {
if is_effectively_flattened(field) {
let inner_shape = field.shape.get();
if let Type::User(UserType::Struct(s)) = &inner_shape.ty {
collect_flattened_fields_enum(
s.fields,
result,
is_effectively_flattened,
);
}
} else if field.is_flattened() {
let inner_shape = field.shape.get();
if let Type::User(UserType::Struct(s)) = &inner_shape.ty {
collect_flattened_fields_enum(
s.fields,
result,
is_effectively_flattened,
);
}
} else {
result.push(field);
}
}
}
for field in variant_fields.iter() {
if is_effectively_flattened(field) {
let inner_shape = field.shape.get();
if let Type::User(UserType::Struct(s)) = &inner_shape.ty {
let mut all_inner_fields = Vec::new();
collect_flattened_fields_enum(
s.fields,
&mut all_inner_fields,
&is_effectively_flattened,
);
for inner_field in all_inner_fields {
if !new_fields.contains_key(inner_field.name) {
let field_path = if path_prefix.is_empty() {
inner_field.name.to_string()
} else {
format!("{}.{}", path_prefix, inner_field.name)
};
if let Some(default_value) =
get_default_config_value(inner_field, &field_path)
{
new_fields.insert(inner_field.name.to_string(), default_value);
}
}
}
}
continue;
}
if !new_fields.contains_key(field.name) {
let field_path = if path_prefix.is_empty() {
field.name.to_string()
} else {
format!("{}.{}", path_prefix, field.name)
};
if let Some(default_value) = get_default_config_value(field, &field_path) {
new_fields.insert(field.name.to_string(), default_value);
}
}
}
for (key, val) in new_fields.iter_mut() {
if let Some(field) = variant_fields
.iter()
.find(|f| f.name == key && !is_effectively_flattened(f))
{
let field_path = if path_prefix.is_empty() {
key.to_string()
} else {
format!("{}.{}", path_prefix, key)
};
*val = fill_defaults_from_shape_recursive(val, field.shape.get(), &field_path);
} else {
for field in variant_fields
.iter()
.filter(|f| is_effectively_flattened(f))
{
let inner_shape = field.shape.get();
if let Type::User(UserType::Struct(s)) = &inner_shape.ty
&& let Some(inner_field) = s.fields.iter().find(|f| f.name == key)
{
let field_path = if path_prefix.is_empty() {
key.to_string()
} else {
format!("{}.{}", path_prefix, key)
};
*val = fill_defaults_from_shape_recursive(
val,
inner_field.shape.get(),
&field_path,
);
break;
}
}
}
}
ConfigValue::Enum(Sourced {
value: crate::config_value::EnumValue {
variant: sourced.value.variant.clone(),
fields: new_fields,
},
span: sourced.span,
provenance: sourced.provenance.clone(),
})
}
_ => value.clone(),
}
}
fn get_default_config_value(
field: &'static facet_core::Field,
_path_prefix: &str,
) -> Option<ConfigValue> {
use facet_core::ScalarType;
let shape = field.shape.get();
if let Some(default_source) = &field.default {
tracing::trace!(
field = field.name,
"get_default_config_value: field has default, invoking"
);
if let Ok(config_value) = serialize_default_to_config_value(default_source, shape) {
if !matches!(config_value, ConfigValue::Null(_)) {
tracing::trace!(
field = field.name,
?config_value,
"get_default_config_value: successfully serialized default"
);
return Some(config_value);
}
tracing::trace!(
field = field.name,
"get_default_config_value: serialized to null, letting facet handle default"
);
} else {
tracing::trace!(
field = field.name,
"get_default_config_value: serialization failed, letting facet handle default"
);
}
return None;
}
if shape.type_identifier.contains("Option") {
return Some(ConfigValue::Null(Sourced {
value: (),
span: None,
provenance: Some(Provenance::Default),
}));
}
if let Type::User(UserType::Struct(_)) = &shape.ty {
return Some(ConfigValue::Object(Sourced {
value: IndexMap::default(),
span: None,
provenance: Some(Provenance::Default),
}));
}
if let Some(scalar_type) = shape.scalar_type() {
return Some(match scalar_type {
ScalarType::Bool => ConfigValue::Bool(Sourced {
value: false,
span: None,
provenance: Some(Provenance::Default),
}),
ScalarType::U8
| ScalarType::U16
| ScalarType::U32
| ScalarType::U64
| ScalarType::U128
| ScalarType::USize
| ScalarType::I8
| ScalarType::I16
| ScalarType::I32
| ScalarType::I64
| ScalarType::I128
| ScalarType::ISize => ConfigValue::Integer(Sourced {
value: 0,
span: None,
provenance: Some(Provenance::Default),
}),
_ => {
return None;
}
});
}
None
}
#[allow(unsafe_code)]
pub(crate) fn serialize_default_to_config_value(
default_source: &facet_core::DefaultSource,
shape: &'static facet_core::Shape,
) -> Result<ConfigValue, String> {
use facet_core::{DefaultSource, TypeOps};
let mut storage: [u8; 1024] = [0; 1024]; let ptr = storage.as_mut_ptr() as *mut ();
match default_source {
DefaultSource::FromTrait => {
let type_ops = shape
.type_ops
.ok_or_else(|| format!("Shape {} has no type_ops", shape.type_identifier))?;
match type_ops {
TypeOps::Direct(ops) => {
let default_fn = ops.default_in_place.ok_or_else(|| {
format!("Shape {} has no default function", shape.type_identifier)
})?;
unsafe { default_fn(ptr) };
}
TypeOps::Indirect(_) => {
Err(
"Indirect type ops not yet supported for default serialization".to_string(),
)?;
}
}
}
DefaultSource::Custom(fn_ptr) => {
unsafe {
let ptr_uninit = facet_core::PtrUninit::new_sized(ptr);
(*fn_ptr)(ptr_uninit);
}
}
}
let peek = unsafe {
let ptr_const = facet_core::PtrConst::new_sized(ptr as *const ());
facet_reflect::Peek::unchecked_new(ptr_const, shape)
};
let mut serializer = ConfigValueSerializer::new();
facet_format::serialize_root(&mut serializer, peek)
.map_err(|e| format!("Serialization failed: {:?}", e))?;
let result = serializer.finish();
if matches!(result, ConfigValue::Null(_)) && shape.vtable.has_display() {
let string_value = peek.to_string();
return Ok(ConfigValue::String(Sourced {
value: string_value,
span: None,
provenance: Some(Provenance::Default),
}));
}
Ok(result)
}
#[derive(Debug)]
pub enum ConfigValueDeserializeError {
Deserialize(facet_format::DeserializeError),
}
impl core::fmt::Display for ConfigValueDeserializeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ConfigValueDeserializeError::Deserialize(e) => {
write!(f, "{}", e.kind)?;
if let Some(ref path) = e.path
&& !path.is_empty()
{
write!(f, " at {}", path)?;
}
Ok(())
}
}
}
}
impl core::error::Error for ConfigValueDeserializeError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
ConfigValueDeserializeError::Deserialize(e) => Some(e),
}
}
}
impl ConfigValueDeserializeError {
pub fn span(&self) -> Option<facet_reflect::Span> {
match self {
ConfigValueDeserializeError::Deserialize(e) => e.span,
}
}
}
#[derive(Clone)]
pub struct ConfigValueParser<'input> {
stack: Vec<StackFrame<'input>>,
last_span: Option<Span>,
peeked: Option<ParseEvent<'input>>,
saved_state: Option<Box<ConfigValueParser<'input>>>,
}
#[derive(Clone)]
enum StackFrame<'input> {
Object {
entries: Vec<(&'input str, &'input ConfigValue)>,
index: usize,
},
Array {
items: &'input [ConfigValue],
index: usize,
},
Value(&'input ConfigValue),
Enum {
variant: &'input str,
fields: Vec<(&'input str, &'input ConfigValue)>,
phase: u8,
is_unit: bool,
},
}
impl<'input> ConfigValueParser<'input> {
pub fn new(value: &'input ConfigValue) -> Self {
Self {
stack: vec![StackFrame::Value(value)],
last_span: None,
peeked: None,
saved_state: None,
}
}
fn update_span<T>(&mut self, sourced: &Sourced<T>) {
if let Some(span) = sourced.span {
self.last_span = Some(span);
}
}
}
impl<'input> FormatParser<'input> for ConfigValueParser<'input> {
fn next_event(&mut self) -> Result<Option<ParseEvent<'input>>, ParseError> {
if let Some(event) = self.peeked.take() {
return Ok(Some(event));
}
loop {
let frame = match self.stack.pop() {
Some(f) => f,
None => return Ok(None), };
match frame {
StackFrame::Value(value) => {
return Ok(Some(self.emit_value(value)));
}
StackFrame::Object { entries, index } => {
if index < entries.len() {
let (key, value) = entries[index];
self.stack.push(StackFrame::Object {
entries: entries.clone(),
index: index + 1,
});
self.stack.push(StackFrame::Value(value));
return Ok(Some(self.event(ParseEventKind::FieldKey(FieldKey::new(
key,
FieldLocationHint::KeyValue,
)))));
} else {
return Ok(Some(self.event(ParseEventKind::StructEnd)));
}
}
StackFrame::Array { items, index } => {
if index < items.len() {
self.stack.push(StackFrame::Array {
items,
index: index + 1,
});
self.stack.push(StackFrame::Value(&items[index]));
continue;
} else {
return Ok(Some(self.event(ParseEventKind::SequenceEnd)));
}
}
StackFrame::Enum {
variant,
fields,
phase,
is_unit,
} => {
match phase {
0 => {
self.stack.push(StackFrame::Enum {
variant,
fields,
phase: 1,
is_unit,
});
return Ok(Some(self.event(ParseEventKind::FieldKey(FieldKey::new(
variant,
FieldLocationHint::KeyValue,
)))));
}
1 => {
self.stack.push(StackFrame::Enum {
variant,
fields: Vec::new(), phase: 2,
is_unit: true, });
if is_unit {
return Ok(Some(
self.event(ParseEventKind::Scalar(ScalarValue::Unit)),
));
} else {
self.stack.push(StackFrame::Object {
entries: fields,
index: 0,
});
return Ok(Some(
self.event(ParseEventKind::StructStart(ContainerKind::Object)),
));
}
}
_ => {
return Ok(Some(self.event(ParseEventKind::StructEnd)));
}
}
}
}
}
}
fn peek_event(&mut self) -> Result<Option<ParseEvent<'input>>, ParseError> {
if self.peeked.is_none() {
self.peeked = self.next_event()?;
}
Ok(self.peeked.clone())
}
fn skip_value(&mut self) -> Result<(), ParseError> {
self.next_event()?;
Ok(())
}
fn save(&mut self) -> SavePoint {
let mut clone = self.clone();
clone.saved_state = None;
self.saved_state = Some(Box::new(clone));
SavePoint(0)
}
fn restore(&mut self, _save_point: SavePoint) {
if let Some(saved) = self.saved_state.take() {
*self = *saved;
}
}
fn current_span(&self) -> Option<Span> {
self.last_span
}
}
impl<'input> ConfigValueParser<'input> {
fn event(&self, kind: ParseEventKind<'input>) -> ParseEvent<'input> {
ParseEvent::new(kind, self.last_span.unwrap_or_default())
}
fn emit_value(&mut self, value: &'input ConfigValue) -> ParseEvent<'input> {
match value {
ConfigValue::Null(sourced) => {
self.update_span(sourced);
self.event(ParseEventKind::Scalar(ScalarValue::Null))
}
ConfigValue::Bool(sourced) => {
self.update_span(sourced);
self.event(ParseEventKind::Scalar(ScalarValue::Bool(sourced.value)))
}
ConfigValue::Integer(sourced) => {
self.update_span(sourced);
self.event(ParseEventKind::Scalar(ScalarValue::I64(sourced.value)))
}
ConfigValue::Float(sourced) => {
self.update_span(sourced);
self.event(ParseEventKind::Scalar(ScalarValue::F64(sourced.value)))
}
ConfigValue::String(sourced) => {
self.update_span(sourced);
self.event(ParseEventKind::Scalar(ScalarValue::Str(
std::borrow::Cow::Borrowed(&sourced.value),
)))
}
ConfigValue::Array(sourced) => {
self.update_span(sourced);
self.stack.push(StackFrame::Array {
items: &sourced.value,
index: 0,
});
self.event(ParseEventKind::SequenceStart(ContainerKind::Array))
}
ConfigValue::Object(sourced) => {
self.update_span(sourced);
let entries: Vec<(&str, &ConfigValue)> =
sourced.value.iter().map(|(k, v)| (k.as_str(), v)).collect();
self.stack.push(StackFrame::Object { entries, index: 0 });
self.event(ParseEventKind::StructStart(ContainerKind::Object))
}
ConfigValue::Enum(sourced) => {
self.update_span(sourced);
let fields: Vec<(&str, &ConfigValue)> = sourced
.value
.fields
.iter()
.map(|(k, v)| (k.as_str(), v))
.collect();
let is_unit = false;
self.stack.push(StackFrame::Enum {
variant: &sourced.value.variant,
fields,
phase: 0,
is_unit,
});
self.event(ParseEventKind::StructStart(ContainerKind::Object))
}
}
}
}
pub struct ConfigValueSerializer {
stack: Vec<BuildFrame>,
}
impl Default for ConfigValueSerializer {
fn default() -> Self {
Self {
stack: vec![BuildFrame::Root(None)],
}
}
}
enum BuildFrame {
Root(Option<ConfigValue>),
Object {
map: IndexMap<String, ConfigValue, std::hash::RandomState>,
},
Array {
items: Vec<ConfigValue>,
},
PendingField {
map: IndexMap<String, ConfigValue, std::hash::RandomState>,
key: String,
},
}
impl ConfigValueSerializer {
pub fn new() -> Self {
Default::default()
}
pub fn finish(mut self) -> ConfigValue {
match self.stack.pop() {
Some(BuildFrame::Root(Some(value))) if self.stack.is_empty() => value,
Some(BuildFrame::Root(None)) if self.stack.is_empty() => {
ConfigValue::Null(Sourced {
value: (),
span: None,
provenance: Some(Provenance::Default),
})
}
_ => panic!("Serializer finished in unexpected state"),
}
}
fn attach_value(&mut self, value: ConfigValue) -> Result<(), String> {
let parent = self.stack.pop().ok_or("Stack underflow")?;
match parent {
BuildFrame::Root(None) => {
self.stack.push(BuildFrame::Root(Some(value)));
Ok(())
}
BuildFrame::Root(Some(_)) => Err("Root already has a value")?,
BuildFrame::PendingField { mut map, key } => {
map.insert(key, value);
self.stack.push(BuildFrame::Object { map });
Ok(())
}
BuildFrame::Array { mut items } => {
items.push(value);
self.stack.push(BuildFrame::Array { items });
Ok(())
}
BuildFrame::Object { .. } => {
Err("Cannot attach value directly to Object without field_key")?
}
}
}
}
impl facet_format::FormatSerializer for ConfigValueSerializer {
type Error = String;
fn begin_struct(&mut self) -> Result<(), Self::Error> {
self.stack.push(BuildFrame::Object {
map: IndexMap::default(),
});
Ok(())
}
fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
let frame = self.stack.pop().ok_or("Stack underflow")?;
match frame {
BuildFrame::Object { map } => {
self.stack.push(BuildFrame::PendingField {
map,
key: key.to_string(),
});
Ok(())
}
_ => Err("field_key called outside of struct")?,
}
}
fn end_struct(&mut self) -> Result<(), Self::Error> {
let frame = self.stack.pop().ok_or("Stack underflow")?;
match frame {
BuildFrame::Object { map } => {
let value = ConfigValue::Object(Sourced {
value: map,
span: None,
provenance: Some(Provenance::Default),
});
self.attach_value(value)
}
_ => Err("end_struct called without matching begin_struct")?,
}
}
fn begin_seq(&mut self) -> Result<(), Self::Error> {
self.stack.push(BuildFrame::Array { items: Vec::new() });
Ok(())
}
fn end_seq(&mut self) -> Result<(), Self::Error> {
let frame = self.stack.pop().ok_or("Stack underflow")?;
match frame {
BuildFrame::Array { items } => {
let value = ConfigValue::Array(Sourced {
value: items,
span: None,
provenance: Some(Provenance::Default),
});
self.attach_value(value)
}
_ => Err("end_seq called without matching begin_seq")?,
}
}
fn scalar(&mut self, value: facet_format::ScalarValue) -> Result<(), Self::Error> {
use facet_format::ScalarValue;
let config_value = match value {
ScalarValue::Unit | ScalarValue::Null => ConfigValue::Null(Sourced {
value: (),
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::Bool(b) => ConfigValue::Bool(Sourced {
value: b,
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::I64(i) => ConfigValue::Integer(Sourced {
value: i,
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::U64(u) => ConfigValue::Integer(Sourced {
value: u as i64,
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::I128(i) => ConfigValue::Integer(Sourced {
value: i as i64,
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::U128(u) => ConfigValue::Integer(Sourced {
value: u as i64,
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::F64(f) => ConfigValue::Float(Sourced {
value: f,
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::Char(c) => ConfigValue::String(Sourced {
value: c.to_string(),
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::Str(s) => ConfigValue::String(Sourced {
value: s.into_owned(),
span: None,
provenance: Some(Provenance::Default),
}),
ScalarValue::Bytes(_) => {
return Err("Bytes not supported in ConfigValue")?;
}
};
self.attach_value(config_value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::string::ToString;
#[test]
fn test_parse_null() {
let value = ConfigValue::Null(Sourced::new(()));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::Null),
..
})
));
let event = parser.next_event().unwrap();
assert!(event.is_none());
}
#[test]
fn test_parse_bool() {
let value = ConfigValue::Bool(Sourced::new(true));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::Bool(true)),
..
})
));
}
#[test]
fn test_parse_integer() {
let value = ConfigValue::Integer(Sourced::new(42));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::I64(42)),
..
})
));
}
#[test]
fn test_parse_string() {
let value = ConfigValue::String(Sourced::new("hello".to_string()));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
if let Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::Str(s)),
..
}) = event
{
assert_eq!(s.as_ref(), "hello");
} else {
panic!("expected string scalar");
}
}
#[test]
fn test_parse_empty_array() {
let value = ConfigValue::Array(Sourced::new(vec![]));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::SequenceStart(ContainerKind::Array),
..
})
));
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::SequenceEnd,
..
})
));
let event = parser.next_event().unwrap();
assert!(event.is_none());
}
#[test]
fn test_parse_array_with_items() {
let value = ConfigValue::Array(Sourced::new(vec![
ConfigValue::Integer(Sourced::new(1)),
ConfigValue::Integer(Sourced::new(2)),
ConfigValue::Integer(Sourced::new(3)),
]));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::SequenceStart(_),
..
})
));
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::I64(1)),
..
})
));
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::I64(2)),
..
})
));
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::I64(3)),
..
})
));
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::SequenceEnd,
..
})
));
let event = parser.next_event().unwrap();
assert!(event.is_none());
}
#[test]
fn test_parse_empty_object() {
let value = ConfigValue::Object(Sourced::new(indexmap::IndexMap::default()));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::StructStart(ContainerKind::Object),
..
})
));
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::StructEnd,
..
})
));
let event = parser.next_event().unwrap();
assert!(event.is_none());
}
#[test]
fn test_parse_object_with_fields() {
let mut map = IndexMap::default();
map.insert(
"name".to_string(),
ConfigValue::String(Sourced::new("Alice".to_string())),
);
map.insert("age".to_string(), ConfigValue::Integer(Sourced::new(30)));
let value = ConfigValue::Object(Sourced::new(map));
let mut parser = ConfigValueParser::new(&value);
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::StructStart(_),
..
})
));
let event = parser.next_event().unwrap();
if let Some(ParseEvent {
kind: ParseEventKind::FieldKey(key),
..
}) = event
{
assert_eq!(key.name().as_ref().map(|c| c.as_ref()), Some("name"));
} else {
panic!("expected FieldKey");
}
let event = parser.next_event().unwrap();
if let Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::Str(s)),
..
}) = event
{
assert_eq!(s.as_ref(), "Alice");
} else {
panic!("expected string value");
}
let event = parser.next_event().unwrap();
if let Some(ParseEvent {
kind: ParseEventKind::FieldKey(key),
..
}) = event
{
assert_eq!(key.name().as_ref().map(|c| c.as_ref()), Some("age"));
} else {
panic!("expected FieldKey");
}
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::Scalar(ScalarValue::I64(30)),
..
})
));
let event = parser.next_event().unwrap();
assert!(matches!(
event,
Some(ParseEvent {
kind: ParseEventKind::StructEnd,
..
})
));
let event = parser.next_event().unwrap();
assert!(event.is_none());
}
#[test]
fn test_from_config_value_simple() {
use facet::Facet;
#[derive(Debug, Facet, PartialEq)]
struct SimpleConfig {
port: i64,
enabled: bool,
}
let mut map = IndexMap::default();
map.insert("port".to_string(), ConfigValue::Integer(Sourced::new(8080)));
map.insert("enabled".to_string(), ConfigValue::Bool(Sourced::new(true)));
let value = ConfigValue::Object(Sourced::new(map));
let config: SimpleConfig = from_config_value(&value).expect("should deserialize");
assert_eq!(config.port, 8080);
assert!(config.enabled);
}
#[test]
fn test_from_config_value_nested() {
use facet::Facet;
#[derive(Debug, Facet, PartialEq)]
struct SmtpConfig {
host: String,
port: i64,
}
#[derive(Debug, Facet, PartialEq)]
struct ServerConfig {
port: i64,
smtp: SmtpConfig,
}
let mut smtp_map = IndexMap::default();
smtp_map.insert(
"host".to_string(),
ConfigValue::String(Sourced::new("smtp.example.com".to_string())),
);
smtp_map.insert("port".to_string(), ConfigValue::Integer(Sourced::new(587)));
let mut server_map = IndexMap::default();
server_map.insert("port".to_string(), ConfigValue::Integer(Sourced::new(8080)));
server_map.insert(
"smtp".to_string(),
ConfigValue::Object(Sourced::new(smtp_map)),
);
let value = ConfigValue::Object(Sourced::new(server_map));
let config: ServerConfig = from_config_value(&value).expect("should deserialize");
assert_eq!(config.port, 8080);
assert_eq!(config.smtp.host, "smtp.example.com");
assert_eq!(config.smtp.port, 587);
}
#[test]
fn test_fill_defaults_simple_struct() {
use facet::Facet;
#[derive(Debug, Facet, PartialEq)]
struct Config {
#[facet(default)]
enabled: bool,
port: i64,
}
let mut map = IndexMap::default();
map.insert("port".to_string(), ConfigValue::Integer(Sourced::new(8080)));
let input = ConfigValue::Object(Sourced::new(map));
let result = fill_defaults_from_shape(&input, Config::SHAPE);
if let ConfigValue::Object(obj) = result {
assert!(obj.value.contains_key("port"), "should have port");
assert!(
obj.value.contains_key("enabled"),
"should have enabled with default"
);
if let ConfigValue::Bool(b) = &obj.value["enabled"] {
assert!(!b.value, "enabled default should be false");
} else {
panic!("enabled should be a bool");
}
} else {
panic!("expected object");
}
}
#[test]
fn test_fill_defaults_with_flatten() {
use facet::Facet;
#[derive(Debug, Facet, PartialEq, Default)]
struct CommonOpts {
#[facet(default)]
verbose: bool,
#[facet(default)]
debug: bool,
}
#[derive(Debug, Facet, PartialEq)]
struct Config {
name: String,
#[facet(flatten)]
common: CommonOpts,
}
let mut map = IndexMap::default();
map.insert(
"name".to_string(),
ConfigValue::String(Sourced::new("test".to_string())),
);
let input = ConfigValue::Object(Sourced::new(map));
let result = fill_defaults_from_shape(&input, Config::SHAPE);
if let ConfigValue::Object(obj) = result {
assert!(obj.value.contains_key("name"), "should have name");
assert!(
obj.value.contains_key("verbose"),
"should have verbose at top level (flattened)"
);
assert!(
obj.value.contains_key("debug"),
"should have debug at top level (flattened)"
);
assert!(
!obj.value.contains_key("common"),
"should NOT have nested 'common' object"
);
} else {
panic!("expected object");
}
}
#[test]
fn test_fill_defaults_flatten_preserves_existing() {
use facet::Facet;
#[derive(Debug, Facet, PartialEq, Default)]
struct CommonOpts {
#[facet(default)]
verbose: bool,
#[facet(default)]
debug: bool,
}
#[derive(Debug, Facet, PartialEq)]
struct Config {
name: String,
#[facet(flatten)]
common: CommonOpts,
}
let mut map = IndexMap::default();
map.insert(
"name".to_string(),
ConfigValue::String(Sourced::new("test".to_string())),
);
map.insert("verbose".to_string(), ConfigValue::Bool(Sourced::new(true)));
let input = ConfigValue::Object(Sourced::new(map));
let result = fill_defaults_from_shape(&input, Config::SHAPE);
if let ConfigValue::Object(obj) = result {
if let ConfigValue::Bool(b) = &obj.value["verbose"] {
assert!(b.value, "verbose should be true (preserved)");
}
assert!(obj.value.contains_key("debug"), "should have debug");
if let ConfigValue::Bool(b) = &obj.value["debug"] {
assert!(!b.value, "debug should be false (default)");
}
} else {
panic!("expected object");
}
}
#[test]
fn test_fill_defaults_subcommand_with_flatten() {
use facet::Facet;
#[derive(Debug, Facet, PartialEq, Default)]
struct Builtins {
#[facet(default)]
help: bool,
#[facet(default)]
version: bool,
}
#[derive(Debug, Facet, PartialEq)]
#[repr(u8)]
enum Command {
Build { release: bool },
}
#[derive(Debug, Facet)]
struct Args {
command: Command,
#[facet(flatten)]
builtins: Builtins,
}
let mut fields = IndexMap::default();
fields.insert("release".to_string(), ConfigValue::Bool(Sourced::new(true)));
let enum_value = ConfigValue::Enum(Sourced::new(crate::config_value::EnumValue {
variant: "Build".to_string(),
fields,
}));
let mut map = IndexMap::default();
map.insert("command".to_string(), enum_value);
let input = ConfigValue::Object(Sourced::new(map));
let result = fill_defaults_from_shape(&input, Args::SHAPE);
if let ConfigValue::Object(obj) = result {
assert!(obj.value.contains_key("command"), "should have command");
assert!(
obj.value.contains_key("help"),
"should have help at top level"
);
assert!(
obj.value.contains_key("version"),
"should have version at top level"
);
assert!(
!obj.value.contains_key("builtins"),
"should NOT have nested 'builtins'"
);
} else {
panic!("expected object");
}
}
#[facet_testhelpers::test]
fn test_full_deserialization_with_flatten() {
use facet::Facet;
#[derive(Debug, Facet, PartialEq, Default)]
struct CommonOpts {
#[facet(default)]
verbose: bool,
}
#[derive(Debug, Facet, PartialEq)]
struct Config {
name: String,
#[facet(flatten)]
common: CommonOpts,
}
let mut map = indexmap::IndexMap::default();
map.insert(
"name".to_string(),
ConfigValue::String(Sourced::new("test".to_string())),
);
map.insert("verbose".to_string(), ConfigValue::Bool(Sourced::new(true)));
let input = ConfigValue::Object(Sourced::new(map));
let config: Config = from_config_value(&input).expect("should deserialize");
assert_eq!(config.name, "test");
assert!(config.common.verbose);
}
}
#[cfg(test)]
mod fill_defaults_tests {
use super::*;
use crate::config_value::Sourced;
use facet::Facet;
#[derive(Facet, Debug)]
struct SimpleStruct {
#[facet(default)]
flag: bool,
#[facet(default)]
count: i64,
}
#[test]
fn test_fill_defaults_simple_struct() {
let input = ConfigValue::Object(Sourced::new(IndexMap::default()));
let result = fill_defaults_from_shape(&input, SimpleStruct::SHAPE);
if let ConfigValue::Object(obj) = result {
assert!(obj.value.contains_key("flag"), "should have flag");
assert!(obj.value.contains_key("count"), "should have count");
} else {
panic!("expected object");
}
}
#[derive(Facet, Debug, Default)]
struct Inner {
#[facet(default)]
inner_flag: bool,
}
#[derive(Facet, Debug)]
struct StructWithFlatten {
#[facet(default)]
outer_flag: bool,
#[facet(flatten)]
inner: Inner,
}
#[test]
fn test_fill_defaults_flattened_struct() {
let input = ConfigValue::Object(Sourced::new(indexmap::IndexMap::default()));
let result = fill_defaults_from_shape(&input, StructWithFlatten::SHAPE);
if let ConfigValue::Object(obj) = result {
assert!(
obj.value.contains_key("outer_flag"),
"should have outer_flag at top level"
);
assert!(
obj.value.contains_key("inner_flag"),
"should have inner_flag at top level (flattened)"
);
assert!(
!obj.value.contains_key("inner"),
"should NOT have nested 'inner' object"
);
} else {
panic!("expected object");
}
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum EnumWithStructVariant {
Variant {
#[facet(default)]
field: bool,
},
}
#[test]
fn test_fill_defaults_enum_struct_variant() {
let fields = IndexMap::default();
let input = ConfigValue::Enum(Sourced::new(crate::config_value::EnumValue {
variant: "Variant".to_string(),
fields,
}));
let result = fill_defaults_from_shape(&input, EnumWithStructVariant::SHAPE);
if let ConfigValue::Enum(e) = result {
assert!(e.value.fields.contains_key("field"), "should have field");
} else {
panic!("expected enum");
}
}
#[derive(Facet, Debug, Default)]
struct TuplePayload {
#[facet(default)]
payload_flag: bool,
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum EnumWithTupleVariant {
TupleVar(TuplePayload),
}
#[test]
fn test_fill_defaults_enum_tuple_variant() {
let fields = IndexMap::default();
let input = ConfigValue::Enum(Sourced::new(crate::config_value::EnumValue {
variant: "TupleVar".to_string(),
fields,
}));
let result = fill_defaults_from_shape(&input, EnumWithTupleVariant::SHAPE);
if let ConfigValue::Enum(e) = result {
assert!(
e.value.fields.contains_key("payload_flag"),
"should have payload_flag at top level"
);
assert!(
!e.value.fields.contains_key("0"),
"should NOT have '0' wrapper"
);
} else {
panic!("expected enum");
}
}
#[derive(Facet, Debug, Default)]
struct FlattenedOpts {
#[facet(default)]
opt_a: bool,
#[facet(default)]
opt_b: bool,
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum EnumWithFlattenInVariant {
Cmd {
#[facet(flatten)]
opts: FlattenedOpts,
#[facet(default)]
other: bool,
},
}
#[test]
fn test_fill_defaults_enum_flatten_in_variant() {
let fields = IndexMap::default();
let input = ConfigValue::Enum(Sourced::new(crate::config_value::EnumValue {
variant: "Cmd".to_string(),
fields,
}));
let result = fill_defaults_from_shape(&input, EnumWithFlattenInVariant::SHAPE);
if let ConfigValue::Enum(e) = result {
assert!(
e.value.fields.contains_key("opt_a"),
"should have opt_a (flattened)"
);
assert!(
e.value.fields.contains_key("opt_b"),
"should have opt_b (flattened)"
);
assert!(e.value.fields.contains_key("other"), "should have other");
assert!(
!e.value.fields.contains_key("opts"),
"should NOT have 'opts' wrapper"
);
} else {
panic!("expected enum");
}
}
#[derive(Facet, Debug, Default)]
struct DeepInner {
#[facet(default)]
deep_flag: bool,
}
#[derive(Facet, Debug, Default)]
struct MiddleLayer {
#[facet(default)]
middle_flag: bool,
#[facet(flatten)]
deep: DeepInner,
}
#[derive(Facet, Debug)]
struct DeepFlattenStruct {
#[facet(default)]
top_flag: bool,
#[facet(flatten)]
middle: MiddleLayer,
}
#[test]
fn test_fill_defaults_deep_flatten() {
let input = ConfigValue::Object(Sourced::new(IndexMap::default()));
let result = fill_defaults_from_shape(&input, DeepFlattenStruct::SHAPE);
if let ConfigValue::Object(obj) = result {
eprintln!("Result keys: {:?}", obj.value.keys().collect::<Vec<_>>());
assert!(obj.value.contains_key("top_flag"), "should have top_flag");
assert!(
obj.value.contains_key("middle_flag"),
"should have middle_flag (flattened)"
);
assert!(
obj.value.contains_key("deep_flag"),
"should have deep_flag (double flattened)"
);
assert!(
!obj.value.contains_key("middle"),
"should NOT have 'middle' wrapper"
);
assert!(
!obj.value.contains_key("deep"),
"should NOT have 'deep' wrapper"
);
} else {
panic!("expected object");
}
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum RenamedVariantEnum {
#[facet(rename = "ls")]
List {
#[facet(default)]
all: bool,
},
}
#[test]
fn test_fill_defaults_renamed_variant() {
let fields = IndexMap::default();
let input = ConfigValue::Enum(Sourced::new(crate::config_value::EnumValue {
variant: "ls".to_string(), fields,
}));
let result = fill_defaults_from_shape(&input, RenamedVariantEnum::SHAPE);
if let ConfigValue::Enum(e) = result {
assert_eq!(e.value.variant, "ls", "variant name should be preserved");
assert!(e.value.fields.contains_key("all"), "should have all field");
} else {
panic!("expected enum");
}
}
}