use alef_core::ir::{DefaultValue, FieldDef, PrimitiveType, TypeDef, TypeRef};
use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
fn is_tuple_field(field: &FieldDef) -> bool {
(field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
|| field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
}
fn use_unwrap_or_default(field: &FieldDef) -> bool {
if let Some(typed_default) = &field.typed_default {
return matches!(typed_default, DefaultValue::Empty | DefaultValue::None);
}
field.default.is_none() && !matches!(&field.ty, TypeRef::Named(_))
}
pub fn gen_pyo3_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let mut lines = Vec::new();
lines.push("#[new]".to_string());
let mut sig_parts = Vec::new();
for field in &typ.fields {
let default_str = default_value_for_field(field, "python");
sig_parts.push(format!("{}={}", field.name, default_str));
}
let signature = format!("#[pyo3(signature = ({}))]", sig_parts.join(", "));
lines.push(signature);
lines.push("fn new(".to_string());
for (i, field) in typ.fields.iter().enumerate() {
let type_str = type_mapper(&field.ty);
let comma = if i < typ.fields.len() - 1 { "," } else { "" };
lines.push(format!(" {}: {}{}", field.name, type_str, comma));
}
lines.push(") -> Self {".to_string());
lines.push(" Self {".to_string());
for field in &typ.fields {
lines.push(format!(" {},", field.name));
}
lines.push(" }".to_string());
lines.push("}".to_string());
lines.join("\n")
}
pub fn gen_napi_defaults_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let mut lines = Vec::new();
lines.push("pub fn new(mut env: napi::Env, obj: napi::Object) -> napi::Result<Self> {".to_string());
for field in &typ.fields {
let type_str = type_mapper(&field.ty);
let default_str = default_value_for_field(field, "rust");
lines.push(format!(
" let {}: {} = obj.get(\"{}\").unwrap_or({})?;",
field.name, type_str, field.name, default_str
));
}
lines.push(" Ok(Self {".to_string());
for field in &typ.fields {
lines.push(format!(" {},", field.name));
}
lines.push(" })".to_string());
lines.push("}".to_string());
lines.join("\n")
}
pub fn gen_go_functional_options(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let mut lines = Vec::new();
lines.push(format!("// {} is a configuration type.", typ.name));
lines.push(format!("type {} struct {{", typ.name));
for field in &typ.fields {
if is_tuple_field(field) {
continue;
}
let go_type = type_mapper(&field.ty);
lines.push(format!(" {} {}", field.name.to_pascal_case(), go_type));
}
lines.push("}".to_string());
lines.push("".to_string());
lines.push(format!(
"// {}Option is a functional option for {}.",
typ.name, typ.name
));
lines.push(format!("type {}Option func(*{})", typ.name, typ.name));
lines.push("".to_string());
for field in &typ.fields {
if is_tuple_field(field) {
continue;
}
let option_name = format!("With{}{}", typ.name, field.name.to_pascal_case());
let go_type = type_mapper(&field.ty);
lines.push(format!("// {} sets the {}.", option_name, field.name));
lines.push(format!("func {}(val {}) {}Option {{", option_name, go_type, typ.name));
lines.push(format!(" return func(c *{}) {{", typ.name));
lines.push(format!(" c.{} = val", field.name.to_pascal_case()));
lines.push(" }".to_string());
lines.push("}".to_string());
lines.push("".to_string());
}
lines.push(format!(
"// New{} creates a new {} with default values and applies options.",
typ.name, typ.name
));
lines.push(format!(
"func New{}(opts ...{}Option) *{} {{",
typ.name, typ.name, typ.name
));
lines.push(format!(" c := &{} {{", typ.name));
for field in &typ.fields {
if is_tuple_field(field) {
continue;
}
let default_str = default_value_for_field(field, "go");
lines.push(format!(" {}: {},", field.name.to_pascal_case(), default_str));
}
lines.push(" }".to_string());
lines.push(" for _, opt := range opts {".to_string());
lines.push(" opt(c)".to_string());
lines.push(" }".to_string());
lines.push(" return c".to_string());
lines.push("}".to_string());
lines.join("\n")
}
pub fn gen_java_builder(typ: &TypeDef, package: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let mut lines = Vec::new();
lines.push(format!(
"// DO NOT EDIT - auto-generated by alef\npackage {};\n",
package
));
lines.push("/// Builder for creating instances of {} with sensible defaults".to_string());
lines.push(format!("public class {}Builder {{", typ.name));
for field in &typ.fields {
let java_type = type_mapper(&field.ty);
lines.push(format!(" private {} {};", java_type, field.name.to_lowercase()));
}
lines.push("".to_string());
lines.push(format!(" public {}Builder() {{", typ.name));
for field in &typ.fields {
let default_str = default_value_for_field(field, "java");
lines.push(format!(" this.{} = {};", field.name.to_lowercase(), default_str));
}
lines.push(" }".to_string());
lines.push("".to_string());
for field in &typ.fields {
let java_type = type_mapper(&field.ty);
let method_name = format!("with{}", field.name.to_pascal_case());
lines.push(format!(
" public {}Builder {}({} value) {{",
typ.name, method_name, java_type
));
lines.push(format!(" this.{} = value;", field.name.to_lowercase()));
lines.push(" return this;".to_string());
lines.push(" }".to_string());
lines.push("".to_string());
}
lines.push(format!(" public {} build() {{", typ.name));
lines.push(format!(" return new {}(", typ.name));
for (i, field) in typ.fields.iter().enumerate() {
let comma = if i < typ.fields.len() - 1 { "," } else { "" };
lines.push(format!(" this.{}{}", field.name.to_lowercase(), comma));
}
lines.push(" );".to_string());
lines.push(" }".to_string());
lines.push("}".to_string());
lines.join("\n")
}
pub fn gen_csharp_record(typ: &TypeDef, namespace: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let mut lines = Vec::new();
lines.push("// This file is auto-generated by alef. DO NOT EDIT.".to_string());
lines.push("using System;".to_string());
lines.push("".to_string());
lines.push(format!("namespace {};\n", namespace));
lines.push(format!("/// Configuration record: {}", typ.name));
lines.push(format!("public record {} {{", typ.name));
for field in &typ.fields {
if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
|| field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
{
continue;
}
let cs_type = type_mapper(&field.ty);
let default_str = default_value_for_field(field, "csharp");
lines.push(format!(
" public {} {} {{ get; init; }} = {};",
cs_type,
field.name.to_pascal_case(),
default_str
));
}
lines.push("}".to_string());
lines.join("\n")
}
pub fn default_value_for_field(field: &FieldDef, language: &str) -> String {
if let Some(typed_default) = &field.typed_default {
return match typed_default {
DefaultValue::BoolLiteral(b) => match language {
"python" => {
if *b {
"True".to_string()
} else {
"False".to_string()
}
}
"ruby" => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
"go" => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
"java" => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
"csharp" => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
"php" => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
"r" => {
if *b {
"TRUE".to_string()
} else {
"FALSE".to_string()
}
}
"rust" => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
_ => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
},
DefaultValue::StringLiteral(s) => match language {
"rust" => format!("\"{}\".to_string()", s.replace('"', "\\\"")),
_ => format!("\"{}\"", s.replace('"', "\\\"")),
},
DefaultValue::IntLiteral(n) => n.to_string(),
DefaultValue::FloatLiteral(f) => {
let s = f.to_string();
if !s.contains('.') { format!("{}.0", s) } else { s }
}
DefaultValue::EnumVariant(v) => {
if matches!(field.ty, TypeRef::String) {
let snake = v.to_snake_case();
return match language {
"rust" => format!("\"{}\".to_string()", snake),
_ => format!("\"{}\"", snake),
};
}
match language {
"python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
"ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
"go" => format!("{}{}", field.ty.type_name(), v.to_pascal_case()),
"java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
"csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
"php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
"r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
"rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
_ => v.clone(),
}
}
DefaultValue::Empty => {
match &field.ty {
TypeRef::Vec(_) => match language {
"python" | "ruby" | "csharp" => "[]".to_string(),
"go" => "nil".to_string(),
"java" => "List.of()".to_string(),
"php" => "[]".to_string(),
"r" => "c()".to_string(),
"rust" => "vec![]".to_string(),
_ => "null".to_string(),
},
TypeRef::Map(_, _) => match language {
"python" => "{}".to_string(),
"go" => "nil".to_string(),
"java" => "Map.of()".to_string(),
"rust" => "Default::default()".to_string(),
_ => "null".to_string(),
},
TypeRef::Primitive(p) => match p {
PrimitiveType::Bool => match language {
"python" => "False".to_string(),
"ruby" => "false".to_string(),
_ => "false".to_string(),
},
PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
_ => "0".to_string(),
},
TypeRef::String | TypeRef::Char | TypeRef::Path => match language {
"rust" => "String::new()".to_string(),
_ => "\"\"".to_string(),
},
TypeRef::Json => match language {
"python" | "ruby" => "{}".to_string(),
"go" => "json.RawMessage(nil)".to_string(),
"java" => "new com.fasterxml.jackson.databind.node.ObjectNode(null)".to_string(),
"csharp" => "JObject.Parse(\"{}\")".to_string(),
"php" => "[]".to_string(),
"r" => "list()".to_string(),
"rust" => "serde_json::json!({})".to_string(),
_ => "{}".to_string(),
},
TypeRef::Duration => "0".to_string(),
TypeRef::Bytes => match language {
"python" => "b\"\"".to_string(),
"go" => "[]byte{}".to_string(),
"rust" => "vec![]".to_string(),
_ => "\"\"".to_string(),
},
_ => match language {
"python" => "None".to_string(),
"ruby" => "nil".to_string(),
"go" => "nil".to_string(),
"rust" => "Default::default()".to_string(),
_ => "null".to_string(),
},
}
}
DefaultValue::None => match language {
"python" => "None".to_string(),
"ruby" => "nil".to_string(),
"go" => "nil".to_string(),
"java" => "null".to_string(),
"csharp" => "null".to_string(),
"php" => "null".to_string(),
"r" => "NULL".to_string(),
"rust" => "None".to_string(),
_ => "null".to_string(),
},
};
}
if let Some(default_str) = &field.default {
return default_str.clone();
}
match &field.ty {
TypeRef::Primitive(p) => match p {
alef_core::ir::PrimitiveType::Bool => match language {
"python" => "False".to_string(),
"ruby" => "false".to_string(),
"csharp" => "false".to_string(),
"java" => "false".to_string(),
"php" => "false".to_string(),
"r" => "FALSE".to_string(),
_ => "false".to_string(),
},
alef_core::ir::PrimitiveType::U8
| alef_core::ir::PrimitiveType::U16
| alef_core::ir::PrimitiveType::U32
| alef_core::ir::PrimitiveType::U64
| alef_core::ir::PrimitiveType::I8
| alef_core::ir::PrimitiveType::I16
| alef_core::ir::PrimitiveType::I32
| alef_core::ir::PrimitiveType::I64
| alef_core::ir::PrimitiveType::Usize
| alef_core::ir::PrimitiveType::Isize => "0".to_string(),
alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
},
TypeRef::String | TypeRef::Char => match language {
"python" => "\"\"".to_string(),
"ruby" => "\"\"".to_string(),
"go" => "\"\"".to_string(),
"java" => "\"\"".to_string(),
"csharp" => "\"\"".to_string(),
"php" => "\"\"".to_string(),
"r" => "\"\"".to_string(),
"rust" => "String::new()".to_string(),
_ => "\"\"".to_string(),
},
TypeRef::Bytes => match language {
"python" => "b\"\"".to_string(),
"ruby" => "\"\"".to_string(),
"go" => "[]byte{}".to_string(),
"java" => "new byte[]{}".to_string(),
"csharp" => "new byte[]{}".to_string(),
"php" => "\"\"".to_string(),
"r" => "raw()".to_string(),
"rust" => "vec![]".to_string(),
_ => "[]".to_string(),
},
TypeRef::Optional(_) => match language {
"python" => "None".to_string(),
"ruby" => "nil".to_string(),
"go" => "nil".to_string(),
"java" => "null".to_string(),
"csharp" => "null".to_string(),
"php" => "null".to_string(),
"r" => "NULL".to_string(),
"rust" => "None".to_string(),
_ => "null".to_string(),
},
TypeRef::Vec(_) => match language {
"python" => "[]".to_string(),
"ruby" => "[]".to_string(),
"go" => "[]interface{}{}".to_string(),
"java" => "new java.util.ArrayList<>()".to_string(),
"csharp" => "[]".to_string(),
"php" => "[]".to_string(),
"r" => "c()".to_string(),
"rust" => "vec![]".to_string(),
_ => "[]".to_string(),
},
TypeRef::Map(_, _) => match language {
"python" => "{}".to_string(),
"ruby" => "{}".to_string(),
"go" => "make(map[string]interface{})".to_string(),
"java" => "new java.util.HashMap<>()".to_string(),
"csharp" => "new Dictionary<string, object>()".to_string(),
"php" => "[]".to_string(),
"r" => "list()".to_string(),
"rust" => "std::collections::HashMap::new()".to_string(),
_ => "{}".to_string(),
},
TypeRef::Json => match language {
"python" => "{}".to_string(),
"ruby" => "{}".to_string(),
"go" => "json.RawMessage(nil)".to_string(),
"java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
"csharp" => "JObject.Parse(\"{}\")".to_string(),
"php" => "[]".to_string(),
"r" => "list()".to_string(),
"rust" => "serde_json::json!({})".to_string(),
_ => "{}".to_string(),
},
TypeRef::Named(name) => match language {
"rust" => format!("{name}::default()"),
"python" => "None".to_string(),
"ruby" => "nil".to_string(),
"go" => "nil".to_string(),
"java" => "null".to_string(),
"csharp" => "null".to_string(),
"php" => "null".to_string(),
"r" => "NULL".to_string(),
_ => "null".to_string(),
},
_ => match language {
"python" => "None".to_string(),
"ruby" => "nil".to_string(),
"go" => "nil".to_string(),
"java" => "null".to_string(),
"csharp" => "null".to_string(),
"php" => "null".to_string(),
"r" => "NULL".to_string(),
"rust" => "Default::default()".to_string(),
_ => "null".to_string(),
},
}
}
trait TypeRefExt {
fn type_name(&self) -> String;
}
impl TypeRefExt for TypeRef {
fn type_name(&self) -> String {
match self {
TypeRef::Named(n) => n.clone(),
TypeRef::Primitive(p) => format!("{:?}", p),
TypeRef::String | TypeRef::Char => "String".to_string(),
TypeRef::Bytes => "Bytes".to_string(),
TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
TypeRef::Path => "Path".to_string(),
TypeRef::Unit => "()".to_string(),
TypeRef::Json => "Json".to_string(),
TypeRef::Duration => "Duration".to_string(),
}
}
}
const MAGNUS_MAX_ARITY: usize = 15;
pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
if typ.fields.len() > MAGNUS_MAX_ARITY {
gen_magnus_hash_constructor(typ, type_mapper)
} else {
gen_magnus_positional_constructor(typ, type_mapper)
}
}
fn as_type_path_prefix(type_str: &str) -> String {
if type_str.contains('<') {
format!("<{type_str}>")
} else {
type_str.to_string()
}
}
fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let fields: Vec<_> = typ
.fields
.iter()
.map(|field| {
let is_optional = field_is_optional_in_rust(field);
let effective_inner_ty = match &field.ty {
TypeRef::Optional(inner) if is_optional => inner.as_ref(),
ty => ty,
};
let inner_type = type_mapper(effective_inner_ty);
let type_prefix = as_type_path_prefix(&inner_type);
let assignment = if is_optional {
format!(
"kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()),",
field.name, type_prefix
)
} else if use_unwrap_or_default(field) {
format!(
"kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()).unwrap_or_default(),",
field.name, type_prefix
)
} else {
let default_str = if inner_type == "String" {
if let Some(DefaultValue::EnumVariant(variant)) = &field.typed_default {
use heck::ToSnakeCase;
format!("\"{}\".to_string()", variant.to_snake_case())
} else {
default_value_for_field(field, "rust")
}
} else {
default_value_for_field(field, "rust")
};
format!(
"kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()).unwrap_or({}),",
field.name, type_prefix, default_str
)
};
minijinja::context! {
name => field.name.clone(),
assignment => assignment,
}
})
.collect();
crate::template_env::render(
"config_gen/magnus_hash_constructor.jinja",
minijinja::context! {
fields => fields,
},
)
}
fn field_is_optional_in_rust(field: &FieldDef) -> bool {
field.optional || matches!(&field.ty, TypeRef::Optional(_))
}
fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let fields: Vec<_> = typ
.fields
.iter()
.map(|field| {
let is_optional = field_is_optional_in_rust(field);
let param_type = if is_optional {
let effective_inner_ty = match &field.ty {
TypeRef::Optional(inner) => inner.as_ref(),
ty => ty,
};
let inner_type = type_mapper(effective_inner_ty);
format!("Option<{}>", inner_type)
} else {
let field_type = type_mapper(&field.ty);
format!("Option<{}>", field_type)
};
let assignment = if is_optional {
field.name.clone()
} else if use_unwrap_or_default(field) {
format!("{}.unwrap_or_default()", field.name)
} else {
let default_str = default_value_for_field(field, "rust");
format!("{}.unwrap_or({})", field.name, default_str)
};
minijinja::context! {
name => field.name.clone(),
param_type => param_type,
assignment => assignment,
}
})
.collect();
crate::template_env::render(
"config_gen/magnus_positional_constructor.jinja",
minijinja::context! {
fields => fields,
},
)
}
pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let fields: Vec<_> = typ
.fields
.iter()
.map(|field| {
let mapped = type_mapper(&field.ty);
let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
let assignment = if is_optional_field {
field.name.clone()
} else if use_unwrap_or_default(field) {
format!("{}.unwrap_or_default()", field.name)
} else {
let default_str = default_value_for_field(field, "rust");
format!("{}.unwrap_or({})", field.name, default_str)
};
minijinja::context! {
name => field.name.clone(),
ty => mapped,
assignment => assignment,
}
})
.collect();
crate::template_env::render(
"config_gen/php_kwargs_constructor.jinja",
minijinja::context! {
fields => fields,
},
)
}
pub fn gen_rustler_kwargs_constructor_with_exclude(
typ: &TypeDef,
_type_mapper: &dyn Fn(&TypeRef) -> String,
exclude_fields: &std::collections::HashSet<String>,
) -> String {
let fields: Vec<_> = typ
.fields
.iter()
.filter(|f| !exclude_fields.contains(&f.name))
.map(|field| {
let assignment = if field.optional {
format!("opts.get(\"{}\").and_then(|t| t.decode().ok()),", field.name)
} else if use_unwrap_or_default(field) {
format!(
"opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
field.name
)
} else {
let default_str = default_value_for_field(field, "rust");
let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
if (is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char))
|| matches!(&field.ty, TypeRef::Named(_))
{
format!(
"opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
field.name
)
} else {
format!(
"opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
field.name, default_str
)
}
};
minijinja::context! {
name => field.name.clone(),
assignment => assignment,
}
})
.collect();
crate::template_env::render(
"config_gen/rustler_kwargs_constructor.jinja",
minijinja::context! {
fields => fields,
},
)
}
pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let fields: Vec<_> = typ
.fields
.iter()
.map(|field| {
let assignment = if field.optional {
format!("opts.get(\"{}\").and_then(|t| t.decode().ok()),", field.name)
} else if use_unwrap_or_default(field) {
format!(
"opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
field.name
)
} else {
let default_str = default_value_for_field(field, "rust");
let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
let unwrap_default = (is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char))
|| matches!(&field.ty, TypeRef::Named(_));
if unwrap_default {
format!(
"opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
field.name
)
} else {
format!(
"opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
field.name, default_str
)
}
};
minijinja::context! {
name => field.name.clone(),
assignment => assignment,
}
})
.collect();
crate::template_env::render(
"config_gen/rustler_kwargs_constructor.jinja",
minijinja::context! {
fields => fields,
},
)
}
pub fn gen_extendr_kwargs_constructor(
typ: &TypeDef,
type_mapper: &dyn Fn(&TypeRef) -> String,
enum_names: &ahash::AHashSet<String>,
) -> String {
let is_named_enum = |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Named(n) if enum_names.contains(n.as_str())) };
let is_named_struct =
|ty: &TypeRef| -> bool { matches!(ty, TypeRef::Named(n) if !enum_names.contains(n.as_str())) };
let is_optional_named_struct = |ty: &TypeRef| -> bool {
if let TypeRef::Optional(inner) = ty {
is_named_struct(inner)
} else {
false
}
};
let ty_is_optional = |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Optional(_)) };
let emittable_fields: Vec<_> = typ
.fields
.iter()
.filter(|f| !is_named_struct(&f.ty) && !is_optional_named_struct(&f.ty))
.map(|field| {
let param_type = if is_named_enum(&field.ty) {
"Option<String>".to_string()
} else if ty_is_optional(&field.ty) {
type_mapper(&field.ty)
} else {
format!("Option<{}>", type_mapper(&field.ty))
};
minijinja::context! {
name => field.name.clone(),
type => param_type,
}
})
.collect();
let body_assignments: Vec<_> = typ
.fields
.iter()
.filter(|f| !is_named_struct(&f.ty) && !is_optional_named_struct(&f.ty))
.map(|field| {
let code = if is_named_enum(&field.ty) {
if field.optional {
format!(
"if let Some(v) = {} {{ __out.{} = serde_json::from_str(&format!(\"\\\"{{v}}\\\"\")).ok(); }}",
field.name, field.name
)
} else {
format!(
"if let Some(v) = {} {{ if let Ok(parsed) = serde_json::from_str(&format!(\"\\\"{{v}}\\\"\")) {{ __out.{} = parsed; }} }}",
field.name, field.name
)
}
} else if ty_is_optional(&field.ty) || field.optional {
format!(
"if let Some(v) = {} {{ __out.{} = Some(v); }}",
field.name, field.name
)
} else {
format!(
"if let Some(v) = {} {{ __out.{} = v; }}",
field.name, field.name
)
};
minijinja::context! {
code => code,
}
})
.collect();
crate::template_env::render(
"config_gen/extendr_kwargs_constructor.jinja",
minijinja::context! {
type_name => typ.name.clone(),
type_name_lower => typ.name.to_lowercase(),
params => emittable_fields,
body_assignments => body_assignments,
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
fn make_test_type() -> TypeDef {
TypeDef {
name: "Config".to_string(),
rust_path: "my_crate::Config".to_string(),
original_rust_path: String::new(),
fields: vec![
FieldDef {
name: "timeout".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U64),
optional: false,
default: Some("30".to_string()),
doc: "Timeout in seconds".to_string(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::IntLiteral(30)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
},
FieldDef {
name: "enabled".to_string(),
ty: TypeRef::Primitive(PrimitiveType::Bool),
optional: false,
default: None,
doc: "Enable feature".to_string(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::BoolLiteral(true)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
},
FieldDef {
name: "name".to_string(),
ty: TypeRef::String,
optional: false,
default: None,
doc: "Config name".to_string(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
},
],
methods: vec![],
is_opaque: false,
is_clone: true,
is_copy: false,
doc: "Configuration type".to_string(),
cfg: None,
is_trait: false,
has_default: true,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
}
}
#[test]
fn test_default_value_bool_true_python() {
let field = FieldDef {
name: "enabled".to_string(),
ty: TypeRef::Primitive(PrimitiveType::Bool),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::BoolLiteral(true)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "True");
}
#[test]
fn test_default_value_bool_false_go() {
let field = FieldDef {
name: "enabled".to_string(),
ty: TypeRef::Primitive(PrimitiveType::Bool),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::BoolLiteral(false)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "go"), "false");
}
#[test]
fn test_default_value_string_literal() {
let field = FieldDef {
name: "name".to_string(),
ty: TypeRef::String,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
}
#[test]
fn test_default_value_int_literal() {
let field = FieldDef {
name: "timeout".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U64),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::IntLiteral(42)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
let result = default_value_for_field(&field, "python");
assert_eq!(result, "42");
}
#[test]
fn test_default_value_none() {
let field = FieldDef {
name: "maybe".to_string(),
ty: TypeRef::Optional(Box::new(TypeRef::String)),
optional: true,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::None),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "None");
assert_eq!(default_value_for_field(&field, "go"), "nil");
assert_eq!(default_value_for_field(&field, "java"), "null");
assert_eq!(default_value_for_field(&field, "csharp"), "null");
}
#[test]
fn test_default_value_fallback_string() {
let field = FieldDef {
name: "name".to_string(),
ty: TypeRef::String,
optional: false,
default: Some("\"custom\"".to_string()),
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
}
#[test]
fn test_gen_pyo3_kwargs_constructor() {
let typ = make_test_type();
let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
TypeRef::Primitive(p) => format!("{:?}", p),
TypeRef::String | TypeRef::Char => "str".to_string(),
_ => "Any".to_string(),
});
assert!(output.contains("#[new]"));
assert!(output.contains("#[pyo3(signature = ("));
assert!(output.contains("timeout=30"));
assert!(output.contains("enabled=True"));
assert!(output.contains("name=\"default\""));
assert!(output.contains("fn new("));
}
#[test]
fn test_gen_napi_defaults_constructor() {
let typ = make_test_type();
let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
TypeRef::Primitive(p) => format!("{:?}", p),
TypeRef::String | TypeRef::Char => "String".to_string(),
_ => "Value".to_string(),
});
assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
assert!(output.contains("timeout"));
assert!(output.contains("enabled"));
assert!(output.contains("name"));
}
#[test]
fn test_gen_go_functional_options() {
let typ = make_test_type();
let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
TypeRef::Primitive(p) => match p {
PrimitiveType::U64 => "uint64".to_string(),
PrimitiveType::Bool => "bool".to_string(),
_ => "interface{}".to_string(),
},
TypeRef::String | TypeRef::Char => "string".to_string(),
_ => "interface{}".to_string(),
});
assert!(output.contains("type Config struct {"));
assert!(output.contains("type ConfigOption func(*Config)"));
assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
assert!(output.contains("func WithConfigName(val string) ConfigOption"));
assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
}
#[test]
fn test_gen_java_builder() {
let typ = make_test_type();
let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
TypeRef::Primitive(p) => match p {
PrimitiveType::U64 => "long".to_string(),
PrimitiveType::Bool => "boolean".to_string(),
_ => "int".to_string(),
},
TypeRef::String | TypeRef::Char => "String".to_string(),
_ => "Object".to_string(),
});
assert!(output.contains("package dev.test;"));
assert!(output.contains("public class ConfigBuilder"));
assert!(output.contains("withTimeout"));
assert!(output.contains("withEnabled"));
assert!(output.contains("withName"));
assert!(output.contains("public Config build()"));
}
#[test]
fn test_gen_csharp_record() {
let typ = make_test_type();
let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
TypeRef::Primitive(p) => match p {
PrimitiveType::U64 => "ulong".to_string(),
PrimitiveType::Bool => "bool".to_string(),
_ => "int".to_string(),
},
TypeRef::String | TypeRef::Char => "string".to_string(),
_ => "object".to_string(),
});
assert!(output.contains("namespace MyNamespace;"));
assert!(output.contains("public record Config"));
assert!(output.contains("public ulong Timeout"));
assert!(output.contains("public bool Enabled"));
assert!(output.contains("public string Name"));
assert!(output.contains("init;"));
}
#[test]
fn test_default_value_float_literal() {
let field = FieldDef {
name: "ratio".to_string(),
ty: TypeRef::Primitive(PrimitiveType::F64),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::FloatLiteral(1.5)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
let result = default_value_for_field(&field, "python");
assert!(result.contains("1.5"));
}
#[test]
fn test_default_value_no_typed_no_default() {
let field = FieldDef {
name: "count".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "0");
assert_eq!(default_value_for_field(&field, "go"), "0");
}
fn make_field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
}
}
fn simple_type_mapper(tr: &TypeRef) -> String {
match tr {
TypeRef::Primitive(p) => match p {
PrimitiveType::U64 => "u64".to_string(),
PrimitiveType::Bool => "bool".to_string(),
PrimitiveType::U32 => "u32".to_string(),
_ => "i64".to_string(),
},
TypeRef::String | TypeRef::Char => "String".to_string(),
TypeRef::Optional(inner) => format!("Option<{}>", simple_type_mapper(inner)),
TypeRef::Vec(inner) => format!("Vec<{}>", simple_type_mapper(inner)),
TypeRef::Named(n) => n.clone(),
_ => "Value".to_string(),
}
}
#[test]
fn test_default_value_bool_literal_ruby() {
let field = FieldDef {
name: "flag".to_string(),
ty: TypeRef::Primitive(PrimitiveType::Bool),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::BoolLiteral(true)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "ruby"), "true");
assert_eq!(default_value_for_field(&field, "php"), "true");
assert_eq!(default_value_for_field(&field, "csharp"), "true");
assert_eq!(default_value_for_field(&field, "java"), "true");
assert_eq!(default_value_for_field(&field, "rust"), "true");
}
#[test]
fn test_default_value_bool_literal_r() {
let field = FieldDef {
name: "flag".to_string(),
ty: TypeRef::Primitive(PrimitiveType::Bool),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::BoolLiteral(false)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "r"), "FALSE");
}
#[test]
fn test_default_value_string_literal_rust() {
let field = FieldDef {
name: "label".to_string(),
ty: TypeRef::String,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "rust"), "\"hello\".to_string()");
}
#[test]
fn test_default_value_string_literal_escapes_quotes() {
let field = FieldDef {
name: "label".to_string(),
ty: TypeRef::String,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::StringLiteral("say \"hi\"".to_string())),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "\"say \\\"hi\\\"\"");
}
#[test]
fn test_default_value_float_literal_whole_number() {
let field = FieldDef {
name: "scale".to_string(),
ty: TypeRef::Primitive(PrimitiveType::F32),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::FloatLiteral(2.0)),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
let result = default_value_for_field(&field, "python");
assert!(result.contains('.'), "whole-number float should contain '.': {result}");
}
#[test]
fn test_default_value_enum_variant_per_language() {
let field = FieldDef {
name: "format".to_string(),
ty: TypeRef::Named("OutputFormat".to_string()),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::EnumVariant("JsonOutput".to_string())),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "OutputFormat.JSON_OUTPUT");
assert_eq!(default_value_for_field(&field, "ruby"), "OutputFormat::JsonOutput");
assert_eq!(default_value_for_field(&field, "go"), "OutputFormatJsonOutput");
assert_eq!(default_value_for_field(&field, "java"), "OutputFormat.JSON_OUTPUT");
assert_eq!(default_value_for_field(&field, "csharp"), "OutputFormat.JsonOutput");
assert_eq!(default_value_for_field(&field, "php"), "OutputFormat::JsonOutput");
assert_eq!(default_value_for_field(&field, "r"), "OutputFormat$JsonOutput");
assert_eq!(default_value_for_field(&field, "rust"), "OutputFormat::JsonOutput");
}
#[test]
fn test_default_value_empty_vec_per_language() {
let field = FieldDef {
name: "items".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::String)),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::Empty),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "[]");
assert_eq!(default_value_for_field(&field, "ruby"), "[]");
assert_eq!(default_value_for_field(&field, "csharp"), "[]");
assert_eq!(default_value_for_field(&field, "go"), "nil");
assert_eq!(default_value_for_field(&field, "java"), "List.of()");
assert_eq!(default_value_for_field(&field, "php"), "[]");
assert_eq!(default_value_for_field(&field, "r"), "c()");
assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
}
#[test]
fn test_default_value_empty_map_per_language() {
let field = FieldDef {
name: "meta".to_string(),
ty: TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::Empty),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "{}");
assert_eq!(default_value_for_field(&field, "go"), "nil");
assert_eq!(default_value_for_field(&field, "java"), "Map.of()");
assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
}
#[test]
fn test_default_value_empty_bool_primitive() {
let field = FieldDef {
name: "flag".to_string(),
ty: TypeRef::Primitive(PrimitiveType::Bool),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::Empty),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "False");
assert_eq!(default_value_for_field(&field, "ruby"), "false");
assert_eq!(default_value_for_field(&field, "go"), "false");
}
#[test]
fn test_default_value_empty_float_primitive() {
let field = FieldDef {
name: "ratio".to_string(),
ty: TypeRef::Primitive(PrimitiveType::F64),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::Empty),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "0.0");
}
#[test]
fn test_default_value_empty_string_type() {
let field = FieldDef {
name: "label".to_string(),
ty: TypeRef::String,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::Empty),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
assert_eq!(default_value_for_field(&field, "python"), "\"\"");
}
#[test]
fn test_default_value_empty_bytes_type() {
let field = FieldDef {
name: "data".to_string(),
ty: TypeRef::Bytes,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::Empty),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
}
#[test]
fn test_default_value_empty_json_type() {
let field = FieldDef {
name: "payload".to_string(),
ty: TypeRef::Json,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::Empty),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "python"), "{}");
assert_eq!(default_value_for_field(&field, "ruby"), "{}");
assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
assert_eq!(default_value_for_field(&field, "r"), "list()");
assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
}
#[test]
fn test_default_value_none_ruby_php_r() {
let field = FieldDef {
name: "maybe".to_string(),
ty: TypeRef::Optional(Box::new(TypeRef::String)),
optional: true,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: Some(DefaultValue::None),
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
assert_eq!(default_value_for_field(&field, "ruby"), "nil");
assert_eq!(default_value_for_field(&field, "php"), "null");
assert_eq!(default_value_for_field(&field, "r"), "NULL");
assert_eq!(default_value_for_field(&field, "rust"), "None");
}
#[test]
fn test_default_value_fallback_bool_all_languages() {
let field = make_field("flag", TypeRef::Primitive(PrimitiveType::Bool));
assert_eq!(default_value_for_field(&field, "python"), "False");
assert_eq!(default_value_for_field(&field, "ruby"), "false");
assert_eq!(default_value_for_field(&field, "csharp"), "false");
assert_eq!(default_value_for_field(&field, "java"), "false");
assert_eq!(default_value_for_field(&field, "php"), "false");
assert_eq!(default_value_for_field(&field, "r"), "FALSE");
assert_eq!(default_value_for_field(&field, "rust"), "false");
}
#[test]
fn test_default_value_fallback_float() {
let field = make_field("ratio", TypeRef::Primitive(PrimitiveType::F64));
assert_eq!(default_value_for_field(&field, "python"), "0.0");
assert_eq!(default_value_for_field(&field, "rust"), "0.0");
}
#[test]
fn test_default_value_fallback_string_all_languages() {
let field = make_field("name", TypeRef::String);
assert_eq!(default_value_for_field(&field, "python"), "\"\"");
assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
assert_eq!(default_value_for_field(&field, "go"), "\"\"");
assert_eq!(default_value_for_field(&field, "java"), "\"\"");
assert_eq!(default_value_for_field(&field, "csharp"), "\"\"");
assert_eq!(default_value_for_field(&field, "php"), "\"\"");
assert_eq!(default_value_for_field(&field, "r"), "\"\"");
assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
}
#[test]
fn test_default_value_fallback_bytes_all_languages() {
let field = make_field("data", TypeRef::Bytes);
assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
assert_eq!(default_value_for_field(&field, "java"), "new byte[]{}");
assert_eq!(default_value_for_field(&field, "csharp"), "new byte[]{}");
assert_eq!(default_value_for_field(&field, "php"), "\"\"");
assert_eq!(default_value_for_field(&field, "r"), "raw()");
assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
}
#[test]
fn test_default_value_fallback_optional() {
let field = make_field("maybe", TypeRef::Optional(Box::new(TypeRef::String)));
assert_eq!(default_value_for_field(&field, "python"), "None");
assert_eq!(default_value_for_field(&field, "ruby"), "nil");
assert_eq!(default_value_for_field(&field, "go"), "nil");
assert_eq!(default_value_for_field(&field, "java"), "null");
assert_eq!(default_value_for_field(&field, "csharp"), "null");
assert_eq!(default_value_for_field(&field, "php"), "null");
assert_eq!(default_value_for_field(&field, "r"), "NULL");
assert_eq!(default_value_for_field(&field, "rust"), "None");
}
#[test]
fn test_default_value_fallback_vec_all_languages() {
let field = make_field("items", TypeRef::Vec(Box::new(TypeRef::String)));
assert_eq!(default_value_for_field(&field, "python"), "[]");
assert_eq!(default_value_for_field(&field, "ruby"), "[]");
assert_eq!(default_value_for_field(&field, "go"), "[]interface{}{}");
assert_eq!(default_value_for_field(&field, "java"), "new java.util.ArrayList<>()");
assert_eq!(default_value_for_field(&field, "csharp"), "[]");
assert_eq!(default_value_for_field(&field, "php"), "[]");
assert_eq!(default_value_for_field(&field, "r"), "c()");
assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
}
#[test]
fn test_default_value_fallback_map_all_languages() {
let field = make_field(
"meta",
TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
);
assert_eq!(default_value_for_field(&field, "python"), "{}");
assert_eq!(default_value_for_field(&field, "ruby"), "{}");
assert_eq!(default_value_for_field(&field, "go"), "make(map[string]interface{})");
assert_eq!(default_value_for_field(&field, "java"), "new java.util.HashMap<>()");
assert_eq!(
default_value_for_field(&field, "csharp"),
"new Dictionary<string, object>()"
);
assert_eq!(default_value_for_field(&field, "php"), "[]");
assert_eq!(default_value_for_field(&field, "r"), "list()");
assert_eq!(
default_value_for_field(&field, "rust"),
"std::collections::HashMap::new()"
);
}
#[test]
fn test_default_value_fallback_json_all_languages() {
let field = make_field("payload", TypeRef::Json);
assert_eq!(default_value_for_field(&field, "python"), "{}");
assert_eq!(default_value_for_field(&field, "ruby"), "{}");
assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
assert_eq!(default_value_for_field(&field, "r"), "list()");
assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
}
#[test]
fn test_default_value_fallback_named_type() {
let field = make_field("config", TypeRef::Named("MyConfig".to_string()));
assert_eq!(default_value_for_field(&field, "rust"), "MyConfig::default()");
assert_eq!(default_value_for_field(&field, "python"), "None");
assert_eq!(default_value_for_field(&field, "ruby"), "nil");
assert_eq!(default_value_for_field(&field, "go"), "nil");
assert_eq!(default_value_for_field(&field, "java"), "null");
assert_eq!(default_value_for_field(&field, "csharp"), "null");
assert_eq!(default_value_for_field(&field, "php"), "null");
assert_eq!(default_value_for_field(&field, "r"), "NULL");
}
#[test]
fn test_default_value_fallback_duration() {
let field = make_field("timeout", TypeRef::Duration);
assert_eq!(default_value_for_field(&field, "python"), "None");
assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
}
#[test]
fn test_gen_magnus_kwargs_constructor_positional_basic() {
let typ = make_test_type();
let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
assert!(output.contains("fn new("), "should have fn new");
assert!(output.contains("Option<u64>"), "timeout should be Option<u64>");
assert!(output.contains("Option<bool>"), "enabled should be Option<bool>");
assert!(output.contains("Option<String>"), "name should be Option<String>");
assert!(output.contains("-> Self {"), "should return Self");
assert!(
output.contains("timeout: timeout.unwrap_or(30),"),
"should apply int default"
);
assert!(
output.contains("enabled: enabled.unwrap_or(true),"),
"should apply bool default"
);
assert!(
output.contains("name: name.unwrap_or(\"default\".to_string()),"),
"should apply string default"
);
}
#[test]
fn test_gen_magnus_kwargs_constructor_positional_optional_field() {
let mut typ = make_test_type();
typ.fields.push(FieldDef {
name: "extra".to_string(),
ty: TypeRef::String,
optional: true,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
assert!(output.contains("extra,"), "optional field should be assigned directly");
assert!(!output.contains("extra.unwrap"), "optional field should not use unwrap");
}
#[test]
fn test_gen_magnus_kwargs_constructor_unwrap_or_default() {
let mut typ = make_test_type();
typ.fields.push(FieldDef {
name: "count".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("count: count.unwrap_or_default(),"),
"plain primitive with no default should use unwrap_or_default"
);
}
#[test]
fn test_gen_magnus_kwargs_constructor_hash_path_for_many_fields() {
let mut fields: Vec<FieldDef> = (0..16)
.map(|i| FieldDef {
name: format!("field_{i}"),
ty: TypeRef::Primitive(PrimitiveType::U32),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
})
.collect();
fields[0].optional = true;
let typ = TypeDef {
name: "BigConfig".to_string(),
rust_path: "crate::BigConfig".to_string(),
original_rust_path: String::new(),
fields,
methods: vec![],
is_opaque: false,
is_clone: true,
is_copy: false,
doc: String::new(),
cfg: None,
is_trait: false,
has_default: true,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
};
let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("Option<magnus::RHash>"),
"should accept RHash via scan_args"
);
assert!(output.contains("ruby.to_symbol("), "should use symbol lookup");
assert!(
output.contains("field_0: kwargs.get(ruby.to_symbol(\"field_0\")).and_then(|v|"),
"optional field should use and_then"
);
assert!(
output.contains("field_0:").then_some(()).is_some(),
"field_0 should appear in output"
);
}
#[test]
fn test_gen_php_kwargs_constructor_basic() {
let typ = make_test_type();
let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("pub fn __construct("),
"should use PHP constructor name"
);
assert!(
output.contains("timeout: Option<u64>"),
"timeout param should be Option<u64>"
);
assert!(
output.contains("enabled: Option<bool>"),
"enabled param should be Option<bool>"
);
assert!(
output.contains("name: Option<String>"),
"name param should be Option<String>"
);
assert!(output.contains("-> Self {"), "should return Self");
assert!(
output.contains("timeout: timeout.unwrap_or(30),"),
"should apply int default for timeout"
);
assert!(
output.contains("enabled: enabled.unwrap_or(true),"),
"should apply bool default for enabled"
);
assert!(
output.contains("name: name.unwrap_or(\"default\".to_string()),"),
"should apply string default for name"
);
}
#[test]
fn test_gen_php_kwargs_constructor_optional_field_passthrough() {
let mut typ = make_test_type();
typ.fields.push(FieldDef {
name: "tag".to_string(),
ty: TypeRef::String,
optional: true,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("tag,"),
"optional field should be passed through directly"
);
assert!(!output.contains("tag.unwrap"), "optional field should not call unwrap");
}
#[test]
fn test_gen_php_kwargs_constructor_unwrap_or_default_for_primitive() {
let mut typ = make_test_type();
typ.fields.push(FieldDef {
name: "retries".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("retries: retries.unwrap_or_default(),"),
"primitive with no default should use unwrap_or_default"
);
}
#[test]
fn test_gen_rustler_kwargs_constructor_basic() {
let typ = make_test_type();
let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("pub fn new(opts: std::collections::HashMap<String, rustler::Term>)"),
"should accept HashMap of Terms"
);
assert!(output.contains("Self {"), "should construct Self");
assert!(
output.contains("timeout: opts.get(\"timeout\").and_then(|t| t.decode().ok()).unwrap_or(30),"),
"should apply int default for timeout"
);
assert!(
output.contains("enabled: opts.get(\"enabled\").and_then(|t| t.decode().ok()).unwrap_or(true),"),
"should apply bool default for enabled"
);
}
#[test]
fn test_gen_rustler_kwargs_constructor_optional_field() {
let mut typ = make_test_type();
typ.fields.push(FieldDef {
name: "extra".to_string(),
ty: TypeRef::String,
optional: true,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("extra: opts.get(\"extra\").and_then(|t| t.decode().ok()),"),
"optional field should decode without unwrap"
);
}
#[test]
fn test_gen_rustler_kwargs_constructor_named_type_uses_unwrap_or_default() {
let mut typ = make_test_type();
typ.fields.push(FieldDef {
name: "inner".to_string(),
ty: TypeRef::Named("InnerConfig".to_string()),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("inner: opts.get(\"inner\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
"Named type with no default should use unwrap_or_default"
);
}
#[test]
fn test_gen_rustler_kwargs_constructor_string_field_uses_unwrap_or_default() {
let mut typ = make_test_type();
let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("name: opts.get(\"name\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
"String field with quoted default should use unwrap_or_default"
);
typ.fields.push(FieldDef {
name: "label".to_string(),
ty: TypeRef::String,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output2 = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output2.contains("label: opts.get(\"label\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
"String field with no default should use unwrap_or_default"
);
}
#[test]
fn test_gen_extendr_kwargs_constructor_basic() {
let typ = make_test_type();
let empty_enums = ahash::AHashSet::new();
let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper, &empty_enums);
assert!(output.contains("#[extendr]"), "should have extendr attribute");
assert!(
output.contains("pub fn new_config("),
"function name should be lowercase type name"
);
assert!(
output.contains("timeout: Option<u64>"),
"should accept timeout as Option<u64>: {output}"
);
assert!(
output.contains("enabled: Option<bool>"),
"should accept enabled as Option<bool>: {output}"
);
assert!(
output.contains("name: Option<String>"),
"should accept name as Option<String>: {output}"
);
assert!(output.contains("-> Config {"), "should return Config");
assert!(
output.contains("let mut __out = <Config>::default();"),
"should base on Default impl: {output}"
);
assert!(
output.contains("if let Some(v) = timeout { __out.timeout = v; }"),
"should overlay caller-provided timeout"
);
assert!(
output.contains("if let Some(v) = enabled { __out.enabled = v; }"),
"should overlay caller-provided enabled"
);
assert!(
output.contains("if let Some(v) = name { __out.name = v; }"),
"should overlay caller-provided name"
);
}
#[test]
fn test_gen_extendr_kwargs_constructor_uses_option_for_all_fields() {
let typ = make_test_type();
let empty_enums = ahash::AHashSet::new();
let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper, &empty_enums);
assert!(
!output.contains("= TRUE") && !output.contains("= FALSE") && !output.contains("= \"default\""),
"constructor must not use Rust-syntax param defaults: {output}"
);
}
#[test]
fn test_gen_go_functional_options_skips_tuple_fields() {
let mut typ = make_test_type();
typ.fields.push(FieldDef {
name: "_0".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
});
let output = gen_go_functional_options(&typ, &simple_type_mapper);
assert!(
!output.contains("_0"),
"tuple field _0 should be filtered out from Go output"
);
}
#[test]
fn test_gen_magnus_hash_constructor_generic_type_prefix() {
let fields: Vec<FieldDef> = (0..16)
.map(|i| FieldDef {
name: format!("field_{i}"),
ty: if i == 0 {
TypeRef::Vec(Box::new(TypeRef::String))
} else {
TypeRef::Primitive(PrimitiveType::U32)
},
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
})
.collect();
let typ = TypeDef {
name: "WideConfig".to_string(),
rust_path: "crate::WideConfig".to_string(),
original_rust_path: String::new(),
fields,
methods: vec![],
is_opaque: false,
is_clone: true,
is_copy: false,
doc: String::new(),
cfg: None,
is_trait: false,
has_default: true,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
};
let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
output.contains("<Vec<String>>::try_convert"),
"generic types should use UFCS angle-bracket prefix: {output}"
);
}
#[test]
fn test_magnus_hash_constructor_no_double_option_when_ty_is_optional() {
let field = FieldDef {
name: "max_depth".to_string(),
ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
optional: true,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
let mut fields: Vec<FieldDef> = (0..15)
.map(|i| FieldDef {
name: format!("field_{i}"),
ty: TypeRef::Primitive(PrimitiveType::U32),
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
})
.collect();
fields.push(field);
let typ = TypeDef {
name: "UpdateConfig".to_string(),
rust_path: "crate::UpdateConfig".to_string(),
original_rust_path: String::new(),
fields,
methods: vec![],
is_opaque: false,
is_clone: true,
is_copy: false,
doc: String::new(),
cfg: None,
is_trait: false,
has_default: true,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
};
let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
!output.contains("Option<Option<"),
"hash constructor must not emit double Option: {output}"
);
assert!(
output.contains("i64::try_convert"),
"hash constructor should call inner-type::try_convert, not Option<T>::try_convert: {output}"
);
}
#[test]
fn test_magnus_positional_constructor_no_double_option_when_ty_is_optional() {
let field = FieldDef {
name: "max_depth".to_string(),
ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
optional: true,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
};
let typ = TypeDef {
name: "SmallUpdate".to_string(),
rust_path: "crate::SmallUpdate".to_string(),
original_rust_path: String::new(),
fields: vec![field],
methods: vec![],
is_opaque: false,
is_clone: true,
is_copy: false,
doc: String::new(),
cfg: None,
is_trait: false,
has_default: true,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
};
let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
assert!(
!output.contains("Option<Option<"),
"positional constructor must not emit double Option: {output}"
);
assert!(
output.contains("Option<i64>"),
"positional constructor should emit Option<inner> for optional Optional(T): {output}"
);
}
}