use ahash::AHashSet;
use alef_core::ir::{CoreWrapper, PrimitiveType, TypeDef, TypeRef};
use std::fmt::Write;
use super::ConversionConfig;
use super::binding_to_core::field_conversion_to_core;
use super::helpers::is_newtype;
use super::helpers::{binding_prim_str, core_type_path, needs_i64_cast};
pub fn gen_from_core_to_binding(typ: &TypeDef, core_import: &str, opaque_types: &AHashSet<String>) -> String {
gen_from_core_to_binding_cfg(typ, core_import, opaque_types, &ConversionConfig::default())
}
pub fn gen_from_core_to_binding_cfg(
typ: &TypeDef,
core_import: &str,
opaque_types: &AHashSet<String>,
config: &ConversionConfig,
) -> String {
let core_path = core_type_path(typ, core_import);
let binding_name = format!("{}{}", config.type_name_prefix, typ.name);
let mut out = String::with_capacity(256);
writeln!(out, "impl From<{core_path}> for {binding_name} {{").ok();
writeln!(out, " fn from(val: {core_path}) -> Self {{").ok();
if is_newtype(typ) {
let field = &typ.fields[0];
let inner_expr = match &field.ty {
TypeRef::Named(_) => "val.0.into()".to_string(),
TypeRef::Path => "val.0.to_string_lossy().to_string()".to_string(),
TypeRef::Duration => "val.0.as_millis() as u64".to_string(),
_ => "val.0".to_string(),
};
writeln!(out, " Self {{ _0: {inner_expr} }}").ok();
writeln!(out, " }}").ok();
write!(out, "}}").ok();
return out;
}
let optionalized = config.optionalize_defaults && typ.has_default;
writeln!(out, " Self {{").ok();
for field in &typ.fields {
if !config.exclude_types.is_empty()
&& super::helpers::field_references_excluded_type(&field.ty, config.exclude_types)
{
continue;
}
let base_conversion = field_conversion_from_core_cfg(
&field.name,
&field.ty,
field.optional,
field.sanitized,
opaque_types,
config,
);
let base_conversion = if field.is_boxed && matches!(&field.ty, TypeRef::Named(_)) {
if field.optional {
let src = format!("{}: val.{}.map(Into::into)", field.name, field.name);
let dst = format!("{}: val.{}.map(|v| (*v).into())", field.name, field.name);
if base_conversion == src { dst } else { base_conversion }
} else {
base_conversion.replace(&format!("val.{}", field.name), &format!("(*val.{})", field.name))
}
} else {
base_conversion
};
let base_conversion = if field.newtype_wrapper.is_some() {
match &field.ty {
TypeRef::Optional(_) => {
base_conversion.replace(
&format!("val.{}", field.name),
&format!("val.{}.map(|v| v.0)", field.name),
)
}
TypeRef::Vec(_) => {
base_conversion.replace(
&format!("val.{}", field.name),
&format!("val.{}.iter().map(|v| v.0).collect::<Vec<_>>()", field.name),
)
}
_ if field.optional => base_conversion.replace(
&format!("val.{}", field.name),
&format!("val.{}.map(|v| v.0)", field.name),
),
_ => {
base_conversion.replace(&format!("val.{}", field.name), &format!("val.{}.0", field.name))
}
}
} else {
base_conversion
};
let needs_some_wrap = (optionalized && !field.optional)
|| (config.option_duration_on_defaults
&& typ.has_default
&& !field.optional
&& matches!(field.ty, TypeRef::Duration));
let conversion = if needs_some_wrap {
if let Some(expr) = base_conversion.strip_prefix(&format!("{}: ", field.name)) {
format!("{}: Some({})", field.name, expr)
} else {
base_conversion
}
} else {
base_conversion
};
let conversion = apply_core_wrapper_from_core(
&conversion,
&field.name,
&field.core_wrapper,
&field.vec_inner_core_wrapper,
field.optional,
);
if field.cfg.is_some() {
continue;
}
writeln!(out, " {conversion},").ok();
}
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
write!(out, "}}").ok();
out
}
pub fn field_conversion_from_core(
name: &str,
ty: &TypeRef,
optional: bool,
sanitized: bool,
opaque_types: &AHashSet<String>,
) -> String {
if sanitized {
if let TypeRef::Map(k, v) = ty {
if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
if optional {
return format!(
"{name}: val.{name}.as_ref().map(|m| m.iter().map(|(k, v)| (format!(\"{{:?}}\", k), format!(\"{{:?}}\", v))).collect())"
);
}
return format!(
"{name}: val.{name}.into_iter().map(|(k, v)| (format!(\"{{:?}}\", k), format!(\"{{:?}}\", v))).collect()"
);
}
}
if let TypeRef::Vec(inner) = ty {
if matches!(inner.as_ref(), TypeRef::String) {
if optional {
return format!(
"{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
);
}
return format!("{name}: val.{name}.iter().map(|i| format!(\"{{:?}}\", i)).collect()");
}
}
if let TypeRef::Optional(opt_inner) = ty {
if let TypeRef::Vec(vec_inner) = opt_inner.as_ref() {
if matches!(vec_inner.as_ref(), TypeRef::String) {
return format!(
"{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
);
}
}
}
if matches!(ty, TypeRef::String) {
if optional {
return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{:?}}\", v))");
}
return format!("{name}: format!(\"{{:?}}\", val.{name})");
}
if optional {
return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{:?}}\", v))");
}
return format!("{name}: format!(\"{{:?}}\", val.{name})");
}
match ty {
TypeRef::Duration => {
if optional {
return format!("{name}: val.{name}.map(|d| d.as_millis() as u64)");
}
format!("{name}: val.{name}.as_millis() as u64")
}
TypeRef::Path => {
if optional {
format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
} else {
format!("{name}: val.{name}.to_string_lossy().to_string()")
}
}
TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Path) => {
format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
}
TypeRef::Char => {
if optional {
format!("{name}: val.{name}.map(|c| c.to_string())")
} else {
format!("{name}: val.{name}.to_string()")
}
}
TypeRef::Bytes => {
if optional {
format!("{name}: val.{name}.map(|v| v.to_vec())")
} else {
format!("{name}: val.{name}.to_vec()")
}
}
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if optional {
format!("{name}: val.{name}.map(|v| {n} {{ inner: Arc::new(v) }})")
} else {
format!("{name}: {n} {{ inner: Arc::new(val.{name}) }}")
}
}
TypeRef::Json => {
if optional {
format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
} else {
format!("{name}: val.{name}.to_string()")
}
}
TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
if optional {
format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| i.to_string()).collect())")
} else {
format!("{name}: val.{name}.iter().map(ToString::to_string).collect()")
}
}
TypeRef::Map(k, v) if matches!(v.as_ref(), TypeRef::Json) => {
let k_is_json = matches!(k.as_ref(), TypeRef::Json);
let k_expr = if k_is_json { "k.to_string()" } else { "k" };
if optional {
format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| ({k_expr}, v.to_string())).collect())")
} else {
format!("{name}: val.{name}.into_iter().map(|(k, v)| ({k_expr}, v.to_string())).collect()")
}
}
TypeRef::Map(k, _v) if matches!(k.as_ref(), TypeRef::Json) => {
if optional {
format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v)).collect())")
} else {
format!("{name}: val.{name}.into_iter().map(|(k, v)| (k.to_string(), v)).collect()")
}
}
_ => field_conversion_to_core(name, ty, optional),
}
}
pub fn field_conversion_from_core_cfg(
name: &str,
ty: &TypeRef,
optional: bool,
sanitized: bool,
opaque_types: &AHashSet<String>,
config: &ConversionConfig,
) -> String {
if sanitized {
if config.map_uses_jsvalue {
if let TypeRef::Map(k, v) = ty {
if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
if optional {
return format!(
"{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())"
);
}
return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
}
}
if let TypeRef::Vec(inner) = ty {
if matches!(inner.as_ref(), TypeRef::Json) {
if optional {
return format!(
"{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())"
);
}
return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
}
}
}
return field_conversion_from_core(name, ty, optional, sanitized, opaque_types);
}
if config.map_uses_jsvalue {
let is_nested_vec = matches!(ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Vec(_)));
let is_map = matches!(ty, TypeRef::Map(_, _));
if is_nested_vec || is_map {
if optional {
return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
}
return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
}
if let TypeRef::Optional(inner) = ty {
let is_inner_nested = matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Vec(_)));
let is_inner_map = matches!(inner.as_ref(), TypeRef::Map(_, _));
if is_inner_nested || is_inner_map {
return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
}
}
}
let prefix = config.type_name_prefix;
let is_enum_string = |n: &str| -> bool { config.enum_string_names.as_ref().is_some_and(|names| names.contains(n)) };
match ty {
TypeRef::Primitive(p) if config.cast_large_ints_to_i64 && needs_i64_cast(p) => {
let cast_to = binding_prim_str(p);
if optional {
format!("{name}: val.{name}.map(|v| v as {cast_to})")
} else {
format!("{name}: val.{name} as {cast_to}")
}
}
TypeRef::Primitive(PrimitiveType::F32) if config.cast_f32_to_f64 => {
if optional {
format!("{name}: val.{name}.map(|v| v as f64)")
} else {
format!("{name}: val.{name} as f64")
}
}
TypeRef::Duration if config.cast_large_ints_to_i64 => {
if optional {
format!("{name}: val.{name}.map(|d| d.as_millis() as u64 as i64)")
} else {
format!("{name}: val.{name}.as_millis() as u64 as i64")
}
}
TypeRef::Named(n) if opaque_types.contains(n.as_str()) && !prefix.is_empty() => {
let prefixed = format!("{prefix}{n}");
if optional {
format!("{name}: val.{name}.map(|v| {prefixed} {{ inner: Arc::new(v) }})")
} else {
format!("{name}: {prefixed} {{ inner: Arc::new(val.{name}) }}")
}
}
TypeRef::Named(n) if is_enum_string(n) => {
if optional {
format!(
"{name}: val.{name}.as_ref().map(|v| serde_json::to_value(v).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default())"
)
} else {
format!(
"{name}: serde_json::to_value(val.{name}).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()"
)
}
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if is_enum_string(n)) => {
if optional {
format!(
"{name}: val.{name}.as_ref().map(|v| v.iter().map(|x| serde_json::to_value(x).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()).collect())"
)
} else {
format!(
"{name}: val.{name}.iter().map(|v| serde_json::to_value(v).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()).collect()"
)
}
}
TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(n) if is_enum_string(n))) =>
{
format!(
"{name}: val.{name}.as_ref().map(|v| v.iter().map(|x| serde_json::to_value(x).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()).collect())"
)
}
TypeRef::Vec(inner)
if config.cast_f32_to_f64 && matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::F32)) =>
{
if optional {
format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
} else {
format!("{name}: val.{name}.iter().map(|&v| v as f64).collect()")
}
}
TypeRef::Optional(inner)
if config.cast_f32_to_f64
&& matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(PrimitiveType::F32))) =>
{
format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
}
TypeRef::Optional(inner)
if config.cast_large_ints_to_i64
&& matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
{
if let TypeRef::Primitive(p) = inner.as_ref() {
let cast_to = binding_prim_str(p);
format!("{name}: val.{name}.map(|v| v as {cast_to})")
} else {
field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
}
}
TypeRef::Vec(inner)
if config.cast_large_ints_to_i64
&& matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
{
if let TypeRef::Primitive(p) = inner.as_ref() {
let cast_to = binding_prim_str(p);
if optional {
format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as {cast_to}).collect())")
} else {
format!("{name}: val.{name}.iter().map(|&v| v as {cast_to}).collect()")
}
} else {
field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
}
}
TypeRef::Json if config.json_to_string => {
if optional {
format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
} else {
format!("{name}: val.{name}.to_string()")
}
}
TypeRef::Json if config.map_uses_jsvalue => {
if optional {
format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
} else {
format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
}
}
TypeRef::Vec(inner) if config.map_uses_jsvalue && matches!(inner.as_ref(), TypeRef::Json) => {
if optional {
format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
} else {
format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
}
}
TypeRef::Optional(inner)
if config.map_uses_jsvalue
&& matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Json)) =>
{
format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
}
_ => field_conversion_from_core(name, ty, optional, sanitized, opaque_types),
}
}
fn apply_core_wrapper_from_core(
conversion: &str,
name: &str,
core_wrapper: &CoreWrapper,
vec_inner_core_wrapper: &CoreWrapper,
optional: bool,
) -> String {
if *vec_inner_core_wrapper == CoreWrapper::Arc {
return conversion
.replace(".map(Into::into).collect()", ".map(|v| (*v).clone().into()).collect()")
.replace(
"map(|v| v.into_iter().map(Into::into)",
"map(|v| v.into_iter().map(|v| (*v).clone().into())",
);
}
match core_wrapper {
CoreWrapper::None => conversion.to_string(),
CoreWrapper::Cow => {
if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
if optional {
conversion.to_string()
} else if expr == format!("val.{name}") {
format!("{name}: val.{name}.into_owned()")
} else {
conversion.to_string()
}
} else {
conversion.to_string()
}
}
CoreWrapper::Arc => {
if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
if optional {
format!("{name}: {expr}.map(|v| (*v).clone().into())")
} else {
let unwrapped = expr.replace(&format!("val.{name}"), &format!("(*val.{name}).clone()"));
format!("{name}: {unwrapped}")
}
} else {
conversion.to_string()
}
}
CoreWrapper::Bytes => {
if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
if optional {
format!("{name}: {expr}.map(|v| v.to_vec())")
} else if expr == format!("val.{name}") {
format!("{name}: val.{name}.to_vec()")
} else {
conversion.to_string()
}
} else {
conversion.to_string()
}
}
}
}