1use alef_core::ir::{DefaultValue, FieldDef, PrimitiveType, TypeDef, TypeRef};
2use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
3
4fn is_tuple_field(field: &FieldDef) -> bool {
7 (field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
8 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
9}
10
11fn use_unwrap_or_default(field: &FieldDef) -> bool {
15 if let Some(typed_default) = &field.typed_default {
16 return matches!(typed_default, DefaultValue::Empty | DefaultValue::None);
17 }
18 field.default.is_none() && !matches!(&field.ty, TypeRef::Named(_))
23}
24
25pub fn gen_pyo3_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
28 let signature_defaults = typ
29 .fields
30 .iter()
31 .map(|field| format!("{}={}", field.name, default_value_for_field(field, "python")))
32 .collect::<Vec<_>>()
33 .join(", ");
34 let fields: Vec<_> = typ
35 .fields
36 .iter()
37 .map(|field| {
38 minijinja::context! {
39 name => field.name.clone(),
40 type => type_mapper(&field.ty),
41 }
42 })
43 .collect();
44
45 crate::template_env::render(
46 "config_gen/pyo3_kwargs_constructor.jinja",
47 minijinja::context! {
48 signature_defaults => signature_defaults,
49 fields => fields,
50 },
51 )
52 .trim_end_matches('\n')
53 .to_string()
54}
55
56pub fn gen_napi_defaults_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
58 let fields: Vec<_> = typ
59 .fields
60 .iter()
61 .map(|field| {
62 minijinja::context! {
63 name => field.name.clone(),
64 type => type_mapper(&field.ty),
65 default => default_value_for_field(field, "rust"),
66 }
67 })
68 .collect();
69
70 crate::template_env::render(
71 "config_gen/napi_defaults_constructor.jinja",
72 minijinja::context! {
73 fields => fields,
74 },
75 )
76 .trim_end_matches('\n')
77 .to_string()
78}
79
80pub fn gen_go_functional_options(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
83 let fields: Vec<_> = typ
84 .fields
85 .iter()
86 .filter(|field| !is_tuple_field(field))
87 .map(|field| {
88 minijinja::context! {
89 name => field.name.clone(),
90 pascal_name => field.name.to_pascal_case(),
91 field_name => field.name.to_pascal_case(),
92 go_type => type_mapper(&field.ty),
93 default => default_value_for_field(field, "go"),
94 }
95 })
96 .collect();
97
98 crate::template_env::render(
99 "config_gen/go_functional_options.jinja",
100 minijinja::context! {
101 type_name => typ.name.clone(),
102 fields => fields,
103 },
104 )
105 .trim_end_matches('\n')
106 .to_string()
107}
108
109pub fn gen_java_builder(typ: &TypeDef, package: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
112 let fields: Vec<_> = typ
113 .fields
114 .iter()
115 .map(|field| {
116 minijinja::context! {
117 name_lower => field.name.to_lowercase(),
118 type => type_mapper(&field.ty),
119 default => default_value_for_field(field, "java"),
120 method_name => format!("with{}", field.name.to_pascal_case()),
121 }
122 })
123 .collect();
124
125 crate::template_env::render(
126 "config_gen/java_builder.jinja",
127 minijinja::context! {
128 package => package,
129 type_name => typ.name.clone(),
130 fields => fields,
131 },
132 )
133 .trim_end_matches('\n')
134 .to_string()
135}
136
137pub fn gen_csharp_record(typ: &TypeDef, namespace: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
139 let fields: Vec<_> = typ
140 .fields
141 .iter()
142 .filter(|field| !is_tuple_field(field))
143 .map(|field| {
144 minijinja::context! {
145 type => type_mapper(&field.ty),
146 name_pascal => field.name.to_pascal_case(),
147 default => default_value_for_field(field, "csharp"),
148 }
149 })
150 .collect();
151
152 crate::template_env::render(
153 "config_gen/csharp_record.jinja",
154 minijinja::context! {
155 namespace => namespace,
156 type_name => typ.name.clone(),
157 fields => fields,
158 },
159 )
160 .trim_end_matches('\n')
161 .to_string()
162}
163
164pub fn default_value_for_field(field: &FieldDef, language: &str) -> String {
167 if let Some(typed_default) = &field.typed_default {
169 return match typed_default {
170 DefaultValue::BoolLiteral(b) => match language {
171 "python" => {
172 if *b {
173 "True".to_string()
174 } else {
175 "False".to_string()
176 }
177 }
178 "ruby" => {
179 if *b {
180 "true".to_string()
181 } else {
182 "false".to_string()
183 }
184 }
185 "go" => {
186 if *b {
187 "true".to_string()
188 } else {
189 "false".to_string()
190 }
191 }
192 "java" => {
193 if *b {
194 "true".to_string()
195 } else {
196 "false".to_string()
197 }
198 }
199 "csharp" => {
200 if *b {
201 "true".to_string()
202 } else {
203 "false".to_string()
204 }
205 }
206 "php" => {
207 if *b {
208 "true".to_string()
209 } else {
210 "false".to_string()
211 }
212 }
213 "r" => {
214 if *b {
215 "TRUE".to_string()
216 } else {
217 "FALSE".to_string()
218 }
219 }
220 "rust" => {
221 if *b {
222 "true".to_string()
223 } else {
224 "false".to_string()
225 }
226 }
227 _ => {
228 if *b {
229 "true".to_string()
230 } else {
231 "false".to_string()
232 }
233 }
234 },
235 DefaultValue::StringLiteral(s) => match language {
236 "rust" => format!("\"{}\".to_string()", s.replace('"', "\\\"")),
237 _ => format!("\"{}\"", s.replace('"', "\\\"")),
238 },
239 DefaultValue::IntLiteral(n) => n.to_string(),
240 DefaultValue::FloatLiteral(f) => {
241 let s = f.to_string();
242 if !s.contains('.') { format!("{}.0", s) } else { s }
243 }
244 DefaultValue::EnumVariant(v) => {
245 if matches!(field.ty, TypeRef::String) {
249 let snake = v.to_snake_case();
250 return match language {
251 "rust" => format!("\"{}\".to_string()", snake),
252 _ => format!("\"{}\"", snake),
253 };
254 }
255 match language {
256 "python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
257 "ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
258 "go" => format!("{}{}", field.ty.type_name(), v.to_pascal_case()),
259 "java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
260 "csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
261 "php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
262 "r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
263 "rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
264 _ => v.clone(),
265 }
266 }
267 DefaultValue::Empty => {
268 match &field.ty {
270 TypeRef::Vec(_) => match language {
271 "python" | "ruby" | "csharp" => "[]".to_string(),
272 "go" => "nil".to_string(),
273 "java" => "List.of()".to_string(),
274 "php" => "[]".to_string(),
275 "r" => "c()".to_string(),
276 "rust" => "vec![]".to_string(),
277 _ => "null".to_string(),
278 },
279 TypeRef::Map(_, _) => match language {
280 "python" => "{}".to_string(),
281 "go" => "nil".to_string(),
282 "java" => "Map.of()".to_string(),
283 "rust" => "Default::default()".to_string(),
284 _ => "null".to_string(),
285 },
286 TypeRef::Primitive(p) => match p {
287 PrimitiveType::Bool => match language {
288 "python" => "False".to_string(),
289 "ruby" => "false".to_string(),
290 _ => "false".to_string(),
291 },
292 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
293 _ => "0".to_string(),
294 },
295 TypeRef::String | TypeRef::Char | TypeRef::Path => match language {
296 "rust" => "String::new()".to_string(),
297 _ => "\"\"".to_string(),
298 },
299 TypeRef::Json => match language {
300 "python" | "ruby" => "{}".to_string(),
301 "go" => "json.RawMessage(nil)".to_string(),
302 "java" => "new com.fasterxml.jackson.databind.node.ObjectNode(null)".to_string(),
303 "csharp" => "JObject.Parse(\"{}\")".to_string(),
304 "php" => "[]".to_string(),
305 "r" => "list()".to_string(),
306 "rust" => "serde_json::json!({})".to_string(),
307 _ => "{}".to_string(),
308 },
309 TypeRef::Duration => "0".to_string(),
310 TypeRef::Bytes => match language {
311 "python" => "b\"\"".to_string(),
312 "go" => "[]byte{}".to_string(),
313 "rust" => "vec![]".to_string(),
314 _ => "\"\"".to_string(),
315 },
316 _ => match language {
317 "python" => "None".to_string(),
318 "ruby" => "nil".to_string(),
319 "go" => "nil".to_string(),
320 "rust" => "Default::default()".to_string(),
321 _ => "null".to_string(),
322 },
323 }
324 }
325 DefaultValue::None => match language {
326 "python" => "None".to_string(),
327 "ruby" => "nil".to_string(),
328 "go" => "nil".to_string(),
329 "java" => "null".to_string(),
330 "csharp" => "null".to_string(),
331 "php" => "null".to_string(),
332 "r" => "NULL".to_string(),
333 "rust" => "None".to_string(),
334 _ => "null".to_string(),
335 },
336 };
337 }
338
339 if let Some(default_str) = &field.default {
341 return default_str.clone();
342 }
343
344 match &field.ty {
346 TypeRef::Primitive(p) => match p {
347 alef_core::ir::PrimitiveType::Bool => match language {
348 "python" => "False".to_string(),
349 "ruby" => "false".to_string(),
350 "csharp" => "false".to_string(),
351 "java" => "false".to_string(),
352 "php" => "false".to_string(),
353 "r" => "FALSE".to_string(),
354 _ => "false".to_string(),
355 },
356 alef_core::ir::PrimitiveType::U8
357 | alef_core::ir::PrimitiveType::U16
358 | alef_core::ir::PrimitiveType::U32
359 | alef_core::ir::PrimitiveType::U64
360 | alef_core::ir::PrimitiveType::I8
361 | alef_core::ir::PrimitiveType::I16
362 | alef_core::ir::PrimitiveType::I32
363 | alef_core::ir::PrimitiveType::I64
364 | alef_core::ir::PrimitiveType::Usize
365 | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
366 alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
367 },
368 TypeRef::String | TypeRef::Char => match language {
369 "python" => "\"\"".to_string(),
370 "ruby" => "\"\"".to_string(),
371 "go" => "\"\"".to_string(),
372 "java" => "\"\"".to_string(),
373 "csharp" => "\"\"".to_string(),
374 "php" => "\"\"".to_string(),
375 "r" => "\"\"".to_string(),
376 "rust" => "String::new()".to_string(),
377 _ => "\"\"".to_string(),
378 },
379 TypeRef::Bytes => match language {
380 "python" => "b\"\"".to_string(),
381 "ruby" => "\"\"".to_string(),
382 "go" => "[]byte{}".to_string(),
383 "java" => "new byte[]{}".to_string(),
384 "csharp" => "new byte[]{}".to_string(),
385 "php" => "\"\"".to_string(),
386 "r" => "raw()".to_string(),
387 "rust" => "vec![]".to_string(),
388 _ => "[]".to_string(),
389 },
390 TypeRef::Optional(_) => match language {
391 "python" => "None".to_string(),
392 "ruby" => "nil".to_string(),
393 "go" => "nil".to_string(),
394 "java" => "null".to_string(),
395 "csharp" => "null".to_string(),
396 "php" => "null".to_string(),
397 "r" => "NULL".to_string(),
398 "rust" => "None".to_string(),
399 _ => "null".to_string(),
400 },
401 TypeRef::Vec(_) => match language {
402 "python" => "[]".to_string(),
403 "ruby" => "[]".to_string(),
404 "go" => "[]interface{}{}".to_string(),
405 "java" => "new java.util.ArrayList<>()".to_string(),
406 "csharp" => "[]".to_string(),
407 "php" => "[]".to_string(),
408 "r" => "c()".to_string(),
409 "rust" => "vec![]".to_string(),
410 _ => "[]".to_string(),
411 },
412 TypeRef::Map(_, _) => match language {
413 "python" => "{}".to_string(),
414 "ruby" => "{}".to_string(),
415 "go" => "make(map[string]interface{})".to_string(),
416 "java" => "new java.util.HashMap<>()".to_string(),
417 "csharp" => "new Dictionary<string, object>()".to_string(),
418 "php" => "[]".to_string(),
419 "r" => "list()".to_string(),
420 "rust" => "std::collections::HashMap::new()".to_string(),
421 _ => "{}".to_string(),
422 },
423 TypeRef::Json => match language {
424 "python" => "{}".to_string(),
425 "ruby" => "{}".to_string(),
426 "go" => "json.RawMessage(nil)".to_string(),
427 "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
428 "csharp" => "JObject.Parse(\"{}\")".to_string(),
429 "php" => "[]".to_string(),
430 "r" => "list()".to_string(),
431 "rust" => "serde_json::json!({})".to_string(),
432 _ => "{}".to_string(),
433 },
434 TypeRef::Named(name) => match language {
435 "rust" => format!("{name}::default()"),
436 "python" => "None".to_string(),
437 "ruby" => "nil".to_string(),
438 "go" => "nil".to_string(),
439 "java" => "null".to_string(),
440 "csharp" => "null".to_string(),
441 "php" => "null".to_string(),
442 "r" => "NULL".to_string(),
443 _ => "null".to_string(),
444 },
445 _ => match language {
446 "python" => "None".to_string(),
447 "ruby" => "nil".to_string(),
448 "go" => "nil".to_string(),
449 "java" => "null".to_string(),
450 "csharp" => "null".to_string(),
451 "php" => "null".to_string(),
452 "r" => "NULL".to_string(),
453 "rust" => "Default::default()".to_string(),
454 _ => "null".to_string(),
455 },
456 }
457}
458
459trait TypeRefExt {
461 fn type_name(&self) -> String;
462}
463
464impl TypeRefExt for TypeRef {
465 fn type_name(&self) -> String {
466 match self {
467 TypeRef::Named(n) => n.clone(),
468 TypeRef::Primitive(p) => format!("{:?}", p),
469 TypeRef::String | TypeRef::Char => "String".to_string(),
470 TypeRef::Bytes => "Bytes".to_string(),
471 TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
472 TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
473 TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
474 TypeRef::Path => "Path".to_string(),
475 TypeRef::Unit => "()".to_string(),
476 TypeRef::Json => "Json".to_string(),
477 TypeRef::Duration => "Duration".to_string(),
478 }
479 }
480}
481
482const MAGNUS_MAX_ARITY: usize = 15;
484
485pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
491 let _ = MAGNUS_MAX_ARITY;
496 gen_magnus_hash_constructor(typ, type_mapper)
497}
498
499fn as_type_path_prefix(type_str: &str) -> String {
506 if type_str.contains('<') {
507 format!("<{type_str}>")
508 } else {
509 type_str.to_string()
510 }
511}
512
513fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
516 let fields: Vec<_> = typ
517 .fields
518 .iter()
519 .map(|field| {
520 let is_optional = field_is_optional_in_rust(field);
521 let effective_inner_ty = match &field.ty {
525 TypeRef::Optional(inner) if is_optional => inner.as_ref(),
526 ty => ty,
527 };
528 let inner_type = type_mapper(effective_inner_ty);
529 let type_prefix = as_type_path_prefix(&inner_type);
530
531 let assignment = if is_optional {
532 format!(
534 "kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()),",
535 field.name, type_prefix
536 )
537 } else if use_unwrap_or_default(field) {
538 format!(
539 "kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()).unwrap_or_default(),",
540 field.name, type_prefix
541 )
542 } else {
543 let default_str = if inner_type == "String" {
548 if let Some(DefaultValue::EnumVariant(variant)) = &field.typed_default {
549 use heck::ToSnakeCase;
550 format!("\"{}\".to_string()", variant.to_snake_case())
551 } else {
552 default_value_for_field(field, "rust")
553 }
554 } else {
555 default_value_for_field(field, "rust")
556 };
557 format!(
558 "kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()).unwrap_or({}),",
559 field.name, type_prefix, default_str
560 )
561 };
562
563 minijinja::context! {
564 name => field.name.clone(),
565 assignment => assignment,
566 }
567 })
568 .collect();
569
570 crate::template_env::render(
571 "config_gen/magnus_hash_constructor.jinja",
572 minijinja::context! {
573 fields => fields,
574 },
575 )
576}
577
578fn field_is_optional_in_rust(field: &FieldDef) -> bool {
583 field.optional || matches!(&field.ty, TypeRef::Optional(_))
584}
585
586#[allow(dead_code)]
593fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
594 let fields: Vec<_> = typ
595 .fields
596 .iter()
597 .map(|field| {
598 let is_optional = field_is_optional_in_rust(field);
602 let param_type = if is_optional {
603 let effective_inner_ty = match &field.ty {
607 TypeRef::Optional(inner) => inner.as_ref(),
608 ty => ty,
609 };
610 let inner_type = type_mapper(effective_inner_ty);
611 format!("Option<{}>", inner_type)
612 } else {
613 let field_type = type_mapper(&field.ty);
614 format!("Option<{}>", field_type)
615 };
616
617 let assignment = if is_optional {
618 field.name.clone()
620 } else if use_unwrap_or_default(field) {
621 format!("{}.unwrap_or_default()", field.name)
622 } else {
623 let default_str = default_value_for_field(field, "rust");
624 format!("{}.unwrap_or({})", field.name, default_str)
625 };
626
627 minijinja::context! {
628 name => field.name.clone(),
629 param_type => param_type,
630 assignment => assignment,
631 }
632 })
633 .collect();
634
635 crate::template_env::render(
636 "config_gen/magnus_positional_constructor.jinja",
637 minijinja::context! {
638 fields => fields,
639 },
640 )
641}
642
643pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
647 let fields: Vec<_> = typ
648 .fields
649 .iter()
650 .map(|field| {
651 let mapped = type_mapper(&field.ty);
652 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
653
654 let assignment = if is_optional_field {
655 field.name.clone()
657 } else if use_unwrap_or_default(field) {
658 format!("{}.unwrap_or_default()", field.name)
660 } else {
661 let default_str = default_value_for_field(field, "rust");
663 format!("{}.unwrap_or({})", field.name, default_str)
664 };
665
666 minijinja::context! {
667 name => field.name.clone(),
668 ty => mapped,
669 assignment => assignment,
670 }
671 })
672 .collect();
673
674 crate::template_env::render(
675 "config_gen/php_kwargs_constructor.jinja",
676 minijinja::context! {
677 fields => fields,
678 },
679 )
680}
681
682pub fn gen_rustler_kwargs_constructor_with_exclude(
686 typ: &TypeDef,
687 _type_mapper: &dyn Fn(&TypeRef) -> String,
688 exclude_fields: &std::collections::HashSet<String>,
689) -> String {
690 let fields: Vec<_> = typ
692 .fields
693 .iter()
694 .filter(|f| !exclude_fields.contains(&f.name))
695 .map(|field| {
696 let assignment = if field.optional {
697 format!("opts.get(\"{}\").and_then(|t| t.decode().ok()),", field.name)
698 } else if use_unwrap_or_default(field) {
699 format!(
700 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
701 field.name
702 )
703 } else {
704 let default_str = default_value_for_field(field, "rust");
705 let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
706
707 if (is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char))
708 || matches!(&field.ty, TypeRef::Named(_))
709 {
710 format!(
711 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
712 field.name
713 )
714 } else {
715 format!(
716 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
717 field.name, default_str
718 )
719 }
720 };
721
722 minijinja::context! {
723 name => field.name.clone(),
724 assignment => assignment,
725 }
726 })
727 .collect();
728
729 crate::template_env::render(
730 "config_gen/rustler_kwargs_constructor.jinja",
731 minijinja::context! {
732 fields => fields,
733 },
734 )
735}
736
737pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
740 let fields: Vec<_> = typ
742 .fields
743 .iter()
744 .map(|field| {
745 let assignment = if field.optional {
746 format!("opts.get(\"{}\").and_then(|t| t.decode().ok()),", field.name)
747 } else if use_unwrap_or_default(field) {
748 format!(
749 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
750 field.name
751 )
752 } else {
753 let default_str = default_value_for_field(field, "rust");
754 let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
755
756 let unwrap_default = (is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char))
757 || matches!(&field.ty, TypeRef::Named(_));
758 if unwrap_default {
759 format!(
760 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
761 field.name
762 )
763 } else {
764 format!(
765 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
766 field.name, default_str
767 )
768 }
769 };
770
771 minijinja::context! {
772 name => field.name.clone(),
773 assignment => assignment,
774 }
775 })
776 .collect();
777
778 crate::template_env::render(
779 "config_gen/rustler_kwargs_constructor.jinja",
780 minijinja::context! {
781 fields => fields,
782 },
783 )
784}
785
786pub fn gen_extendr_kwargs_constructor(
801 typ: &TypeDef,
802 type_mapper: &dyn Fn(&TypeRef) -> String,
803 enum_names: &ahash::AHashSet<String>,
804) -> String {
805 let is_named_enum = |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Named(n) if enum_names.contains(n.as_str())) };
807 let is_named_struct =
808 |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Named(n) if !enum_names.contains(n.as_str())) };
809 let is_optional_named_struct = |ty: &TypeRef| -> bool {
810 if let TypeRef::Optional(inner) = ty {
811 is_named_struct(inner)
812 } else {
813 false
814 }
815 };
816 let ty_is_optional = |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Optional(_)) };
817
818 let emittable_fields: Vec<_> = typ
820 .fields
821 .iter()
822 .filter(|f| !is_named_struct(&f.ty) && !is_optional_named_struct(&f.ty))
823 .map(|field| {
824 let param_type = if is_named_enum(&field.ty) {
825 "Option<String>".to_string()
826 } else if ty_is_optional(&field.ty) {
827 type_mapper(&field.ty)
828 } else {
829 format!("Option<{}>", type_mapper(&field.ty))
830 };
831
832 minijinja::context! {
833 name => field.name.clone(),
834 type => param_type,
835 }
836 })
837 .collect();
838
839 let body_assignments: Vec<_> = typ
841 .fields
842 .iter()
843 .filter(|f| !is_named_struct(&f.ty) && !is_optional_named_struct(&f.ty))
844 .map(|field| {
845 let code = if is_named_enum(&field.ty) {
846 if field.optional {
847 format!(
848 "if let Some(v) = {} {{ __out.{} = serde_json::from_str(&format!(\"\\\"{{v}}\\\"\")).ok(); }}",
849 field.name, field.name
850 )
851 } else {
852 format!(
853 "if let Some(v) = {} {{ if let Ok(parsed) = serde_json::from_str(&format!(\"\\\"{{v}}\\\"\")) {{ __out.{} = parsed; }} }}",
854 field.name, field.name
855 )
856 }
857 } else if ty_is_optional(&field.ty) || field.optional {
858 format!(
859 "if let Some(v) = {} {{ __out.{} = Some(v); }}",
860 field.name, field.name
861 )
862 } else {
863 format!(
864 "if let Some(v) = {} {{ __out.{} = v; }}",
865 field.name, field.name
866 )
867 };
868
869 minijinja::context! {
870 code => code,
871 }
872 })
873 .collect();
874
875 crate::template_env::render(
876 "config_gen/extendr_kwargs_constructor.jinja",
877 minijinja::context! {
878 type_name => typ.name.clone(),
879 type_name_lower => typ.name.to_lowercase(),
880 params => emittable_fields,
881 body_assignments => body_assignments,
882 },
883 )
884}
885
886#[cfg(test)]
887mod tests {
888 use super::*;
889 use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
890
891 fn make_test_type() -> TypeDef {
892 TypeDef {
893 name: "Config".to_string(),
894 rust_path: "my_crate::Config".to_string(),
895 original_rust_path: String::new(),
896 fields: vec![
897 FieldDef {
898 name: "timeout".to_string(),
899 ty: TypeRef::Primitive(PrimitiveType::U64),
900 optional: false,
901 default: Some("30".to_string()),
902 doc: "Timeout in seconds".to_string(),
903 sanitized: false,
904 is_boxed: false,
905 type_rust_path: None,
906 cfg: None,
907 typed_default: Some(DefaultValue::IntLiteral(30)),
908 core_wrapper: CoreWrapper::None,
909 vec_inner_core_wrapper: CoreWrapper::None,
910 newtype_wrapper: None,
911 serde_rename: None,
912 serde_flatten: false,
913 },
914 FieldDef {
915 name: "enabled".to_string(),
916 ty: TypeRef::Primitive(PrimitiveType::Bool),
917 optional: false,
918 default: None,
919 doc: "Enable feature".to_string(),
920 sanitized: false,
921 is_boxed: false,
922 type_rust_path: None,
923 cfg: None,
924 typed_default: Some(DefaultValue::BoolLiteral(true)),
925 core_wrapper: CoreWrapper::None,
926 vec_inner_core_wrapper: CoreWrapper::None,
927 newtype_wrapper: None,
928 serde_rename: None,
929 serde_flatten: false,
930 },
931 FieldDef {
932 name: "name".to_string(),
933 ty: TypeRef::String,
934 optional: false,
935 default: None,
936 doc: "Config name".to_string(),
937 sanitized: false,
938 is_boxed: false,
939 type_rust_path: None,
940 cfg: None,
941 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
942 core_wrapper: CoreWrapper::None,
943 vec_inner_core_wrapper: CoreWrapper::None,
944 newtype_wrapper: None,
945 serde_rename: None,
946 serde_flatten: false,
947 },
948 ],
949 methods: vec![],
950 is_opaque: false,
951 is_clone: true,
952 is_copy: false,
953 doc: "Configuration type".to_string(),
954 cfg: None,
955 is_trait: false,
956 has_default: true,
957 has_stripped_cfg_fields: false,
958 is_return_type: false,
959 serde_rename_all: None,
960 has_serde: false,
961 super_traits: vec![],
962 }
963 }
964
965 #[test]
966 fn test_default_value_bool_true_python() {
967 let field = FieldDef {
968 name: "enabled".to_string(),
969 ty: TypeRef::Primitive(PrimitiveType::Bool),
970 optional: false,
971 default: None,
972 doc: String::new(),
973 sanitized: false,
974 is_boxed: false,
975 type_rust_path: None,
976 cfg: None,
977 typed_default: Some(DefaultValue::BoolLiteral(true)),
978 core_wrapper: CoreWrapper::None,
979 vec_inner_core_wrapper: CoreWrapper::None,
980 newtype_wrapper: None,
981 serde_rename: None,
982 serde_flatten: false,
983 };
984 assert_eq!(default_value_for_field(&field, "python"), "True");
985 }
986
987 #[test]
988 fn test_default_value_bool_false_go() {
989 let field = FieldDef {
990 name: "enabled".to_string(),
991 ty: TypeRef::Primitive(PrimitiveType::Bool),
992 optional: false,
993 default: None,
994 doc: String::new(),
995 sanitized: false,
996 is_boxed: false,
997 type_rust_path: None,
998 cfg: None,
999 typed_default: Some(DefaultValue::BoolLiteral(false)),
1000 core_wrapper: CoreWrapper::None,
1001 vec_inner_core_wrapper: CoreWrapper::None,
1002 newtype_wrapper: None,
1003 serde_rename: None,
1004 serde_flatten: false,
1005 };
1006 assert_eq!(default_value_for_field(&field, "go"), "false");
1007 }
1008
1009 #[test]
1010 fn test_default_value_string_literal() {
1011 let field = FieldDef {
1012 name: "name".to_string(),
1013 ty: TypeRef::String,
1014 optional: false,
1015 default: None,
1016 doc: String::new(),
1017 sanitized: false,
1018 is_boxed: false,
1019 type_rust_path: None,
1020 cfg: None,
1021 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1022 core_wrapper: CoreWrapper::None,
1023 vec_inner_core_wrapper: CoreWrapper::None,
1024 newtype_wrapper: None,
1025 serde_rename: None,
1026 serde_flatten: false,
1027 };
1028 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
1029 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
1030 }
1031
1032 #[test]
1033 fn test_default_value_int_literal() {
1034 let field = FieldDef {
1035 name: "timeout".to_string(),
1036 ty: TypeRef::Primitive(PrimitiveType::U64),
1037 optional: false,
1038 default: None,
1039 doc: String::new(),
1040 sanitized: false,
1041 is_boxed: false,
1042 type_rust_path: None,
1043 cfg: None,
1044 typed_default: Some(DefaultValue::IntLiteral(42)),
1045 core_wrapper: CoreWrapper::None,
1046 vec_inner_core_wrapper: CoreWrapper::None,
1047 newtype_wrapper: None,
1048 serde_rename: None,
1049 serde_flatten: false,
1050 };
1051 let result = default_value_for_field(&field, "python");
1052 assert_eq!(result, "42");
1053 }
1054
1055 #[test]
1056 fn test_default_value_none() {
1057 let field = FieldDef {
1058 name: "maybe".to_string(),
1059 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1060 optional: true,
1061 default: None,
1062 doc: String::new(),
1063 sanitized: false,
1064 is_boxed: false,
1065 type_rust_path: None,
1066 cfg: None,
1067 typed_default: Some(DefaultValue::None),
1068 core_wrapper: CoreWrapper::None,
1069 vec_inner_core_wrapper: CoreWrapper::None,
1070 newtype_wrapper: None,
1071 serde_rename: None,
1072 serde_flatten: false,
1073 };
1074 assert_eq!(default_value_for_field(&field, "python"), "None");
1075 assert_eq!(default_value_for_field(&field, "go"), "nil");
1076 assert_eq!(default_value_for_field(&field, "java"), "null");
1077 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1078 }
1079
1080 #[test]
1081 fn test_default_value_fallback_string() {
1082 let field = FieldDef {
1083 name: "name".to_string(),
1084 ty: TypeRef::String,
1085 optional: false,
1086 default: Some("\"custom\"".to_string()),
1087 doc: String::new(),
1088 sanitized: false,
1089 is_boxed: false,
1090 type_rust_path: None,
1091 cfg: None,
1092 typed_default: None,
1093 core_wrapper: CoreWrapper::None,
1094 vec_inner_core_wrapper: CoreWrapper::None,
1095 newtype_wrapper: None,
1096 serde_rename: None,
1097 serde_flatten: false,
1098 };
1099 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1100 }
1101
1102 #[test]
1103 fn test_gen_pyo3_kwargs_constructor() {
1104 let typ = make_test_type();
1105 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1106 TypeRef::Primitive(p) => format!("{:?}", p),
1107 TypeRef::String | TypeRef::Char => "str".to_string(),
1108 _ => "Any".to_string(),
1109 });
1110
1111 assert!(output.contains("#[new]"));
1112 assert!(output.contains("#[pyo3(signature = ("));
1113 assert!(output.contains("timeout=30"));
1114 assert!(output.contains("enabled=True"));
1115 assert!(output.contains("name=\"default\""));
1116 assert!(output.contains("fn new("));
1117 }
1118
1119 #[test]
1120 fn test_gen_napi_defaults_constructor() {
1121 let typ = make_test_type();
1122 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1123 TypeRef::Primitive(p) => format!("{:?}", p),
1124 TypeRef::String | TypeRef::Char => "String".to_string(),
1125 _ => "Value".to_string(),
1126 });
1127
1128 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1129 assert!(output.contains("timeout"));
1130 assert!(output.contains("enabled"));
1131 assert!(output.contains("name"));
1132 }
1133
1134 #[test]
1135 fn test_gen_go_functional_options() {
1136 let typ = make_test_type();
1137 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1138 TypeRef::Primitive(p) => match p {
1139 PrimitiveType::U64 => "uint64".to_string(),
1140 PrimitiveType::Bool => "bool".to_string(),
1141 _ => "interface{}".to_string(),
1142 },
1143 TypeRef::String | TypeRef::Char => "string".to_string(),
1144 _ => "interface{}".to_string(),
1145 });
1146
1147 assert!(output.contains("type Config struct {"));
1148 assert!(output.contains("type ConfigOption func(*Config)"));
1149 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1150 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1151 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1152 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1153 }
1154
1155 #[test]
1156 fn test_gen_java_builder() {
1157 let typ = make_test_type();
1158 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1159 TypeRef::Primitive(p) => match p {
1160 PrimitiveType::U64 => "long".to_string(),
1161 PrimitiveType::Bool => "boolean".to_string(),
1162 _ => "int".to_string(),
1163 },
1164 TypeRef::String | TypeRef::Char => "String".to_string(),
1165 _ => "Object".to_string(),
1166 });
1167
1168 assert!(output.contains("package dev.test;"));
1169 assert!(output.contains("public class ConfigBuilder"));
1170 assert!(output.contains("withTimeout"));
1171 assert!(output.contains("withEnabled"));
1172 assert!(output.contains("withName"));
1173 assert!(output.contains("public Config build()"));
1174 }
1175
1176 #[test]
1177 fn test_gen_csharp_record() {
1178 let typ = make_test_type();
1179 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1180 TypeRef::Primitive(p) => match p {
1181 PrimitiveType::U64 => "ulong".to_string(),
1182 PrimitiveType::Bool => "bool".to_string(),
1183 _ => "int".to_string(),
1184 },
1185 TypeRef::String | TypeRef::Char => "string".to_string(),
1186 _ => "object".to_string(),
1187 });
1188
1189 assert!(output.contains("namespace MyNamespace;"));
1190 assert!(output.contains("public record Config"));
1191 assert!(output.contains("public ulong Timeout"));
1192 assert!(output.contains("public bool Enabled"));
1193 assert!(output.contains("public string Name"));
1194 assert!(output.contains("init;"));
1195 }
1196
1197 #[test]
1198 fn test_default_value_float_literal() {
1199 let field = FieldDef {
1200 name: "ratio".to_string(),
1201 ty: TypeRef::Primitive(PrimitiveType::F64),
1202 optional: false,
1203 default: None,
1204 doc: String::new(),
1205 sanitized: false,
1206 is_boxed: false,
1207 type_rust_path: None,
1208 cfg: None,
1209 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1210 core_wrapper: CoreWrapper::None,
1211 vec_inner_core_wrapper: CoreWrapper::None,
1212 newtype_wrapper: None,
1213 serde_rename: None,
1214 serde_flatten: false,
1215 };
1216 let result = default_value_for_field(&field, "python");
1217 assert!(result.contains("1.5"));
1218 }
1219
1220 #[test]
1221 fn test_default_value_no_typed_no_default() {
1222 let field = FieldDef {
1223 name: "count".to_string(),
1224 ty: TypeRef::Primitive(PrimitiveType::U32),
1225 optional: false,
1226 default: None,
1227 doc: String::new(),
1228 sanitized: false,
1229 is_boxed: false,
1230 type_rust_path: None,
1231 cfg: None,
1232 typed_default: None,
1233 core_wrapper: CoreWrapper::None,
1234 vec_inner_core_wrapper: CoreWrapper::None,
1235 newtype_wrapper: None,
1236 serde_rename: None,
1237 serde_flatten: false,
1238 };
1239 assert_eq!(default_value_for_field(&field, "python"), "0");
1241 assert_eq!(default_value_for_field(&field, "go"), "0");
1242 }
1243
1244 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1245 FieldDef {
1246 name: name.to_string(),
1247 ty,
1248 optional: false,
1249 default: None,
1250 doc: String::new(),
1251 sanitized: false,
1252 is_boxed: false,
1253 type_rust_path: None,
1254 cfg: None,
1255 typed_default: None,
1256 core_wrapper: CoreWrapper::None,
1257 vec_inner_core_wrapper: CoreWrapper::None,
1258 newtype_wrapper: None,
1259 serde_rename: None,
1260 serde_flatten: false,
1261 }
1262 }
1263
1264 fn simple_type_mapper(tr: &TypeRef) -> String {
1265 match tr {
1266 TypeRef::Primitive(p) => match p {
1267 PrimitiveType::U64 => "u64".to_string(),
1268 PrimitiveType::Bool => "bool".to_string(),
1269 PrimitiveType::U32 => "u32".to_string(),
1270 _ => "i64".to_string(),
1271 },
1272 TypeRef::String | TypeRef::Char => "String".to_string(),
1273 TypeRef::Optional(inner) => format!("Option<{}>", simple_type_mapper(inner)),
1274 TypeRef::Vec(inner) => format!("Vec<{}>", simple_type_mapper(inner)),
1275 TypeRef::Named(n) => n.clone(),
1276 _ => "Value".to_string(),
1277 }
1278 }
1279
1280 #[test]
1285 fn test_default_value_bool_literal_ruby() {
1286 let field = FieldDef {
1287 name: "flag".to_string(),
1288 ty: TypeRef::Primitive(PrimitiveType::Bool),
1289 optional: false,
1290 default: None,
1291 doc: String::new(),
1292 sanitized: false,
1293 is_boxed: false,
1294 type_rust_path: None,
1295 cfg: None,
1296 typed_default: Some(DefaultValue::BoolLiteral(true)),
1297 core_wrapper: CoreWrapper::None,
1298 vec_inner_core_wrapper: CoreWrapper::None,
1299 newtype_wrapper: None,
1300 serde_rename: None,
1301 serde_flatten: false,
1302 };
1303 assert_eq!(default_value_for_field(&field, "ruby"), "true");
1304 assert_eq!(default_value_for_field(&field, "php"), "true");
1305 assert_eq!(default_value_for_field(&field, "csharp"), "true");
1306 assert_eq!(default_value_for_field(&field, "java"), "true");
1307 assert_eq!(default_value_for_field(&field, "rust"), "true");
1308 }
1309
1310 #[test]
1311 fn test_default_value_bool_literal_r() {
1312 let field = FieldDef {
1313 name: "flag".to_string(),
1314 ty: TypeRef::Primitive(PrimitiveType::Bool),
1315 optional: false,
1316 default: None,
1317 doc: String::new(),
1318 sanitized: false,
1319 is_boxed: false,
1320 type_rust_path: None,
1321 cfg: None,
1322 typed_default: Some(DefaultValue::BoolLiteral(false)),
1323 core_wrapper: CoreWrapper::None,
1324 vec_inner_core_wrapper: CoreWrapper::None,
1325 newtype_wrapper: None,
1326 serde_rename: None,
1327 serde_flatten: false,
1328 };
1329 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1330 }
1331
1332 #[test]
1333 fn test_default_value_string_literal_rust() {
1334 let field = FieldDef {
1335 name: "label".to_string(),
1336 ty: TypeRef::String,
1337 optional: false,
1338 default: None,
1339 doc: String::new(),
1340 sanitized: false,
1341 is_boxed: false,
1342 type_rust_path: None,
1343 cfg: None,
1344 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1345 core_wrapper: CoreWrapper::None,
1346 vec_inner_core_wrapper: CoreWrapper::None,
1347 newtype_wrapper: None,
1348 serde_rename: None,
1349 serde_flatten: false,
1350 };
1351 assert_eq!(default_value_for_field(&field, "rust"), "\"hello\".to_string()");
1352 }
1353
1354 #[test]
1355 fn test_default_value_string_literal_escapes_quotes() {
1356 let field = FieldDef {
1357 name: "label".to_string(),
1358 ty: TypeRef::String,
1359 optional: false,
1360 default: None,
1361 doc: String::new(),
1362 sanitized: false,
1363 is_boxed: false,
1364 type_rust_path: None,
1365 cfg: None,
1366 typed_default: Some(DefaultValue::StringLiteral("say \"hi\"".to_string())),
1367 core_wrapper: CoreWrapper::None,
1368 vec_inner_core_wrapper: CoreWrapper::None,
1369 newtype_wrapper: None,
1370 serde_rename: None,
1371 serde_flatten: false,
1372 };
1373 assert_eq!(default_value_for_field(&field, "python"), "\"say \\\"hi\\\"\"");
1374 }
1375
1376 #[test]
1377 fn test_default_value_float_literal_whole_number() {
1378 let field = FieldDef {
1380 name: "scale".to_string(),
1381 ty: TypeRef::Primitive(PrimitiveType::F32),
1382 optional: false,
1383 default: None,
1384 doc: String::new(),
1385 sanitized: false,
1386 is_boxed: false,
1387 type_rust_path: None,
1388 cfg: None,
1389 typed_default: Some(DefaultValue::FloatLiteral(2.0)),
1390 core_wrapper: CoreWrapper::None,
1391 vec_inner_core_wrapper: CoreWrapper::None,
1392 newtype_wrapper: None,
1393 serde_rename: None,
1394 serde_flatten: false,
1395 };
1396 let result = default_value_for_field(&field, "python");
1397 assert!(result.contains('.'), "whole-number float should contain '.': {result}");
1398 }
1399
1400 #[test]
1401 fn test_default_value_enum_variant_per_language() {
1402 let field = FieldDef {
1403 name: "format".to_string(),
1404 ty: TypeRef::Named("OutputFormat".to_string()),
1405 optional: false,
1406 default: None,
1407 doc: String::new(),
1408 sanitized: false,
1409 is_boxed: false,
1410 type_rust_path: None,
1411 cfg: None,
1412 typed_default: Some(DefaultValue::EnumVariant("JsonOutput".to_string())),
1413 core_wrapper: CoreWrapper::None,
1414 vec_inner_core_wrapper: CoreWrapper::None,
1415 newtype_wrapper: None,
1416 serde_rename: None,
1417 serde_flatten: false,
1418 };
1419 assert_eq!(default_value_for_field(&field, "python"), "OutputFormat.JSON_OUTPUT");
1420 assert_eq!(default_value_for_field(&field, "ruby"), "OutputFormat::JsonOutput");
1421 assert_eq!(default_value_for_field(&field, "go"), "OutputFormatJsonOutput");
1422 assert_eq!(default_value_for_field(&field, "java"), "OutputFormat.JSON_OUTPUT");
1423 assert_eq!(default_value_for_field(&field, "csharp"), "OutputFormat.JsonOutput");
1424 assert_eq!(default_value_for_field(&field, "php"), "OutputFormat::JsonOutput");
1425 assert_eq!(default_value_for_field(&field, "r"), "OutputFormat$JsonOutput");
1426 assert_eq!(default_value_for_field(&field, "rust"), "OutputFormat::JsonOutput");
1427 }
1428
1429 #[test]
1430 fn test_default_value_empty_vec_per_language() {
1431 let field = FieldDef {
1432 name: "items".to_string(),
1433 ty: TypeRef::Vec(Box::new(TypeRef::String)),
1434 optional: false,
1435 default: None,
1436 doc: String::new(),
1437 sanitized: false,
1438 is_boxed: false,
1439 type_rust_path: None,
1440 cfg: None,
1441 typed_default: Some(DefaultValue::Empty),
1442 core_wrapper: CoreWrapper::None,
1443 vec_inner_core_wrapper: CoreWrapper::None,
1444 newtype_wrapper: None,
1445 serde_rename: None,
1446 serde_flatten: false,
1447 };
1448 assert_eq!(default_value_for_field(&field, "python"), "[]");
1449 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1450 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1451 assert_eq!(default_value_for_field(&field, "go"), "nil");
1452 assert_eq!(default_value_for_field(&field, "java"), "List.of()");
1453 assert_eq!(default_value_for_field(&field, "php"), "[]");
1454 assert_eq!(default_value_for_field(&field, "r"), "c()");
1455 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1456 }
1457
1458 #[test]
1459 fn test_default_value_empty_map_per_language() {
1460 let field = FieldDef {
1461 name: "meta".to_string(),
1462 ty: TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1463 optional: false,
1464 default: None,
1465 doc: String::new(),
1466 sanitized: false,
1467 is_boxed: false,
1468 type_rust_path: None,
1469 cfg: None,
1470 typed_default: Some(DefaultValue::Empty),
1471 core_wrapper: CoreWrapper::None,
1472 vec_inner_core_wrapper: CoreWrapper::None,
1473 newtype_wrapper: None,
1474 serde_rename: None,
1475 serde_flatten: false,
1476 };
1477 assert_eq!(default_value_for_field(&field, "python"), "{}");
1478 assert_eq!(default_value_for_field(&field, "go"), "nil");
1479 assert_eq!(default_value_for_field(&field, "java"), "Map.of()");
1480 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1481 }
1482
1483 #[test]
1484 fn test_default_value_empty_bool_primitive() {
1485 let field = FieldDef {
1486 name: "flag".to_string(),
1487 ty: TypeRef::Primitive(PrimitiveType::Bool),
1488 optional: false,
1489 default: None,
1490 doc: String::new(),
1491 sanitized: false,
1492 is_boxed: false,
1493 type_rust_path: None,
1494 cfg: None,
1495 typed_default: Some(DefaultValue::Empty),
1496 core_wrapper: CoreWrapper::None,
1497 vec_inner_core_wrapper: CoreWrapper::None,
1498 newtype_wrapper: None,
1499 serde_rename: None,
1500 serde_flatten: false,
1501 };
1502 assert_eq!(default_value_for_field(&field, "python"), "False");
1503 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1504 assert_eq!(default_value_for_field(&field, "go"), "false");
1505 }
1506
1507 #[test]
1508 fn test_default_value_empty_float_primitive() {
1509 let field = FieldDef {
1510 name: "ratio".to_string(),
1511 ty: TypeRef::Primitive(PrimitiveType::F64),
1512 optional: false,
1513 default: None,
1514 doc: String::new(),
1515 sanitized: false,
1516 is_boxed: false,
1517 type_rust_path: None,
1518 cfg: None,
1519 typed_default: Some(DefaultValue::Empty),
1520 core_wrapper: CoreWrapper::None,
1521 vec_inner_core_wrapper: CoreWrapper::None,
1522 newtype_wrapper: None,
1523 serde_rename: None,
1524 serde_flatten: false,
1525 };
1526 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1527 }
1528
1529 #[test]
1530 fn test_default_value_empty_string_type() {
1531 let field = FieldDef {
1532 name: "label".to_string(),
1533 ty: TypeRef::String,
1534 optional: false,
1535 default: None,
1536 doc: String::new(),
1537 sanitized: false,
1538 is_boxed: false,
1539 type_rust_path: None,
1540 cfg: None,
1541 typed_default: Some(DefaultValue::Empty),
1542 core_wrapper: CoreWrapper::None,
1543 vec_inner_core_wrapper: CoreWrapper::None,
1544 newtype_wrapper: None,
1545 serde_rename: None,
1546 serde_flatten: false,
1547 };
1548 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1549 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1550 }
1551
1552 #[test]
1553 fn test_default_value_empty_bytes_type() {
1554 let field = FieldDef {
1555 name: "data".to_string(),
1556 ty: TypeRef::Bytes,
1557 optional: false,
1558 default: None,
1559 doc: String::new(),
1560 sanitized: false,
1561 is_boxed: false,
1562 type_rust_path: None,
1563 cfg: None,
1564 typed_default: Some(DefaultValue::Empty),
1565 core_wrapper: CoreWrapper::None,
1566 vec_inner_core_wrapper: CoreWrapper::None,
1567 newtype_wrapper: None,
1568 serde_rename: None,
1569 serde_flatten: false,
1570 };
1571 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1572 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1573 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1574 }
1575
1576 #[test]
1577 fn test_default_value_empty_json_type() {
1578 let field = FieldDef {
1579 name: "payload".to_string(),
1580 ty: TypeRef::Json,
1581 optional: false,
1582 default: None,
1583 doc: String::new(),
1584 sanitized: false,
1585 is_boxed: false,
1586 type_rust_path: None,
1587 cfg: None,
1588 typed_default: Some(DefaultValue::Empty),
1589 core_wrapper: CoreWrapper::None,
1590 vec_inner_core_wrapper: CoreWrapper::None,
1591 newtype_wrapper: None,
1592 serde_rename: None,
1593 serde_flatten: false,
1594 };
1595 assert_eq!(default_value_for_field(&field, "python"), "{}");
1596 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1597 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1598 assert_eq!(default_value_for_field(&field, "r"), "list()");
1599 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1600 }
1601
1602 #[test]
1603 fn test_default_value_none_ruby_php_r() {
1604 let field = FieldDef {
1605 name: "maybe".to_string(),
1606 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1607 optional: true,
1608 default: None,
1609 doc: String::new(),
1610 sanitized: false,
1611 is_boxed: false,
1612 type_rust_path: None,
1613 cfg: None,
1614 typed_default: Some(DefaultValue::None),
1615 core_wrapper: CoreWrapper::None,
1616 vec_inner_core_wrapper: CoreWrapper::None,
1617 newtype_wrapper: None,
1618 serde_rename: None,
1619 serde_flatten: false,
1620 };
1621 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1622 assert_eq!(default_value_for_field(&field, "php"), "null");
1623 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1624 assert_eq!(default_value_for_field(&field, "rust"), "None");
1625 }
1626
1627 #[test]
1632 fn test_default_value_fallback_bool_all_languages() {
1633 let field = make_field("flag", TypeRef::Primitive(PrimitiveType::Bool));
1634 assert_eq!(default_value_for_field(&field, "python"), "False");
1635 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1636 assert_eq!(default_value_for_field(&field, "csharp"), "false");
1637 assert_eq!(default_value_for_field(&field, "java"), "false");
1638 assert_eq!(default_value_for_field(&field, "php"), "false");
1639 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1640 assert_eq!(default_value_for_field(&field, "rust"), "false");
1641 }
1642
1643 #[test]
1644 fn test_default_value_fallback_float() {
1645 let field = make_field("ratio", TypeRef::Primitive(PrimitiveType::F64));
1646 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1647 assert_eq!(default_value_for_field(&field, "rust"), "0.0");
1648 }
1649
1650 #[test]
1651 fn test_default_value_fallback_string_all_languages() {
1652 let field = make_field("name", TypeRef::String);
1653 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1654 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1655 assert_eq!(default_value_for_field(&field, "go"), "\"\"");
1656 assert_eq!(default_value_for_field(&field, "java"), "\"\"");
1657 assert_eq!(default_value_for_field(&field, "csharp"), "\"\"");
1658 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1659 assert_eq!(default_value_for_field(&field, "r"), "\"\"");
1660 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1661 }
1662
1663 #[test]
1664 fn test_default_value_fallback_bytes_all_languages() {
1665 let field = make_field("data", TypeRef::Bytes);
1666 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1667 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1668 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1669 assert_eq!(default_value_for_field(&field, "java"), "new byte[]{}");
1670 assert_eq!(default_value_for_field(&field, "csharp"), "new byte[]{}");
1671 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1672 assert_eq!(default_value_for_field(&field, "r"), "raw()");
1673 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1674 }
1675
1676 #[test]
1677 fn test_default_value_fallback_optional() {
1678 let field = make_field("maybe", TypeRef::Optional(Box::new(TypeRef::String)));
1679 assert_eq!(default_value_for_field(&field, "python"), "None");
1680 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1681 assert_eq!(default_value_for_field(&field, "go"), "nil");
1682 assert_eq!(default_value_for_field(&field, "java"), "null");
1683 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1684 assert_eq!(default_value_for_field(&field, "php"), "null");
1685 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1686 assert_eq!(default_value_for_field(&field, "rust"), "None");
1687 }
1688
1689 #[test]
1690 fn test_default_value_fallback_vec_all_languages() {
1691 let field = make_field("items", TypeRef::Vec(Box::new(TypeRef::String)));
1692 assert_eq!(default_value_for_field(&field, "python"), "[]");
1693 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1694 assert_eq!(default_value_for_field(&field, "go"), "[]interface{}{}");
1695 assert_eq!(default_value_for_field(&field, "java"), "new java.util.ArrayList<>()");
1696 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1697 assert_eq!(default_value_for_field(&field, "php"), "[]");
1698 assert_eq!(default_value_for_field(&field, "r"), "c()");
1699 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1700 }
1701
1702 #[test]
1703 fn test_default_value_fallback_map_all_languages() {
1704 let field = make_field(
1705 "meta",
1706 TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1707 );
1708 assert_eq!(default_value_for_field(&field, "python"), "{}");
1709 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1710 assert_eq!(default_value_for_field(&field, "go"), "make(map[string]interface{})");
1711 assert_eq!(default_value_for_field(&field, "java"), "new java.util.HashMap<>()");
1712 assert_eq!(
1713 default_value_for_field(&field, "csharp"),
1714 "new Dictionary<string, object>()"
1715 );
1716 assert_eq!(default_value_for_field(&field, "php"), "[]");
1717 assert_eq!(default_value_for_field(&field, "r"), "list()");
1718 assert_eq!(
1719 default_value_for_field(&field, "rust"),
1720 "std::collections::HashMap::new()"
1721 );
1722 }
1723
1724 #[test]
1725 fn test_default_value_fallback_json_all_languages() {
1726 let field = make_field("payload", TypeRef::Json);
1727 assert_eq!(default_value_for_field(&field, "python"), "{}");
1728 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1729 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1730 assert_eq!(default_value_for_field(&field, "r"), "list()");
1731 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1732 }
1733
1734 #[test]
1735 fn test_default_value_fallback_named_type() {
1736 let field = make_field("config", TypeRef::Named("MyConfig".to_string()));
1737 assert_eq!(default_value_for_field(&field, "rust"), "MyConfig::default()");
1738 assert_eq!(default_value_for_field(&field, "python"), "None");
1739 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1740 assert_eq!(default_value_for_field(&field, "go"), "nil");
1741 assert_eq!(default_value_for_field(&field, "java"), "null");
1742 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1743 assert_eq!(default_value_for_field(&field, "php"), "null");
1744 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1745 }
1746
1747 #[test]
1748 fn test_default_value_fallback_duration() {
1749 let field = make_field("timeout", TypeRef::Duration);
1751 assert_eq!(default_value_for_field(&field, "python"), "None");
1752 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1753 }
1754
1755 #[test]
1760 fn test_gen_magnus_kwargs_constructor_positional_basic() {
1761 let typ = make_test_type();
1762 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
1763
1764 assert!(output.contains("fn new("), "should have fn new");
1765 assert!(output.contains("Option<u64>"), "timeout should be Option<u64>");
1767 assert!(output.contains("Option<bool>"), "enabled should be Option<bool>");
1768 assert!(output.contains("Option<String>"), "name should be Option<String>");
1769 assert!(output.contains("-> Self {"), "should return Self");
1770 assert!(
1772 output.contains("timeout: timeout.unwrap_or(30),"),
1773 "should apply int default"
1774 );
1775 assert!(
1777 output.contains("enabled: enabled.unwrap_or(true),"),
1778 "should apply bool default"
1779 );
1780 assert!(
1782 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1783 "should apply string default"
1784 );
1785 }
1786
1787 #[test]
1788 fn test_gen_magnus_kwargs_constructor_positional_optional_field() {
1789 let mut typ = make_test_type();
1791 typ.fields.push(FieldDef {
1792 name: "extra".to_string(),
1793 ty: TypeRef::String,
1794 optional: true,
1795 default: None,
1796 doc: String::new(),
1797 sanitized: false,
1798 is_boxed: false,
1799 type_rust_path: None,
1800 cfg: None,
1801 typed_default: None,
1802 core_wrapper: CoreWrapper::None,
1803 vec_inner_core_wrapper: CoreWrapper::None,
1804 newtype_wrapper: None,
1805 serde_rename: None,
1806 serde_flatten: false,
1807 });
1808 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
1809 assert!(output.contains("extra,"), "optional field should be assigned directly");
1811 assert!(!output.contains("extra.unwrap"), "optional field should not use unwrap");
1812 }
1813
1814 #[test]
1815 fn test_gen_magnus_kwargs_constructor_unwrap_or_default() {
1816 let mut typ = make_test_type();
1818 typ.fields.push(FieldDef {
1819 name: "count".to_string(),
1820 ty: TypeRef::Primitive(PrimitiveType::U32),
1821 optional: false,
1822 default: None,
1823 doc: String::new(),
1824 sanitized: false,
1825 is_boxed: false,
1826 type_rust_path: None,
1827 cfg: None,
1828 typed_default: None,
1829 core_wrapper: CoreWrapper::None,
1830 vec_inner_core_wrapper: CoreWrapper::None,
1831 newtype_wrapper: None,
1832 serde_rename: None,
1833 serde_flatten: false,
1834 });
1835 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
1836 assert!(
1837 output.contains("count: count.unwrap_or_default(),"),
1838 "plain primitive with no default should use unwrap_or_default"
1839 );
1840 }
1841
1842 #[test]
1843 fn test_gen_magnus_kwargs_constructor_hash_path_for_many_fields() {
1844 let mut fields: Vec<FieldDef> = (0..16)
1846 .map(|i| FieldDef {
1847 name: format!("field_{i}"),
1848 ty: TypeRef::Primitive(PrimitiveType::U32),
1849 optional: false,
1850 default: None,
1851 doc: String::new(),
1852 sanitized: false,
1853 is_boxed: false,
1854 type_rust_path: None,
1855 cfg: None,
1856 typed_default: None,
1857 core_wrapper: CoreWrapper::None,
1858 vec_inner_core_wrapper: CoreWrapper::None,
1859 newtype_wrapper: None,
1860 serde_rename: None,
1861 serde_flatten: false,
1862 })
1863 .collect();
1864 fields[0].optional = true;
1866
1867 let typ = TypeDef {
1868 name: "BigConfig".to_string(),
1869 rust_path: "crate::BigConfig".to_string(),
1870 original_rust_path: String::new(),
1871 fields,
1872 methods: vec![],
1873 is_opaque: false,
1874 is_clone: true,
1875 is_copy: false,
1876 doc: String::new(),
1877 cfg: None,
1878 is_trait: false,
1879 has_default: true,
1880 has_stripped_cfg_fields: false,
1881 is_return_type: false,
1882 serde_rename_all: None,
1883 has_serde: false,
1884 super_traits: vec![],
1885 };
1886 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1887
1888 assert!(
1889 output.contains("Option<magnus::RHash>"),
1890 "should accept RHash via scan_args"
1891 );
1892 assert!(output.contains("ruby.to_symbol("), "should use symbol lookup");
1893 assert!(
1895 output.contains("field_0: kwargs.get(ruby.to_symbol(\"field_0\")).and_then(|v|"),
1896 "optional field should use and_then"
1897 );
1898 assert!(
1899 output.contains("field_0:").then_some(()).is_some(),
1900 "field_0 should appear in output"
1901 );
1902 }
1903
1904 #[test]
1909 fn test_gen_php_kwargs_constructor_basic() {
1910 let typ = make_test_type();
1911 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1912
1913 assert!(
1914 output.contains("pub fn __construct("),
1915 "should use PHP constructor name"
1916 );
1917 assert!(
1919 output.contains("timeout: Option<u64>"),
1920 "timeout param should be Option<u64>"
1921 );
1922 assert!(
1923 output.contains("enabled: Option<bool>"),
1924 "enabled param should be Option<bool>"
1925 );
1926 assert!(
1927 output.contains("name: Option<String>"),
1928 "name param should be Option<String>"
1929 );
1930 assert!(output.contains("-> Self {"), "should return Self");
1931 assert!(
1932 output.contains("timeout: timeout.unwrap_or(30),"),
1933 "should apply int default for timeout"
1934 );
1935 assert!(
1936 output.contains("enabled: enabled.unwrap_or(true),"),
1937 "should apply bool default for enabled"
1938 );
1939 assert!(
1940 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1941 "should apply string default for name"
1942 );
1943 }
1944
1945 #[test]
1946 fn test_gen_php_kwargs_constructor_optional_field_passthrough() {
1947 let mut typ = make_test_type();
1948 typ.fields.push(FieldDef {
1949 name: "tag".to_string(),
1950 ty: TypeRef::String,
1951 optional: true,
1952 default: None,
1953 doc: String::new(),
1954 sanitized: false,
1955 is_boxed: false,
1956 type_rust_path: None,
1957 cfg: None,
1958 typed_default: None,
1959 core_wrapper: CoreWrapper::None,
1960 vec_inner_core_wrapper: CoreWrapper::None,
1961 newtype_wrapper: None,
1962 serde_rename: None,
1963 serde_flatten: false,
1964 });
1965 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1966 assert!(
1967 output.contains("tag,"),
1968 "optional field should be passed through directly"
1969 );
1970 assert!(!output.contains("tag.unwrap"), "optional field should not call unwrap");
1971 }
1972
1973 #[test]
1974 fn test_gen_php_kwargs_constructor_unwrap_or_default_for_primitive() {
1975 let mut typ = make_test_type();
1976 typ.fields.push(FieldDef {
1977 name: "retries".to_string(),
1978 ty: TypeRef::Primitive(PrimitiveType::U32),
1979 optional: false,
1980 default: None,
1981 doc: String::new(),
1982 sanitized: false,
1983 is_boxed: false,
1984 type_rust_path: None,
1985 cfg: None,
1986 typed_default: None,
1987 core_wrapper: CoreWrapper::None,
1988 vec_inner_core_wrapper: CoreWrapper::None,
1989 newtype_wrapper: None,
1990 serde_rename: None,
1991 serde_flatten: false,
1992 });
1993 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1994 assert!(
1995 output.contains("retries: retries.unwrap_or_default(),"),
1996 "primitive with no default should use unwrap_or_default"
1997 );
1998 }
1999
2000 #[test]
2005 fn test_gen_rustler_kwargs_constructor_basic() {
2006 let typ = make_test_type();
2007 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2008
2009 assert!(
2010 output.contains("pub fn new(opts: std::collections::HashMap<String, rustler::Term>)"),
2011 "should accept HashMap of Terms"
2012 );
2013 assert!(output.contains("Self {"), "should construct Self");
2014 assert!(
2016 output.contains("timeout: opts.get(\"timeout\").and_then(|t| t.decode().ok()).unwrap_or(30),"),
2017 "should apply int default for timeout"
2018 );
2019 assert!(
2021 output.contains("enabled: opts.get(\"enabled\").and_then(|t| t.decode().ok()).unwrap_or(true),"),
2022 "should apply bool default for enabled"
2023 );
2024 }
2025
2026 #[test]
2027 fn test_gen_rustler_kwargs_constructor_optional_field() {
2028 let mut typ = make_test_type();
2029 typ.fields.push(FieldDef {
2030 name: "extra".to_string(),
2031 ty: TypeRef::String,
2032 optional: true,
2033 default: None,
2034 doc: String::new(),
2035 sanitized: false,
2036 is_boxed: false,
2037 type_rust_path: None,
2038 cfg: None,
2039 typed_default: None,
2040 core_wrapper: CoreWrapper::None,
2041 vec_inner_core_wrapper: CoreWrapper::None,
2042 newtype_wrapper: None,
2043 serde_rename: None,
2044 serde_flatten: false,
2045 });
2046 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2047 assert!(
2048 output.contains("extra: opts.get(\"extra\").and_then(|t| t.decode().ok()),"),
2049 "optional field should decode without unwrap"
2050 );
2051 }
2052
2053 #[test]
2054 fn test_gen_rustler_kwargs_constructor_named_type_uses_unwrap_or_default() {
2055 let mut typ = make_test_type();
2056 typ.fields.push(FieldDef {
2057 name: "inner".to_string(),
2058 ty: TypeRef::Named("InnerConfig".to_string()),
2059 optional: false,
2060 default: None,
2061 doc: String::new(),
2062 sanitized: false,
2063 is_boxed: false,
2064 type_rust_path: None,
2065 cfg: None,
2066 typed_default: None,
2067 core_wrapper: CoreWrapper::None,
2068 vec_inner_core_wrapper: CoreWrapper::None,
2069 newtype_wrapper: None,
2070 serde_rename: None,
2071 serde_flatten: false,
2072 });
2073 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2074 assert!(
2075 output.contains("inner: opts.get(\"inner\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2076 "Named type with no default should use unwrap_or_default"
2077 );
2078 }
2079
2080 #[test]
2081 fn test_gen_rustler_kwargs_constructor_string_field_uses_unwrap_or_default() {
2082 let mut typ = make_test_type();
2085 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2087 assert!(
2088 output.contains("name: opts.get(\"name\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2089 "String field with quoted default should use unwrap_or_default"
2090 );
2091 typ.fields.push(FieldDef {
2093 name: "label".to_string(),
2094 ty: TypeRef::String,
2095 optional: false,
2096 default: None,
2097 doc: String::new(),
2098 sanitized: false,
2099 is_boxed: false,
2100 type_rust_path: None,
2101 cfg: None,
2102 typed_default: None,
2103 core_wrapper: CoreWrapper::None,
2104 vec_inner_core_wrapper: CoreWrapper::None,
2105 newtype_wrapper: None,
2106 serde_rename: None,
2107 serde_flatten: false,
2108 });
2109 let output2 = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2110 assert!(
2111 output2.contains("label: opts.get(\"label\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2112 "String field with no default should use unwrap_or_default"
2113 );
2114 }
2115
2116 #[test]
2121 fn test_gen_extendr_kwargs_constructor_basic() {
2122 let typ = make_test_type();
2123 let empty_enums = ahash::AHashSet::new();
2124 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper, &empty_enums);
2125
2126 assert!(output.contains("#[extendr]"), "should have extendr attribute");
2127 assert!(
2128 output.contains("pub fn new_config("),
2129 "function name should be lowercase type name"
2130 );
2131 assert!(
2133 output.contains("timeout: Option<u64>"),
2134 "should accept timeout as Option<u64>: {output}"
2135 );
2136 assert!(
2137 output.contains("enabled: Option<bool>"),
2138 "should accept enabled as Option<bool>: {output}"
2139 );
2140 assert!(
2141 output.contains("name: Option<String>"),
2142 "should accept name as Option<String>: {output}"
2143 );
2144 assert!(output.contains("-> Config {"), "should return Config");
2145 assert!(
2146 output.contains("let mut __out = <Config>::default();"),
2147 "should base on Default impl: {output}"
2148 );
2149 assert!(
2150 output.contains("if let Some(v) = timeout { __out.timeout = v; }"),
2151 "should overlay caller-provided timeout"
2152 );
2153 assert!(
2154 output.contains("if let Some(v) = enabled { __out.enabled = v; }"),
2155 "should overlay caller-provided enabled"
2156 );
2157 assert!(
2158 output.contains("if let Some(v) = name { __out.name = v; }"),
2159 "should overlay caller-provided name"
2160 );
2161 }
2162
2163 #[test]
2164 fn test_gen_extendr_kwargs_constructor_uses_option_for_all_fields() {
2165 let typ = make_test_type();
2169 let empty_enums = ahash::AHashSet::new();
2170 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper, &empty_enums);
2171 assert!(
2172 !output.contains("= TRUE") && !output.contains("= FALSE") && !output.contains("= \"default\""),
2173 "constructor must not use Rust-syntax param defaults: {output}"
2174 );
2175 }
2176
2177 #[test]
2182 fn test_gen_go_functional_options_skips_tuple_fields() {
2183 let mut typ = make_test_type();
2184 typ.fields.push(FieldDef {
2185 name: "_0".to_string(),
2186 ty: TypeRef::Primitive(PrimitiveType::U32),
2187 optional: false,
2188 default: None,
2189 doc: String::new(),
2190 sanitized: false,
2191 is_boxed: false,
2192 type_rust_path: None,
2193 cfg: None,
2194 typed_default: None,
2195 core_wrapper: CoreWrapper::None,
2196 vec_inner_core_wrapper: CoreWrapper::None,
2197 newtype_wrapper: None,
2198 serde_rename: None,
2199 serde_flatten: false,
2200 });
2201 let output = gen_go_functional_options(&typ, &simple_type_mapper);
2202 assert!(
2203 !output.contains("_0"),
2204 "tuple field _0 should be filtered out from Go output"
2205 );
2206 }
2207
2208 #[test]
2213 fn test_gen_magnus_hash_constructor_generic_type_prefix() {
2214 let fields: Vec<FieldDef> = (0..16)
2216 .map(|i| FieldDef {
2217 name: format!("field_{i}"),
2218 ty: if i == 0 {
2219 TypeRef::Vec(Box::new(TypeRef::String))
2220 } else {
2221 TypeRef::Primitive(PrimitiveType::U32)
2222 },
2223 optional: false,
2224 default: None,
2225 doc: String::new(),
2226 sanitized: false,
2227 is_boxed: false,
2228 type_rust_path: None,
2229 cfg: None,
2230 typed_default: None,
2231 core_wrapper: CoreWrapper::None,
2232 vec_inner_core_wrapper: CoreWrapper::None,
2233 newtype_wrapper: None,
2234 serde_rename: None,
2235 serde_flatten: false,
2236 })
2237 .collect();
2238 let typ = TypeDef {
2239 name: "WideConfig".to_string(),
2240 rust_path: "crate::WideConfig".to_string(),
2241 original_rust_path: String::new(),
2242 fields,
2243 methods: vec![],
2244 is_opaque: false,
2245 is_clone: true,
2246 is_copy: false,
2247 doc: String::new(),
2248 cfg: None,
2249 is_trait: false,
2250 has_default: true,
2251 has_stripped_cfg_fields: false,
2252 is_return_type: false,
2253 serde_rename_all: None,
2254 has_serde: false,
2255 super_traits: vec![],
2256 };
2257 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2258 assert!(
2260 output.contains("<Vec<String>>::try_convert"),
2261 "generic types should use UFCS angle-bracket prefix: {output}"
2262 );
2263 }
2264
2265 #[test]
2272 fn test_magnus_hash_constructor_no_double_option_when_ty_is_optional() {
2273 let field = FieldDef {
2277 name: "max_depth".to_string(),
2278 ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
2279 optional: true,
2280 default: None,
2281 doc: String::new(),
2282 sanitized: false,
2283 is_boxed: false,
2284 type_rust_path: None,
2285 cfg: None,
2286 typed_default: None,
2287 core_wrapper: CoreWrapper::None,
2288 vec_inner_core_wrapper: CoreWrapper::None,
2289 newtype_wrapper: None,
2290 serde_rename: None,
2291 serde_flatten: false,
2292 };
2293 let mut fields: Vec<FieldDef> = (0..15)
2295 .map(|i| FieldDef {
2296 name: format!("field_{i}"),
2297 ty: TypeRef::Primitive(PrimitiveType::U32),
2298 optional: false,
2299 default: None,
2300 doc: String::new(),
2301 sanitized: false,
2302 is_boxed: false,
2303 type_rust_path: None,
2304 cfg: None,
2305 typed_default: None,
2306 core_wrapper: CoreWrapper::None,
2307 vec_inner_core_wrapper: CoreWrapper::None,
2308 newtype_wrapper: None,
2309 serde_rename: None,
2310 serde_flatten: false,
2311 })
2312 .collect();
2313 fields.push(field);
2314 let typ = TypeDef {
2315 name: "UpdateConfig".to_string(),
2316 rust_path: "crate::UpdateConfig".to_string(),
2317 original_rust_path: String::new(),
2318 fields,
2319 methods: vec![],
2320 is_opaque: false,
2321 is_clone: true,
2322 is_copy: false,
2323 doc: String::new(),
2324 cfg: None,
2325 is_trait: false,
2326 has_default: true,
2327 has_stripped_cfg_fields: false,
2328 is_return_type: false,
2329 serde_rename_all: None,
2330 has_serde: false,
2331 super_traits: vec![],
2332 };
2333 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2334 assert!(
2337 !output.contains("Option<Option<"),
2338 "hash constructor must not emit double Option: {output}"
2339 );
2340 assert!(
2341 output.contains("i64::try_convert"),
2342 "hash constructor should call inner-type::try_convert, not Option<T>::try_convert: {output}"
2343 );
2344 }
2345
2346 #[test]
2347 fn test_magnus_positional_constructor_no_double_option_when_ty_is_optional() {
2348 let field = FieldDef {
2351 name: "max_depth".to_string(),
2352 ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
2353 optional: true,
2354 default: None,
2355 doc: String::new(),
2356 sanitized: false,
2357 is_boxed: false,
2358 type_rust_path: None,
2359 cfg: None,
2360 typed_default: None,
2361 core_wrapper: CoreWrapper::None,
2362 vec_inner_core_wrapper: CoreWrapper::None,
2363 newtype_wrapper: None,
2364 serde_rename: None,
2365 serde_flatten: false,
2366 };
2367 let typ = TypeDef {
2368 name: "SmallUpdate".to_string(),
2369 rust_path: "crate::SmallUpdate".to_string(),
2370 original_rust_path: String::new(),
2371 fields: vec![field],
2372 methods: vec![],
2373 is_opaque: false,
2374 is_clone: true,
2375 is_copy: false,
2376 doc: String::new(),
2377 cfg: None,
2378 is_trait: false,
2379 has_default: true,
2380 has_stripped_cfg_fields: false,
2381 is_return_type: false,
2382 serde_rename_all: None,
2383 has_serde: false,
2384 super_traits: vec![],
2385 };
2386 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
2387 assert!(
2390 !output.contains("Option<Option<"),
2391 "positional constructor must not emit double Option: {output}"
2392 );
2393 assert!(
2394 output.contains("Option<i64>"),
2395 "positional constructor should emit Option<inner> for optional Optional(T): {output}"
2396 );
2397 }
2398}