use std::collections::HashSet;
use heck::ToLowerCamelCase;
use crate::backends::dart::ident::dart_safe_ident;
use crate::backends::dart::template_env;
use crate::codegen::naming::wire_variant_value;
use crate::core::ir::EnumDef;
pub(super) fn flat_wire_enums<'a>(enums: &'a [EnumDef], exclude_types: &HashSet<&str>) -> Vec<&'a EnumDef> {
enums
.iter()
.filter(|e| {
!exclude_types.contains(e.name.as_str())
&& !e.binding_excluded
&& !e.variants.is_empty()
&& e.variants.iter().all(|v| v.fields.is_empty())
})
.collect()
}
pub(super) fn emit_wire_value_extensions(enums: &[&EnumDef], out: &mut String) {
for en in enums {
let variants: Vec<minijinja::Value> = en
.variants
.iter()
.map(|v| {
let vname = dart_safe_ident(&v.name.to_lower_camel_case());
let wire = escape_dart_string(&wire_variant_value(
&v.name,
v.serde_rename.as_deref(),
en.serde_rename_all.as_deref(),
));
minijinja::context! { vname => vname, wire => wire }
})
.collect();
out.push_str(&template_env::render(
"enum_wire_value_extension.jinja",
minijinja::context! {
name => en.name.as_str(),
variants => variants,
},
));
out.push('\n');
}
}
fn escape_dart_string(value: &str) -> String {
value.replace('\\', "\\\\").replace('\'', "\\'").replace('$', "\\$")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{EnumVariant, FieldDef, PrimitiveType, TypeRef};
fn unit_variant(name: &str) -> EnumVariant {
EnumVariant {
name: name.to_string(),
..EnumVariant::default()
}
}
fn flat_enum(name: &str, variants: Vec<EnumVariant>) -> EnumDef {
EnumDef {
name: name.to_string(),
variants,
..EnumDef::default()
}
}
#[test]
fn flat_wire_enums_excludes_data_variants() {
let mut with_data = flat_enum("Shape", vec![unit_variant("Circle")]);
with_data.variants.push(EnumVariant {
name: "Rect".to_string(),
fields: vec![FieldDef {
name: "w".to_string(),
ty: TypeRef::Primitive(PrimitiveType::F64),
..FieldDef::default()
}],
..EnumVariant::default()
});
let enums = vec![with_data];
let result = flat_wire_enums(&enums, &HashSet::new());
assert!(
result.is_empty(),
"an enum with a data variant must not be treated as flat"
);
}
#[test]
fn flat_wire_enums_excludes_binding_excluded() {
let mut excluded = flat_enum("Hidden", vec![unit_variant("A")]);
excluded.binding_excluded = true;
let enums = vec![excluded];
assert!(flat_wire_enums(&enums, &HashSet::new()).is_empty());
}
#[test]
fn flat_wire_enums_excludes_configured_exclude_types() {
let enums = vec![flat_enum("Kind", vec![unit_variant("A")])];
let exclude: HashSet<&str> = ["Kind"].into_iter().collect();
assert!(flat_wire_enums(&enums, &exclude).is_empty());
}
#[test]
fn flat_wire_enums_excludes_empty_enums() {
let enums = vec![flat_enum("Empty", vec![])];
assert!(flat_wire_enums(&enums, &HashSet::new()).is_empty());
}
#[test]
fn emit_wire_value_extensions_uses_the_exact_wire_value_with_no_rename_all() {
let enums = [flat_enum(
"DataNodeKind",
vec![unit_variant("KeyValue"), unit_variant("Sequence")],
)];
let refs: Vec<&EnumDef> = enums.iter().collect();
let mut out = String::new();
emit_wire_value_extensions(&refs, &mut out);
assert!(
out.contains("extension DataNodeKindWireValue on DataNodeKind {"),
"got: {out}"
);
assert!(out.contains("case DataNodeKind.keyValue:"), "got: {out}");
assert!(out.contains("return 'KeyValue';"), "got: {out}");
assert!(out.contains("case DataNodeKind.sequence:"), "got: {out}");
assert!(out.contains("return 'Sequence';"), "got: {out}");
}
#[test]
fn emit_wire_value_extensions_honors_rename_all_and_serde_rename() {
let mut en = flat_enum("Status", vec![unit_variant("InProgress"), unit_variant("Done")]);
en.serde_rename_all = Some("kebab-case".to_string());
en.variants[1].serde_rename = Some("finished".to_string());
let enums = [en];
let refs: Vec<&EnumDef> = enums.iter().collect();
let mut out = String::new();
emit_wire_value_extensions(&refs, &mut out);
assert!(out.contains("return 'in-progress';"), "got: {out}");
assert!(out.contains("return 'finished';"), "got: {out}");
}
}