use crate::backends::swift::gen_rust_crate::type_bridge::enum_from_string_fn_name;
use crate::codegen::generators::type_paths::resolve_type_path;
use crate::core::ir::EnumDef;
use std::collections::HashMap;
pub(crate) fn emit_enum_wrapper(en: &EnumDef, source_crate: &str, type_paths: &HashMap<String, String>) -> String {
let mut out = String::new();
let source_path = resolve_type_path(&en.name, source_crate, type_paths);
out.push_str(&crate::backends::swift::template_env::render(
"enum_unit_header.jinja",
minijinja::context! {
name => &en.name,
},
));
for variant in &en.variants {
out.push_str(&crate::backends::swift::template_env::render(
"enum_unit_variant.jinja",
minijinja::context! {
variant_name => &variant.name,
},
));
}
out.push_str("}\n\n");
out.push_str(&crate::backends::swift::template_env::render(
"enum_from_impl_header.jinja",
minijinja::context! {
source_path => &source_path,
name => &en.name,
},
));
out.push_str(" match val {\n");
let has_cfg_variants = en.variants.iter().any(|v| v.cfg.is_some());
for variant in &en.variants {
let pattern = if variant.fields.is_empty() {
variant.name.clone()
} else if variant.is_tuple {
format!("{}(..)", variant.name)
} else {
format!("{} {{ .. }}", variant.name)
};
if let Some(condition) = variant.cfg.as_deref() {
out.push_str(" #[cfg(");
out.push_str(condition);
out.push_str(")]\n");
}
out.push_str(&crate::backends::swift::template_env::render(
"enum_from_variant.jinja",
minijinja::context! {
source_path => &source_path,
variant_name => &variant.name,
pattern => pattern,
},
));
}
if !en.excluded_variants.is_empty() || has_cfg_variants {
out.push_str(&format!(
" _ => unreachable!(\"bridge enum variant of {} not exposed in binding\"),\n",
en.name
));
}
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str("}\n\n");
let mut variants = String::new();
for variant in &en.variants {
let serde_name = serde_variant_wire_name(variant, en.serde_rename_all.as_deref());
variants.push_str(&crate::backends::swift::template_env::render(
"rust_enum_to_string_variant.rs.jinja",
minijinja::context! {
variant_name => &variant.name,
serde_name => &serde_name,
},
));
}
out.push_str(&crate::backends::swift::template_env::render(
"rust_enum_to_string_impl.rs.jinja",
minijinja::context! {
enum_name => &en.name,
variants => variants,
},
));
let is_unit_enum = en.variants.iter().all(|v| v.fields.is_empty());
if is_unit_enum {
let mut from_string_variants = String::new();
for variant in &en.variants {
let serde_name = serde_variant_wire_name(variant, en.serde_rename_all.as_deref());
from_string_variants.push_str(&crate::backends::swift::template_env::render(
"rust_enum_from_string_variant.rs.jinja",
minijinja::context! {
variant_name => &variant.name,
serde_name => &serde_name,
source_path => &source_path,
},
));
}
out.push_str(&crate::backends::swift::template_env::render(
"rust_enum_from_string_impl.rs.jinja",
minijinja::context! {
fn_name => enum_from_string_fn_name(&en.name),
enum_name => &en.name,
source_path => &source_path,
variants => from_string_variants,
},
));
}
out
}
fn serde_variant_wire_name(variant: &crate::core::ir::EnumVariant, rename_all: Option<&str>) -> String {
crate::codegen::naming::wire_variant_value(&variant.name, variant.serde_rename.as_deref(), rename_all)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{EnumDef, EnumVariant};
fn make_unit_variant(name: &str, cfg: Option<&str>) -> EnumVariant {
EnumVariant {
name: name.to_string(),
cfg: cfg.map(str::to_string),
..Default::default()
}
}
fn make_tuple_variant(name: &str) -> EnumVariant {
EnumVariant {
name: name.to_string(),
fields: vec![crate::core::ir::FieldDef {
name: "0".to_string(),
ty: crate::core::ir::TypeRef::String,
..Default::default()
}],
is_tuple: true,
..Default::default()
}
}
fn make_struct_variant(name: &str) -> EnumVariant {
EnumVariant {
name: name.to_string(),
fields: vec![crate::core::ir::FieldDef {
name: "value".to_string(),
ty: crate::core::ir::TypeRef::String,
..Default::default()
}],
is_tuple: false,
..Default::default()
}
}
#[test]
fn fieldless_enum_still_emits_from_string_helper() {
let en = EnumDef {
name: "Mode".to_string(),
variants: vec![make_unit_variant("Fast", None), make_unit_variant("Thorough", None)],
methods: vec![],
excluded_variants: vec![],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
out.contains("fn __alef_mode_from_swift_string"),
"expected the from-string helper for a fieldless enum, got:\n{out}"
);
assert!(
out.contains("\"Fast\" => mylib::Mode::Fast,"),
"expected a bare unit-variant arm, got:\n{out}"
);
}
#[test]
fn fielded_enum_omits_from_string_helper_entirely() {
let en = EnumDef {
name: "AuthHeaderFormat".to_string(),
variants: vec![make_unit_variant("None", None), make_tuple_variant("ApiKey")],
methods: vec![],
excluded_variants: vec![],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
!out.contains("__alef_auth_header_format_from_swift_string"),
"expected no from-string helper for an enum with a tuple variant, got:\n{out}"
);
assert!(
!out.contains("fn __alef_"),
"expected no from-string helper of any name for an enum with a tuple variant, got:\n{out}"
);
}
#[test]
fn struct_variant_enum_omits_from_string_helper_entirely() {
let en = EnumDef {
name: "CacheBackend".to_string(),
variants: vec![make_unit_variant("Memory", None), make_struct_variant("OpenDal")],
methods: vec![],
excluded_variants: vec![],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
!out.contains("fn __alef_"),
"expected no from-string helper for an enum with a struct variant, got:\n{out}"
);
}
#[test]
fn cfg_gated_variant_emits_catch_all_in_from_impl() {
let en = EnumDef {
name: "ImageOutputFormat".to_string(),
variants: vec![
make_unit_variant("Jpeg", None),
make_unit_variant("Heif", Some(r#"feature = "heic""#)),
],
methods: vec![],
excluded_variants: vec![],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
out.contains("_ => unreachable!"),
"expected catch-all `_ => unreachable!` arm when cfg-gated variant present, got:\n{out}"
);
assert!(
out.contains("ImageOutputFormat"),
"catch-all message must include the enum name, got:\n{out}"
);
}
#[test]
fn no_cfg_or_excluded_variants_does_not_emit_catch_all() {
let en = EnumDef {
name: "SimpleEnum".to_string(),
variants: vec![make_unit_variant("A", None), make_unit_variant("B", None)],
methods: vec![],
excluded_variants: vec![],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
!out.contains("_ => unreachable!"),
"unexpected catch-all arm in From impl for fully-covered enum:\n{out}"
);
}
#[test]
fn excluded_variants_alone_emits_catch_all() {
let en = EnumDef {
name: "ImageOutputFormat".to_string(),
variants: vec![make_unit_variant("Jpeg", None)],
methods: vec![],
excluded_variants: vec![make_unit_variant("ExcludedVariant", None)],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
out.contains("_ => unreachable!"),
"expected catch-all arm when excluded_variants is non-empty, got:\n{out}"
);
}
}