use crate::backends::wasm::gen_bindings::{cfg_condition_enabled, field_references_excluded_type};
use crate::codegen::naming::to_node_name;
pub(in crate::backends::wasm::gen_bindings) fn should_have_input_dto(type_def: &crate::core::ir::TypeDef) -> bool {
type_def.has_default
&& type_def.has_serde
&& crate::codegen::shared::binding_fields(&type_def.fields).any(|field| {
field.serde_rename.is_some()
|| type_def.serde_rename_all.is_some()
|| crate::codegen::naming::to_node_name(&field.name) != field.name
})
}
#[cfg(test)]
pub(in crate::backends::wasm::gen_bindings) fn gen_input_dto_for_type(
type_name: &str,
core_import: &str,
type_def: &crate::core::ir::TypeDef,
) -> (String, String) {
gen_input_dto_for_type_with_cfg(
type_name,
core_import,
type_def,
&[],
&[],
&std::collections::HashSet::new(),
)
}
pub(in crate::backends::wasm::gen_bindings) struct InputDtoConfig<'a> {
pub core_path: &'a str,
pub core_import: &'a str,
pub core_type_paths: &'a ahash::AHashMap<String, String>,
pub exclude_types: &'a [String],
pub enabled_features: &'a [String],
pub non_deserializable_type_names: &'a std::collections::HashSet<String>,
}
pub(in crate::backends::wasm::gen_bindings) fn gen_input_dto_for_type_at_path(
type_name: &str,
core_path: &str,
core_import: &str,
core_type_paths: &ahash::AHashMap<String, String>,
type_def: &crate::core::ir::TypeDef,
) -> (String, String) {
gen_input_dto_for_type_with_cfg_at_path(
type_name,
type_def,
InputDtoConfig {
core_path,
core_import,
core_type_paths,
exclude_types: &[],
enabled_features: &[],
non_deserializable_type_names: &std::collections::HashSet::new(),
},
)
}
#[cfg(test)]
pub(in crate::backends::wasm::gen_bindings) fn gen_input_dto_for_type_with_cfg(
type_name: &str,
core_import: &str,
type_def: &crate::core::ir::TypeDef,
exclude_types: &[String],
enabled_features: &[String],
non_deserializable_type_names: &std::collections::HashSet<String>,
) -> (String, String) {
let core_path = format!("{}::{}", core_import, type_name);
gen_input_dto_for_type_with_cfg_at_path(
type_name,
type_def,
InputDtoConfig {
core_path: &core_path,
core_import,
core_type_paths: &ahash::AHashMap::new(),
exclude_types,
enabled_features,
non_deserializable_type_names,
},
)
}
pub(in crate::backends::wasm::gen_bindings) fn gen_input_dto_for_type_with_cfg_at_path(
type_name: &str,
type_def: &crate::core::ir::TypeDef,
config: InputDtoConfig<'_>,
) -> (String, String) {
let input_name = format!("{}Input", type_name);
let fields: Vec<_> = crate::codegen::shared::binding_fields(&type_def.fields)
.map(|field| input_dto_field_context(field, &config))
.collect::<Vec<_>>();
let code = if !fields.is_empty() || !type_def.fields.is_empty() {
crate::backends::wasm::template_env::render(
"gen_input_dto",
crate::alef_context! {
input_name => &input_name,
core_path => config.core_path,
fields => &fields,
has_default => type_def.has_default,
},
)
} else {
String::new()
};
(code, input_name)
}
fn input_dto_field_context(field: &crate::core::ir::FieldDef, config: &InputDtoConfig<'_>) -> minijinja::Value {
let field_cfg = field.cfg.as_deref();
let cfg_satisfied = field_cfg.is_none_or(|cfg| cfg_condition_enabled(cfg, config.enabled_features));
let inner_ty = match &field.ty {
crate::core::ir::TypeRef::Optional(inner) => inner.as_ref(),
other => other,
};
let is_skipped = field_references_excluded_type(&field.ty, config.exclude_types)
|| !cfg_satisfied
|| matches!(
inner_ty,
crate::core::ir::TypeRef::Named(name) if config.non_deserializable_type_names.contains(name)
);
let dto_ty = format!(
"Option<{}>",
type_ref_to_dto_type_with_paths(&field.ty, config.core_import, config.core_type_paths)
);
let wire_name = field.serde_rename.clone().unwrap_or_else(|| to_node_name(&field.name));
let conversion = input_dto_field_conversion(field);
crate::alef_context! {
name => &field.name,
ty => &dto_ty,
core_name => &field.name,
serde_rename => &wire_name,
conv => conversion,
cfg => field_cfg,
is_skipped => is_skipped,
}
}
pub(super) fn input_dto_field_conversion(field: &crate::core::ir::FieldDef) -> String {
let Some(wrapper) = field.newtype_wrapper.as_deref() else {
return dto_field_conversion(&field.ty, field.sanitized, field.optional);
};
let source = if field.optional { "Some(v)" } else { "v" };
let converted = crate::backends::wasm::gen_bindings::types::types_helpers::apply_newtype_to_core(
source,
&field.ty,
field.optional,
wrapper,
);
if !field.is_boxed || !crate::codegen::conversions::helpers::is_explicit_newtype(wrapper) {
return converted;
}
if field.optional || matches!(field.ty, crate::core::ir::TypeRef::Optional(_)) {
format!("({converted}).map(Box::new)")
} else {
format!("Box::new({converted})")
}
}
fn type_ref_to_dto_type_with_paths(
ty: &crate::core::ir::TypeRef,
core_import: &str,
core_type_paths: &ahash::AHashMap<String, String>,
) -> String {
use crate::core::ir::TypeRef;
match ty {
TypeRef::String | TypeRef::Char => "String".to_string(),
TypeRef::Primitive(p) => match p {
crate::core::ir::PrimitiveType::Bool => "bool".to_string(),
crate::core::ir::PrimitiveType::U8 => "u8".to_string(),
crate::core::ir::PrimitiveType::U16 => "u16".to_string(),
crate::core::ir::PrimitiveType::U32 => "u32".to_string(),
crate::core::ir::PrimitiveType::U64 => "u64".to_string(),
crate::core::ir::PrimitiveType::I8 => "i8".to_string(),
crate::core::ir::PrimitiveType::I16 => "i16".to_string(),
crate::core::ir::PrimitiveType::I32 => "i32".to_string(),
crate::core::ir::PrimitiveType::I64 => "i64".to_string(),
crate::core::ir::PrimitiveType::F32 => "f32".to_string(),
crate::core::ir::PrimitiveType::F64 => "f64".to_string(),
crate::core::ir::PrimitiveType::Usize => "usize".to_string(),
crate::core::ir::PrimitiveType::Isize => "isize".to_string(),
},
TypeRef::Vec(inner) => format!(
"Vec<{}>",
type_ref_to_dto_type_with_paths(inner, core_import, core_type_paths)
),
TypeRef::Optional(inner) => format!(
"Option<{}>",
type_ref_to_dto_type_with_paths(inner, core_import, core_type_paths)
),
TypeRef::Map(k, v) => format!(
"std::collections::HashMap<{}, {}>",
type_ref_to_dto_type_with_paths(k, core_import, core_type_paths),
type_ref_to_dto_type_with_paths(v, core_import, core_type_paths)
),
TypeRef::Json => "serde_json::Value".to_string(),
TypeRef::Bytes => "Vec<u8>".to_string(),
TypeRef::Path => "String".to_string(),
TypeRef::Duration => "u64".to_string(),
TypeRef::Named(n) => core_type_paths
.get(n)
.cloned()
.unwrap_or_else(|| format!("{core_import}::{n}")),
TypeRef::Unit => "()".to_string(),
}
}
pub(super) fn dto_field_conversion(ty: &crate::core::ir::TypeRef, sanitized: bool, optional: bool) -> String {
use crate::core::ir::TypeRef;
let wrap_optional = |expr: &str| -> String {
if optional {
format!("Some({expr})")
} else {
expr.to_string()
}
};
match ty {
TypeRef::Duration => "Into::into(std::time::Duration::from_millis(v))".to_string(),
TypeRef::Path => "Into::into(std::path::PathBuf::from(v))".to_string(),
TypeRef::Char => "Into::into(v.chars().next().unwrap_or('\\0'))".to_string(),
TypeRef::String if sanitized => "serde_json::from_str(&v).unwrap_or_default()".to_string(),
TypeRef::Vec(_) => wrap_optional("v.into_iter().collect()"),
TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Vec(_)) => {
"v.map(|items| items.into_iter().collect())".to_string()
}
_ => "v.into()".to_string(),
}
}