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 if matches!(effective_inner_ty, TypeRef::Named(_))
543 && field.typed_default.is_none()
544 {
545 format!(
549 "kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()).ok_or_else(|| magnus::Error::new(unsafe {{ magnus::Ruby::get_unchecked() }}.exception_arg_error(), \"missing required field: {}\"))?,",
550 field.name, type_prefix, field.name
551 )
552 } else {
553 let default_str = if inner_type == "String" {
558 if let Some(DefaultValue::EnumVariant(variant)) = &field.typed_default {
559 use heck::ToSnakeCase;
560 format!("\"{}\".to_string()", variant.to_snake_case())
561 } else {
562 default_value_for_field(field, "rust")
563 }
564 } else {
565 default_value_for_field(field, "rust")
566 };
567 format!(
568 "kwargs.get(ruby.to_symbol(\"{}\")).and_then(|v| {}::try_convert(v).ok()).unwrap_or({}),",
569 field.name, type_prefix, default_str
570 )
571 };
572
573 minijinja::context! {
574 name => field.name.clone(),
575 assignment => assignment,
576 }
577 })
578 .collect();
579
580 crate::template_env::render(
581 "config_gen/magnus_hash_constructor.jinja",
582 minijinja::context! {
583 fields => fields,
584 },
585 )
586}
587
588fn field_is_optional_in_rust(field: &FieldDef) -> bool {
593 field.optional || matches!(&field.ty, TypeRef::Optional(_))
594}
595
596#[allow(dead_code)]
603fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
604 let fields: Vec<_> = typ
605 .fields
606 .iter()
607 .map(|field| {
608 let is_optional = field_is_optional_in_rust(field);
612 let param_type = if is_optional {
613 let effective_inner_ty = match &field.ty {
617 TypeRef::Optional(inner) => inner.as_ref(),
618 ty => ty,
619 };
620 let inner_type = type_mapper(effective_inner_ty);
621 format!("Option<{}>", inner_type)
622 } else {
623 let field_type = type_mapper(&field.ty);
624 format!("Option<{}>", field_type)
625 };
626
627 let assignment = if is_optional {
628 field.name.clone()
630 } else if use_unwrap_or_default(field) {
631 format!("{}.unwrap_or_default()", field.name)
632 } else {
633 let default_str = default_value_for_field(field, "rust");
634 format!("{}.unwrap_or({})", field.name, default_str)
635 };
636
637 minijinja::context! {
638 name => field.name.clone(),
639 param_type => param_type,
640 assignment => assignment,
641 }
642 })
643 .collect();
644
645 crate::template_env::render(
646 "config_gen/magnus_positional_constructor.jinja",
647 minijinja::context! {
648 fields => fields,
649 },
650 )
651}
652
653pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
657 let fields: Vec<_> = typ
658 .fields
659 .iter()
660 .map(|field| {
661 let mapped = type_mapper(&field.ty);
662 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
663
664 let assignment = if is_optional_field {
665 field.name.clone()
667 } else if use_unwrap_or_default(field) {
668 format!("{}.unwrap_or_default()", field.name)
670 } else {
671 let default_str = default_value_for_field(field, "rust");
673 format!("{}.unwrap_or({})", field.name, default_str)
674 };
675
676 minijinja::context! {
677 name => field.name.clone(),
678 ty => mapped,
679 assignment => assignment,
680 }
681 })
682 .collect();
683
684 crate::template_env::render(
685 "config_gen/php_kwargs_constructor.jinja",
686 minijinja::context! {
687 fields => fields,
688 },
689 )
690}
691
692pub fn gen_rustler_kwargs_constructor_with_exclude(
696 typ: &TypeDef,
697 _type_mapper: &dyn Fn(&TypeRef) -> String,
698 exclude_fields: &std::collections::HashSet<String>,
699) -> String {
700 let fields: Vec<_> = typ
702 .fields
703 .iter()
704 .filter(|f| !exclude_fields.contains(&f.name))
705 .map(|field| {
706 let assignment = if field.optional {
707 format!("opts.get(\"{}\").and_then(|t| t.decode().ok()),", field.name)
708 } else if use_unwrap_or_default(field) {
709 format!(
710 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
711 field.name
712 )
713 } else {
714 let default_str = default_value_for_field(field, "rust");
715 let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
716
717 if (is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char))
718 || matches!(&field.ty, TypeRef::Named(_))
719 {
720 format!(
721 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
722 field.name
723 )
724 } else {
725 format!(
726 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
727 field.name, default_str
728 )
729 }
730 };
731
732 minijinja::context! {
733 name => field.name.clone(),
734 assignment => assignment,
735 }
736 })
737 .collect();
738
739 crate::template_env::render(
740 "config_gen/rustler_kwargs_constructor.jinja",
741 minijinja::context! {
742 fields => fields,
743 },
744 )
745}
746
747pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
750 let fields: Vec<_> = typ
752 .fields
753 .iter()
754 .map(|field| {
755 let assignment = if field.optional {
756 format!("opts.get(\"{}\").and_then(|t| t.decode().ok()),", field.name)
757 } else if use_unwrap_or_default(field) {
758 format!(
759 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
760 field.name
761 )
762 } else {
763 let default_str = default_value_for_field(field, "rust");
764 let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
765
766 let unwrap_default = (is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char))
767 || matches!(&field.ty, TypeRef::Named(_));
768 if unwrap_default {
769 format!(
770 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
771 field.name
772 )
773 } else {
774 format!(
775 "opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
776 field.name, default_str
777 )
778 }
779 };
780
781 minijinja::context! {
782 name => field.name.clone(),
783 assignment => assignment,
784 }
785 })
786 .collect();
787
788 crate::template_env::render(
789 "config_gen/rustler_kwargs_constructor.jinja",
790 minijinja::context! {
791 fields => fields,
792 },
793 )
794}
795
796pub fn gen_extendr_kwargs_constructor(
811 typ: &TypeDef,
812 type_mapper: &dyn Fn(&TypeRef) -> String,
813 enum_names: &ahash::AHashSet<String>,
814) -> String {
815 let is_named_enum = |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Named(n) if enum_names.contains(n.as_str())) };
817 let is_named_struct =
818 |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Named(n) if !enum_names.contains(n.as_str())) };
819 let is_optional_named_struct = |ty: &TypeRef| -> bool {
820 if let TypeRef::Optional(inner) = ty {
821 is_named_struct(inner)
822 } else {
823 false
824 }
825 };
826 let ty_is_optional = |ty: &TypeRef| -> bool { matches!(ty, TypeRef::Optional(_)) };
827
828 let emittable_fields: Vec<_> = typ
830 .fields
831 .iter()
832 .filter(|f| f.cfg.is_none() && !is_named_struct(&f.ty) && !is_optional_named_struct(&f.ty))
833 .map(|field| {
834 let param_type = if is_named_enum(&field.ty) {
835 "Option<String>".to_string()
836 } else if ty_is_optional(&field.ty) {
837 type_mapper(&field.ty)
838 } else {
839 format!("Option<{}>", type_mapper(&field.ty))
840 };
841
842 minijinja::context! {
843 name => field.name.clone(),
844 type => param_type,
845 }
846 })
847 .collect();
848
849 let body_assignments: Vec<_> = typ
851 .fields
852 .iter()
853 .filter(|f| f.cfg.is_none() && !is_named_struct(&f.ty) && !is_optional_named_struct(&f.ty))
854 .map(|field| {
855 let code = if is_named_enum(&field.ty) {
856 if field.optional {
857 format!(
858 "if let Some(v) = {} {{ __out.{} = serde_json::from_str(&format!(\"\\\"{{v}}\\\"\")).ok(); }}",
859 field.name, field.name
860 )
861 } else {
862 format!(
863 "if let Some(v) = {} {{ if let Ok(parsed) = serde_json::from_str(&format!(\"\\\"{{v}}\\\"\")) {{ __out.{} = parsed; }} }}",
864 field.name, field.name
865 )
866 }
867 } else if ty_is_optional(&field.ty) || field.optional {
868 format!(
869 "if let Some(v) = {} {{ __out.{} = Some(v); }}",
870 field.name, field.name
871 )
872 } else {
873 format!(
874 "if let Some(v) = {} {{ __out.{} = v; }}",
875 field.name, field.name
876 )
877 };
878
879 minijinja::context! {
880 code => code,
881 }
882 })
883 .collect();
884
885 crate::template_env::render(
886 "config_gen/extendr_kwargs_constructor.jinja",
887 minijinja::context! {
888 type_name => typ.name.clone(),
889 type_name_lower => typ.name.to_lowercase(),
890 params => emittable_fields,
891 body_assignments => body_assignments,
892 },
893 )
894}
895
896#[cfg(test)]
897mod tests {
898 use super::*;
899 use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
900
901 fn make_test_type() -> TypeDef {
902 TypeDef {
903 name: "Config".to_string(),
904 rust_path: "my_crate::Config".to_string(),
905 original_rust_path: String::new(),
906 fields: vec![
907 FieldDef {
908 name: "timeout".to_string(),
909 ty: TypeRef::Primitive(PrimitiveType::U64),
910 optional: false,
911 default: Some("30".to_string()),
912 doc: "Timeout in seconds".to_string(),
913 sanitized: false,
914 is_boxed: false,
915 type_rust_path: None,
916 cfg: None,
917 typed_default: Some(DefaultValue::IntLiteral(30)),
918 core_wrapper: CoreWrapper::None,
919 vec_inner_core_wrapper: CoreWrapper::None,
920 newtype_wrapper: None,
921 serde_rename: None,
922 serde_flatten: false,
923 },
924 FieldDef {
925 name: "enabled".to_string(),
926 ty: TypeRef::Primitive(PrimitiveType::Bool),
927 optional: false,
928 default: None,
929 doc: "Enable feature".to_string(),
930 sanitized: false,
931 is_boxed: false,
932 type_rust_path: None,
933 cfg: None,
934 typed_default: Some(DefaultValue::BoolLiteral(true)),
935 core_wrapper: CoreWrapper::None,
936 vec_inner_core_wrapper: CoreWrapper::None,
937 newtype_wrapper: None,
938 serde_rename: None,
939 serde_flatten: false,
940 },
941 FieldDef {
942 name: "name".to_string(),
943 ty: TypeRef::String,
944 optional: false,
945 default: None,
946 doc: "Config name".to_string(),
947 sanitized: false,
948 is_boxed: false,
949 type_rust_path: None,
950 cfg: None,
951 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
952 core_wrapper: CoreWrapper::None,
953 vec_inner_core_wrapper: CoreWrapper::None,
954 newtype_wrapper: None,
955 serde_rename: None,
956 serde_flatten: false,
957 },
958 ],
959 methods: vec![],
960 is_opaque: false,
961 is_clone: true,
962 is_copy: false,
963 doc: "Configuration type".to_string(),
964 cfg: None,
965 is_trait: false,
966 has_default: true,
967 has_stripped_cfg_fields: false,
968 is_return_type: false,
969 serde_rename_all: None,
970 has_serde: false,
971 super_traits: vec![],
972 }
973 }
974
975 #[test]
976 fn test_default_value_bool_true_python() {
977 let field = FieldDef {
978 name: "enabled".to_string(),
979 ty: TypeRef::Primitive(PrimitiveType::Bool),
980 optional: false,
981 default: None,
982 doc: String::new(),
983 sanitized: false,
984 is_boxed: false,
985 type_rust_path: None,
986 cfg: None,
987 typed_default: Some(DefaultValue::BoolLiteral(true)),
988 core_wrapper: CoreWrapper::None,
989 vec_inner_core_wrapper: CoreWrapper::None,
990 newtype_wrapper: None,
991 serde_rename: None,
992 serde_flatten: false,
993 };
994 assert_eq!(default_value_for_field(&field, "python"), "True");
995 }
996
997 #[test]
998 fn test_default_value_bool_false_go() {
999 let field = FieldDef {
1000 name: "enabled".to_string(),
1001 ty: TypeRef::Primitive(PrimitiveType::Bool),
1002 optional: false,
1003 default: None,
1004 doc: String::new(),
1005 sanitized: false,
1006 is_boxed: false,
1007 type_rust_path: None,
1008 cfg: None,
1009 typed_default: Some(DefaultValue::BoolLiteral(false)),
1010 core_wrapper: CoreWrapper::None,
1011 vec_inner_core_wrapper: CoreWrapper::None,
1012 newtype_wrapper: None,
1013 serde_rename: None,
1014 serde_flatten: false,
1015 };
1016 assert_eq!(default_value_for_field(&field, "go"), "false");
1017 }
1018
1019 #[test]
1020 fn test_default_value_string_literal() {
1021 let field = FieldDef {
1022 name: "name".to_string(),
1023 ty: TypeRef::String,
1024 optional: false,
1025 default: None,
1026 doc: String::new(),
1027 sanitized: false,
1028 is_boxed: false,
1029 type_rust_path: None,
1030 cfg: None,
1031 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1032 core_wrapper: CoreWrapper::None,
1033 vec_inner_core_wrapper: CoreWrapper::None,
1034 newtype_wrapper: None,
1035 serde_rename: None,
1036 serde_flatten: false,
1037 };
1038 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
1039 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
1040 }
1041
1042 #[test]
1043 fn test_default_value_int_literal() {
1044 let field = FieldDef {
1045 name: "timeout".to_string(),
1046 ty: TypeRef::Primitive(PrimitiveType::U64),
1047 optional: false,
1048 default: None,
1049 doc: String::new(),
1050 sanitized: false,
1051 is_boxed: false,
1052 type_rust_path: None,
1053 cfg: None,
1054 typed_default: Some(DefaultValue::IntLiteral(42)),
1055 core_wrapper: CoreWrapper::None,
1056 vec_inner_core_wrapper: CoreWrapper::None,
1057 newtype_wrapper: None,
1058 serde_rename: None,
1059 serde_flatten: false,
1060 };
1061 let result = default_value_for_field(&field, "python");
1062 assert_eq!(result, "42");
1063 }
1064
1065 #[test]
1066 fn test_default_value_none() {
1067 let field = FieldDef {
1068 name: "maybe".to_string(),
1069 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1070 optional: true,
1071 default: None,
1072 doc: String::new(),
1073 sanitized: false,
1074 is_boxed: false,
1075 type_rust_path: None,
1076 cfg: None,
1077 typed_default: Some(DefaultValue::None),
1078 core_wrapper: CoreWrapper::None,
1079 vec_inner_core_wrapper: CoreWrapper::None,
1080 newtype_wrapper: None,
1081 serde_rename: None,
1082 serde_flatten: false,
1083 };
1084 assert_eq!(default_value_for_field(&field, "python"), "None");
1085 assert_eq!(default_value_for_field(&field, "go"), "nil");
1086 assert_eq!(default_value_for_field(&field, "java"), "null");
1087 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1088 }
1089
1090 #[test]
1091 fn test_default_value_fallback_string() {
1092 let field = FieldDef {
1093 name: "name".to_string(),
1094 ty: TypeRef::String,
1095 optional: false,
1096 default: Some("\"custom\"".to_string()),
1097 doc: String::new(),
1098 sanitized: false,
1099 is_boxed: false,
1100 type_rust_path: None,
1101 cfg: None,
1102 typed_default: None,
1103 core_wrapper: CoreWrapper::None,
1104 vec_inner_core_wrapper: CoreWrapper::None,
1105 newtype_wrapper: None,
1106 serde_rename: None,
1107 serde_flatten: false,
1108 };
1109 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1110 }
1111
1112 #[test]
1113 fn test_gen_pyo3_kwargs_constructor() {
1114 let typ = make_test_type();
1115 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1116 TypeRef::Primitive(p) => format!("{:?}", p),
1117 TypeRef::String | TypeRef::Char => "str".to_string(),
1118 _ => "Any".to_string(),
1119 });
1120
1121 assert!(output.contains("#[new]"));
1122 assert!(output.contains("#[pyo3(signature = ("));
1123 assert!(output.contains("timeout=30"));
1124 assert!(output.contains("enabled=True"));
1125 assert!(output.contains("name=\"default\""));
1126 assert!(output.contains("fn new("));
1127 }
1128
1129 #[test]
1130 fn test_gen_napi_defaults_constructor() {
1131 let typ = make_test_type();
1132 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1133 TypeRef::Primitive(p) => format!("{:?}", p),
1134 TypeRef::String | TypeRef::Char => "String".to_string(),
1135 _ => "Value".to_string(),
1136 });
1137
1138 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1139 assert!(output.contains("timeout"));
1140 assert!(output.contains("enabled"));
1141 assert!(output.contains("name"));
1142 }
1143
1144 #[test]
1145 fn test_gen_go_functional_options() {
1146 let typ = make_test_type();
1147 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1148 TypeRef::Primitive(p) => match p {
1149 PrimitiveType::U64 => "uint64".to_string(),
1150 PrimitiveType::Bool => "bool".to_string(),
1151 _ => "interface{}".to_string(),
1152 },
1153 TypeRef::String | TypeRef::Char => "string".to_string(),
1154 _ => "interface{}".to_string(),
1155 });
1156
1157 assert!(output.contains("type Config struct {"));
1158 assert!(output.contains("type ConfigOption func(*Config)"));
1159 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1160 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1161 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1162 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1163 }
1164
1165 #[test]
1166 fn test_gen_java_builder() {
1167 let typ = make_test_type();
1168 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1169 TypeRef::Primitive(p) => match p {
1170 PrimitiveType::U64 => "long".to_string(),
1171 PrimitiveType::Bool => "boolean".to_string(),
1172 _ => "int".to_string(),
1173 },
1174 TypeRef::String | TypeRef::Char => "String".to_string(),
1175 _ => "Object".to_string(),
1176 });
1177
1178 assert!(output.contains("package dev.test;"));
1179 assert!(output.contains("public class ConfigBuilder"));
1180 assert!(output.contains("withTimeout"));
1181 assert!(output.contains("withEnabled"));
1182 assert!(output.contains("withName"));
1183 assert!(output.contains("public Config build()"));
1184 }
1185
1186 #[test]
1187 fn test_gen_csharp_record() {
1188 let typ = make_test_type();
1189 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1190 TypeRef::Primitive(p) => match p {
1191 PrimitiveType::U64 => "ulong".to_string(),
1192 PrimitiveType::Bool => "bool".to_string(),
1193 _ => "int".to_string(),
1194 },
1195 TypeRef::String | TypeRef::Char => "string".to_string(),
1196 _ => "object".to_string(),
1197 });
1198
1199 assert!(output.contains("namespace MyNamespace;"));
1200 assert!(output.contains("public record Config"));
1201 assert!(output.contains("public ulong Timeout"));
1202 assert!(output.contains("public bool Enabled"));
1203 assert!(output.contains("public string Name"));
1204 assert!(output.contains("init;"));
1205 }
1206
1207 #[test]
1208 fn test_default_value_float_literal() {
1209 let field = FieldDef {
1210 name: "ratio".to_string(),
1211 ty: TypeRef::Primitive(PrimitiveType::F64),
1212 optional: false,
1213 default: None,
1214 doc: String::new(),
1215 sanitized: false,
1216 is_boxed: false,
1217 type_rust_path: None,
1218 cfg: None,
1219 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1220 core_wrapper: CoreWrapper::None,
1221 vec_inner_core_wrapper: CoreWrapper::None,
1222 newtype_wrapper: None,
1223 serde_rename: None,
1224 serde_flatten: false,
1225 };
1226 let result = default_value_for_field(&field, "python");
1227 assert!(result.contains("1.5"));
1228 }
1229
1230 #[test]
1231 fn test_default_value_no_typed_no_default() {
1232 let field = FieldDef {
1233 name: "count".to_string(),
1234 ty: TypeRef::Primitive(PrimitiveType::U32),
1235 optional: false,
1236 default: None,
1237 doc: String::new(),
1238 sanitized: false,
1239 is_boxed: false,
1240 type_rust_path: None,
1241 cfg: None,
1242 typed_default: None,
1243 core_wrapper: CoreWrapper::None,
1244 vec_inner_core_wrapper: CoreWrapper::None,
1245 newtype_wrapper: None,
1246 serde_rename: None,
1247 serde_flatten: false,
1248 };
1249 assert_eq!(default_value_for_field(&field, "python"), "0");
1251 assert_eq!(default_value_for_field(&field, "go"), "0");
1252 }
1253
1254 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1255 FieldDef {
1256 name: name.to_string(),
1257 ty,
1258 optional: false,
1259 default: None,
1260 doc: String::new(),
1261 sanitized: false,
1262 is_boxed: false,
1263 type_rust_path: None,
1264 cfg: None,
1265 typed_default: None,
1266 core_wrapper: CoreWrapper::None,
1267 vec_inner_core_wrapper: CoreWrapper::None,
1268 newtype_wrapper: None,
1269 serde_rename: None,
1270 serde_flatten: false,
1271 }
1272 }
1273
1274 fn simple_type_mapper(tr: &TypeRef) -> String {
1275 match tr {
1276 TypeRef::Primitive(p) => match p {
1277 PrimitiveType::U64 => "u64".to_string(),
1278 PrimitiveType::Bool => "bool".to_string(),
1279 PrimitiveType::U32 => "u32".to_string(),
1280 _ => "i64".to_string(),
1281 },
1282 TypeRef::String | TypeRef::Char => "String".to_string(),
1283 TypeRef::Optional(inner) => format!("Option<{}>", simple_type_mapper(inner)),
1284 TypeRef::Vec(inner) => format!("Vec<{}>", simple_type_mapper(inner)),
1285 TypeRef::Named(n) => n.clone(),
1286 _ => "Value".to_string(),
1287 }
1288 }
1289
1290 #[test]
1295 fn test_default_value_bool_literal_ruby() {
1296 let field = FieldDef {
1297 name: "flag".to_string(),
1298 ty: TypeRef::Primitive(PrimitiveType::Bool),
1299 optional: false,
1300 default: None,
1301 doc: String::new(),
1302 sanitized: false,
1303 is_boxed: false,
1304 type_rust_path: None,
1305 cfg: None,
1306 typed_default: Some(DefaultValue::BoolLiteral(true)),
1307 core_wrapper: CoreWrapper::None,
1308 vec_inner_core_wrapper: CoreWrapper::None,
1309 newtype_wrapper: None,
1310 serde_rename: None,
1311 serde_flatten: false,
1312 };
1313 assert_eq!(default_value_for_field(&field, "ruby"), "true");
1314 assert_eq!(default_value_for_field(&field, "php"), "true");
1315 assert_eq!(default_value_for_field(&field, "csharp"), "true");
1316 assert_eq!(default_value_for_field(&field, "java"), "true");
1317 assert_eq!(default_value_for_field(&field, "rust"), "true");
1318 }
1319
1320 #[test]
1321 fn test_default_value_bool_literal_r() {
1322 let field = FieldDef {
1323 name: "flag".to_string(),
1324 ty: TypeRef::Primitive(PrimitiveType::Bool),
1325 optional: false,
1326 default: None,
1327 doc: String::new(),
1328 sanitized: false,
1329 is_boxed: false,
1330 type_rust_path: None,
1331 cfg: None,
1332 typed_default: Some(DefaultValue::BoolLiteral(false)),
1333 core_wrapper: CoreWrapper::None,
1334 vec_inner_core_wrapper: CoreWrapper::None,
1335 newtype_wrapper: None,
1336 serde_rename: None,
1337 serde_flatten: false,
1338 };
1339 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1340 }
1341
1342 #[test]
1343 fn test_default_value_string_literal_rust() {
1344 let field = FieldDef {
1345 name: "label".to_string(),
1346 ty: TypeRef::String,
1347 optional: false,
1348 default: None,
1349 doc: String::new(),
1350 sanitized: false,
1351 is_boxed: false,
1352 type_rust_path: None,
1353 cfg: None,
1354 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1355 core_wrapper: CoreWrapper::None,
1356 vec_inner_core_wrapper: CoreWrapper::None,
1357 newtype_wrapper: None,
1358 serde_rename: None,
1359 serde_flatten: false,
1360 };
1361 assert_eq!(default_value_for_field(&field, "rust"), "\"hello\".to_string()");
1362 }
1363
1364 #[test]
1365 fn test_default_value_string_literal_escapes_quotes() {
1366 let field = FieldDef {
1367 name: "label".to_string(),
1368 ty: TypeRef::String,
1369 optional: false,
1370 default: None,
1371 doc: String::new(),
1372 sanitized: false,
1373 is_boxed: false,
1374 type_rust_path: None,
1375 cfg: None,
1376 typed_default: Some(DefaultValue::StringLiteral("say \"hi\"".to_string())),
1377 core_wrapper: CoreWrapper::None,
1378 vec_inner_core_wrapper: CoreWrapper::None,
1379 newtype_wrapper: None,
1380 serde_rename: None,
1381 serde_flatten: false,
1382 };
1383 assert_eq!(default_value_for_field(&field, "python"), "\"say \\\"hi\\\"\"");
1384 }
1385
1386 #[test]
1387 fn test_default_value_float_literal_whole_number() {
1388 let field = FieldDef {
1390 name: "scale".to_string(),
1391 ty: TypeRef::Primitive(PrimitiveType::F32),
1392 optional: false,
1393 default: None,
1394 doc: String::new(),
1395 sanitized: false,
1396 is_boxed: false,
1397 type_rust_path: None,
1398 cfg: None,
1399 typed_default: Some(DefaultValue::FloatLiteral(2.0)),
1400 core_wrapper: CoreWrapper::None,
1401 vec_inner_core_wrapper: CoreWrapper::None,
1402 newtype_wrapper: None,
1403 serde_rename: None,
1404 serde_flatten: false,
1405 };
1406 let result = default_value_for_field(&field, "python");
1407 assert!(result.contains('.'), "whole-number float should contain '.': {result}");
1408 }
1409
1410 #[test]
1411 fn test_default_value_enum_variant_per_language() {
1412 let field = FieldDef {
1413 name: "format".to_string(),
1414 ty: TypeRef::Named("OutputFormat".to_string()),
1415 optional: false,
1416 default: None,
1417 doc: String::new(),
1418 sanitized: false,
1419 is_boxed: false,
1420 type_rust_path: None,
1421 cfg: None,
1422 typed_default: Some(DefaultValue::EnumVariant("JsonOutput".to_string())),
1423 core_wrapper: CoreWrapper::None,
1424 vec_inner_core_wrapper: CoreWrapper::None,
1425 newtype_wrapper: None,
1426 serde_rename: None,
1427 serde_flatten: false,
1428 };
1429 assert_eq!(default_value_for_field(&field, "python"), "OutputFormat.JSON_OUTPUT");
1430 assert_eq!(default_value_for_field(&field, "ruby"), "OutputFormat::JsonOutput");
1431 assert_eq!(default_value_for_field(&field, "go"), "OutputFormatJsonOutput");
1432 assert_eq!(default_value_for_field(&field, "java"), "OutputFormat.JSON_OUTPUT");
1433 assert_eq!(default_value_for_field(&field, "csharp"), "OutputFormat.JsonOutput");
1434 assert_eq!(default_value_for_field(&field, "php"), "OutputFormat::JsonOutput");
1435 assert_eq!(default_value_for_field(&field, "r"), "OutputFormat$JsonOutput");
1436 assert_eq!(default_value_for_field(&field, "rust"), "OutputFormat::JsonOutput");
1437 }
1438
1439 #[test]
1440 fn test_default_value_empty_vec_per_language() {
1441 let field = FieldDef {
1442 name: "items".to_string(),
1443 ty: TypeRef::Vec(Box::new(TypeRef::String)),
1444 optional: false,
1445 default: None,
1446 doc: String::new(),
1447 sanitized: false,
1448 is_boxed: false,
1449 type_rust_path: None,
1450 cfg: None,
1451 typed_default: Some(DefaultValue::Empty),
1452 core_wrapper: CoreWrapper::None,
1453 vec_inner_core_wrapper: CoreWrapper::None,
1454 newtype_wrapper: None,
1455 serde_rename: None,
1456 serde_flatten: false,
1457 };
1458 assert_eq!(default_value_for_field(&field, "python"), "[]");
1459 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1460 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1461 assert_eq!(default_value_for_field(&field, "go"), "nil");
1462 assert_eq!(default_value_for_field(&field, "java"), "List.of()");
1463 assert_eq!(default_value_for_field(&field, "php"), "[]");
1464 assert_eq!(default_value_for_field(&field, "r"), "c()");
1465 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1466 }
1467
1468 #[test]
1469 fn test_default_value_empty_map_per_language() {
1470 let field = FieldDef {
1471 name: "meta".to_string(),
1472 ty: TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1473 optional: false,
1474 default: None,
1475 doc: String::new(),
1476 sanitized: false,
1477 is_boxed: false,
1478 type_rust_path: None,
1479 cfg: None,
1480 typed_default: Some(DefaultValue::Empty),
1481 core_wrapper: CoreWrapper::None,
1482 vec_inner_core_wrapper: CoreWrapper::None,
1483 newtype_wrapper: None,
1484 serde_rename: None,
1485 serde_flatten: false,
1486 };
1487 assert_eq!(default_value_for_field(&field, "python"), "{}");
1488 assert_eq!(default_value_for_field(&field, "go"), "nil");
1489 assert_eq!(default_value_for_field(&field, "java"), "Map.of()");
1490 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1491 }
1492
1493 #[test]
1494 fn test_default_value_empty_bool_primitive() {
1495 let field = FieldDef {
1496 name: "flag".to_string(),
1497 ty: TypeRef::Primitive(PrimitiveType::Bool),
1498 optional: false,
1499 default: None,
1500 doc: String::new(),
1501 sanitized: false,
1502 is_boxed: false,
1503 type_rust_path: None,
1504 cfg: None,
1505 typed_default: Some(DefaultValue::Empty),
1506 core_wrapper: CoreWrapper::None,
1507 vec_inner_core_wrapper: CoreWrapper::None,
1508 newtype_wrapper: None,
1509 serde_rename: None,
1510 serde_flatten: false,
1511 };
1512 assert_eq!(default_value_for_field(&field, "python"), "False");
1513 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1514 assert_eq!(default_value_for_field(&field, "go"), "false");
1515 }
1516
1517 #[test]
1518 fn test_default_value_empty_float_primitive() {
1519 let field = FieldDef {
1520 name: "ratio".to_string(),
1521 ty: TypeRef::Primitive(PrimitiveType::F64),
1522 optional: false,
1523 default: None,
1524 doc: String::new(),
1525 sanitized: false,
1526 is_boxed: false,
1527 type_rust_path: None,
1528 cfg: None,
1529 typed_default: Some(DefaultValue::Empty),
1530 core_wrapper: CoreWrapper::None,
1531 vec_inner_core_wrapper: CoreWrapper::None,
1532 newtype_wrapper: None,
1533 serde_rename: None,
1534 serde_flatten: false,
1535 };
1536 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1537 }
1538
1539 #[test]
1540 fn test_default_value_empty_string_type() {
1541 let field = FieldDef {
1542 name: "label".to_string(),
1543 ty: TypeRef::String,
1544 optional: false,
1545 default: None,
1546 doc: String::new(),
1547 sanitized: false,
1548 is_boxed: false,
1549 type_rust_path: None,
1550 cfg: None,
1551 typed_default: Some(DefaultValue::Empty),
1552 core_wrapper: CoreWrapper::None,
1553 vec_inner_core_wrapper: CoreWrapper::None,
1554 newtype_wrapper: None,
1555 serde_rename: None,
1556 serde_flatten: false,
1557 };
1558 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1559 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1560 }
1561
1562 #[test]
1563 fn test_default_value_empty_bytes_type() {
1564 let field = FieldDef {
1565 name: "data".to_string(),
1566 ty: TypeRef::Bytes,
1567 optional: false,
1568 default: None,
1569 doc: String::new(),
1570 sanitized: false,
1571 is_boxed: false,
1572 type_rust_path: None,
1573 cfg: None,
1574 typed_default: Some(DefaultValue::Empty),
1575 core_wrapper: CoreWrapper::None,
1576 vec_inner_core_wrapper: CoreWrapper::None,
1577 newtype_wrapper: None,
1578 serde_rename: None,
1579 serde_flatten: false,
1580 };
1581 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1582 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1583 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1584 }
1585
1586 #[test]
1587 fn test_default_value_empty_json_type() {
1588 let field = FieldDef {
1589 name: "payload".to_string(),
1590 ty: TypeRef::Json,
1591 optional: false,
1592 default: None,
1593 doc: String::new(),
1594 sanitized: false,
1595 is_boxed: false,
1596 type_rust_path: None,
1597 cfg: None,
1598 typed_default: Some(DefaultValue::Empty),
1599 core_wrapper: CoreWrapper::None,
1600 vec_inner_core_wrapper: CoreWrapper::None,
1601 newtype_wrapper: None,
1602 serde_rename: None,
1603 serde_flatten: false,
1604 };
1605 assert_eq!(default_value_for_field(&field, "python"), "{}");
1606 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1607 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1608 assert_eq!(default_value_for_field(&field, "r"), "list()");
1609 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1610 }
1611
1612 #[test]
1613 fn test_default_value_none_ruby_php_r() {
1614 let field = FieldDef {
1615 name: "maybe".to_string(),
1616 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1617 optional: true,
1618 default: None,
1619 doc: String::new(),
1620 sanitized: false,
1621 is_boxed: false,
1622 type_rust_path: None,
1623 cfg: None,
1624 typed_default: Some(DefaultValue::None),
1625 core_wrapper: CoreWrapper::None,
1626 vec_inner_core_wrapper: CoreWrapper::None,
1627 newtype_wrapper: None,
1628 serde_rename: None,
1629 serde_flatten: false,
1630 };
1631 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1632 assert_eq!(default_value_for_field(&field, "php"), "null");
1633 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1634 assert_eq!(default_value_for_field(&field, "rust"), "None");
1635 }
1636
1637 #[test]
1642 fn test_default_value_fallback_bool_all_languages() {
1643 let field = make_field("flag", TypeRef::Primitive(PrimitiveType::Bool));
1644 assert_eq!(default_value_for_field(&field, "python"), "False");
1645 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1646 assert_eq!(default_value_for_field(&field, "csharp"), "false");
1647 assert_eq!(default_value_for_field(&field, "java"), "false");
1648 assert_eq!(default_value_for_field(&field, "php"), "false");
1649 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1650 assert_eq!(default_value_for_field(&field, "rust"), "false");
1651 }
1652
1653 #[test]
1654 fn test_default_value_fallback_float() {
1655 let field = make_field("ratio", TypeRef::Primitive(PrimitiveType::F64));
1656 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1657 assert_eq!(default_value_for_field(&field, "rust"), "0.0");
1658 }
1659
1660 #[test]
1661 fn test_default_value_fallback_string_all_languages() {
1662 let field = make_field("name", TypeRef::String);
1663 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1664 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1665 assert_eq!(default_value_for_field(&field, "go"), "\"\"");
1666 assert_eq!(default_value_for_field(&field, "java"), "\"\"");
1667 assert_eq!(default_value_for_field(&field, "csharp"), "\"\"");
1668 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1669 assert_eq!(default_value_for_field(&field, "r"), "\"\"");
1670 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1671 }
1672
1673 #[test]
1674 fn test_default_value_fallback_bytes_all_languages() {
1675 let field = make_field("data", TypeRef::Bytes);
1676 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1677 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1678 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1679 assert_eq!(default_value_for_field(&field, "java"), "new byte[]{}");
1680 assert_eq!(default_value_for_field(&field, "csharp"), "new byte[]{}");
1681 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1682 assert_eq!(default_value_for_field(&field, "r"), "raw()");
1683 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1684 }
1685
1686 #[test]
1687 fn test_default_value_fallback_optional() {
1688 let field = make_field("maybe", TypeRef::Optional(Box::new(TypeRef::String)));
1689 assert_eq!(default_value_for_field(&field, "python"), "None");
1690 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1691 assert_eq!(default_value_for_field(&field, "go"), "nil");
1692 assert_eq!(default_value_for_field(&field, "java"), "null");
1693 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1694 assert_eq!(default_value_for_field(&field, "php"), "null");
1695 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1696 assert_eq!(default_value_for_field(&field, "rust"), "None");
1697 }
1698
1699 #[test]
1700 fn test_default_value_fallback_vec_all_languages() {
1701 let field = make_field("items", TypeRef::Vec(Box::new(TypeRef::String)));
1702 assert_eq!(default_value_for_field(&field, "python"), "[]");
1703 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1704 assert_eq!(default_value_for_field(&field, "go"), "[]interface{}{}");
1705 assert_eq!(default_value_for_field(&field, "java"), "new java.util.ArrayList<>()");
1706 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1707 assert_eq!(default_value_for_field(&field, "php"), "[]");
1708 assert_eq!(default_value_for_field(&field, "r"), "c()");
1709 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1710 }
1711
1712 #[test]
1713 fn test_default_value_fallback_map_all_languages() {
1714 let field = make_field(
1715 "meta",
1716 TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1717 );
1718 assert_eq!(default_value_for_field(&field, "python"), "{}");
1719 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1720 assert_eq!(default_value_for_field(&field, "go"), "make(map[string]interface{})");
1721 assert_eq!(default_value_for_field(&field, "java"), "new java.util.HashMap<>()");
1722 assert_eq!(
1723 default_value_for_field(&field, "csharp"),
1724 "new Dictionary<string, object>()"
1725 );
1726 assert_eq!(default_value_for_field(&field, "php"), "[]");
1727 assert_eq!(default_value_for_field(&field, "r"), "list()");
1728 assert_eq!(
1729 default_value_for_field(&field, "rust"),
1730 "std::collections::HashMap::new()"
1731 );
1732 }
1733
1734 #[test]
1735 fn test_default_value_fallback_json_all_languages() {
1736 let field = make_field("payload", TypeRef::Json);
1737 assert_eq!(default_value_for_field(&field, "python"), "{}");
1738 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1739 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1740 assert_eq!(default_value_for_field(&field, "r"), "list()");
1741 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1742 }
1743
1744 #[test]
1745 fn test_default_value_fallback_named_type() {
1746 let field = make_field("config", TypeRef::Named("MyConfig".to_string()));
1747 assert_eq!(default_value_for_field(&field, "rust"), "MyConfig::default()");
1748 assert_eq!(default_value_for_field(&field, "python"), "None");
1749 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1750 assert_eq!(default_value_for_field(&field, "go"), "nil");
1751 assert_eq!(default_value_for_field(&field, "java"), "null");
1752 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1753 assert_eq!(default_value_for_field(&field, "php"), "null");
1754 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1755 }
1756
1757 #[test]
1758 fn test_default_value_fallback_duration() {
1759 let field = make_field("timeout", TypeRef::Duration);
1761 assert_eq!(default_value_for_field(&field, "python"), "None");
1762 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1763 }
1764
1765 #[test]
1770 fn test_gen_magnus_kwargs_constructor_positional_basic() {
1771 let typ = make_test_type();
1772 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
1773
1774 assert!(output.contains("fn new("), "should have fn new");
1775 assert!(output.contains("Option<u64>"), "timeout should be Option<u64>");
1777 assert!(output.contains("Option<bool>"), "enabled should be Option<bool>");
1778 assert!(output.contains("Option<String>"), "name should be Option<String>");
1779 assert!(output.contains("-> Self {"), "should return Self");
1780 assert!(
1782 output.contains("timeout: timeout.unwrap_or(30),"),
1783 "should apply int default"
1784 );
1785 assert!(
1787 output.contains("enabled: enabled.unwrap_or(true),"),
1788 "should apply bool default"
1789 );
1790 assert!(
1792 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1793 "should apply string default"
1794 );
1795 }
1796
1797 #[test]
1798 fn test_gen_magnus_kwargs_constructor_positional_optional_field() {
1799 let mut typ = make_test_type();
1801 typ.fields.push(FieldDef {
1802 name: "extra".to_string(),
1803 ty: TypeRef::String,
1804 optional: true,
1805 default: None,
1806 doc: String::new(),
1807 sanitized: false,
1808 is_boxed: false,
1809 type_rust_path: None,
1810 cfg: None,
1811 typed_default: None,
1812 core_wrapper: CoreWrapper::None,
1813 vec_inner_core_wrapper: CoreWrapper::None,
1814 newtype_wrapper: None,
1815 serde_rename: None,
1816 serde_flatten: false,
1817 });
1818 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
1819 assert!(output.contains("extra,"), "optional field should be assigned directly");
1821 assert!(!output.contains("extra.unwrap"), "optional field should not use unwrap");
1822 }
1823
1824 #[test]
1825 fn test_gen_magnus_kwargs_constructor_unwrap_or_default() {
1826 let mut typ = make_test_type();
1828 typ.fields.push(FieldDef {
1829 name: "count".to_string(),
1830 ty: TypeRef::Primitive(PrimitiveType::U32),
1831 optional: false,
1832 default: None,
1833 doc: String::new(),
1834 sanitized: false,
1835 is_boxed: false,
1836 type_rust_path: None,
1837 cfg: None,
1838 typed_default: None,
1839 core_wrapper: CoreWrapper::None,
1840 vec_inner_core_wrapper: CoreWrapper::None,
1841 newtype_wrapper: None,
1842 serde_rename: None,
1843 serde_flatten: false,
1844 });
1845 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
1846 assert!(
1847 output.contains("count: count.unwrap_or_default(),"),
1848 "plain primitive with no default should use unwrap_or_default"
1849 );
1850 }
1851
1852 #[test]
1853 fn test_gen_magnus_kwargs_constructor_hash_path_for_many_fields() {
1854 let mut fields: Vec<FieldDef> = (0..16)
1856 .map(|i| FieldDef {
1857 name: format!("field_{i}"),
1858 ty: TypeRef::Primitive(PrimitiveType::U32),
1859 optional: false,
1860 default: None,
1861 doc: String::new(),
1862 sanitized: false,
1863 is_boxed: false,
1864 type_rust_path: None,
1865 cfg: None,
1866 typed_default: None,
1867 core_wrapper: CoreWrapper::None,
1868 vec_inner_core_wrapper: CoreWrapper::None,
1869 newtype_wrapper: None,
1870 serde_rename: None,
1871 serde_flatten: false,
1872 })
1873 .collect();
1874 fields[0].optional = true;
1876
1877 let typ = TypeDef {
1878 name: "BigConfig".to_string(),
1879 rust_path: "crate::BigConfig".to_string(),
1880 original_rust_path: String::new(),
1881 fields,
1882 methods: vec![],
1883 is_opaque: false,
1884 is_clone: true,
1885 is_copy: false,
1886 doc: String::new(),
1887 cfg: None,
1888 is_trait: false,
1889 has_default: true,
1890 has_stripped_cfg_fields: false,
1891 is_return_type: false,
1892 serde_rename_all: None,
1893 has_serde: false,
1894 super_traits: vec![],
1895 };
1896 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1897
1898 assert!(
1899 output.contains("Option<magnus::RHash>"),
1900 "should accept RHash via scan_args"
1901 );
1902 assert!(output.contains("ruby.to_symbol("), "should use symbol lookup");
1903 assert!(
1905 output.contains("field_0: kwargs.get(ruby.to_symbol(\"field_0\")).and_then(|v|"),
1906 "optional field should use and_then"
1907 );
1908 assert!(
1909 output.contains("field_0:").then_some(()).is_some(),
1910 "field_0 should appear in output"
1911 );
1912 }
1913
1914 #[test]
1919 fn test_gen_php_kwargs_constructor_basic() {
1920 let typ = make_test_type();
1921 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1922
1923 assert!(
1924 output.contains("pub fn __construct("),
1925 "should use PHP constructor name"
1926 );
1927 assert!(
1929 output.contains("timeout: Option<u64>"),
1930 "timeout param should be Option<u64>"
1931 );
1932 assert!(
1933 output.contains("enabled: Option<bool>"),
1934 "enabled param should be Option<bool>"
1935 );
1936 assert!(
1937 output.contains("name: Option<String>"),
1938 "name param should be Option<String>"
1939 );
1940 assert!(output.contains("-> Self {"), "should return Self");
1941 assert!(
1942 output.contains("timeout: timeout.unwrap_or(30),"),
1943 "should apply int default for timeout"
1944 );
1945 assert!(
1946 output.contains("enabled: enabled.unwrap_or(true),"),
1947 "should apply bool default for enabled"
1948 );
1949 assert!(
1950 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1951 "should apply string default for name"
1952 );
1953 }
1954
1955 #[test]
1956 fn test_gen_php_kwargs_constructor_optional_field_passthrough() {
1957 let mut typ = make_test_type();
1958 typ.fields.push(FieldDef {
1959 name: "tag".to_string(),
1960 ty: TypeRef::String,
1961 optional: true,
1962 default: None,
1963 doc: String::new(),
1964 sanitized: false,
1965 is_boxed: false,
1966 type_rust_path: None,
1967 cfg: None,
1968 typed_default: None,
1969 core_wrapper: CoreWrapper::None,
1970 vec_inner_core_wrapper: CoreWrapper::None,
1971 newtype_wrapper: None,
1972 serde_rename: None,
1973 serde_flatten: false,
1974 });
1975 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1976 assert!(
1977 output.contains("tag,"),
1978 "optional field should be passed through directly"
1979 );
1980 assert!(!output.contains("tag.unwrap"), "optional field should not call unwrap");
1981 }
1982
1983 #[test]
1984 fn test_gen_php_kwargs_constructor_unwrap_or_default_for_primitive() {
1985 let mut typ = make_test_type();
1986 typ.fields.push(FieldDef {
1987 name: "retries".to_string(),
1988 ty: TypeRef::Primitive(PrimitiveType::U32),
1989 optional: false,
1990 default: None,
1991 doc: String::new(),
1992 sanitized: false,
1993 is_boxed: false,
1994 type_rust_path: None,
1995 cfg: None,
1996 typed_default: None,
1997 core_wrapper: CoreWrapper::None,
1998 vec_inner_core_wrapper: CoreWrapper::None,
1999 newtype_wrapper: None,
2000 serde_rename: None,
2001 serde_flatten: false,
2002 });
2003 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
2004 assert!(
2005 output.contains("retries: retries.unwrap_or_default(),"),
2006 "primitive with no default should use unwrap_or_default"
2007 );
2008 }
2009
2010 #[test]
2015 fn test_gen_rustler_kwargs_constructor_basic() {
2016 let typ = make_test_type();
2017 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2018
2019 assert!(
2020 output.contains("pub fn new(opts: std::collections::HashMap<String, rustler::Term>)"),
2021 "should accept HashMap of Terms"
2022 );
2023 assert!(output.contains("Self {"), "should construct Self");
2024 assert!(
2026 output.contains("timeout: opts.get(\"timeout\").and_then(|t| t.decode().ok()).unwrap_or(30),"),
2027 "should apply int default for timeout"
2028 );
2029 assert!(
2031 output.contains("enabled: opts.get(\"enabled\").and_then(|t| t.decode().ok()).unwrap_or(true),"),
2032 "should apply bool default for enabled"
2033 );
2034 }
2035
2036 #[test]
2037 fn test_gen_rustler_kwargs_constructor_optional_field() {
2038 let mut typ = make_test_type();
2039 typ.fields.push(FieldDef {
2040 name: "extra".to_string(),
2041 ty: TypeRef::String,
2042 optional: true,
2043 default: None,
2044 doc: String::new(),
2045 sanitized: false,
2046 is_boxed: false,
2047 type_rust_path: None,
2048 cfg: None,
2049 typed_default: None,
2050 core_wrapper: CoreWrapper::None,
2051 vec_inner_core_wrapper: CoreWrapper::None,
2052 newtype_wrapper: None,
2053 serde_rename: None,
2054 serde_flatten: false,
2055 });
2056 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2057 assert!(
2058 output.contains("extra: opts.get(\"extra\").and_then(|t| t.decode().ok()),"),
2059 "optional field should decode without unwrap"
2060 );
2061 }
2062
2063 #[test]
2064 fn test_gen_rustler_kwargs_constructor_named_type_uses_unwrap_or_default() {
2065 let mut typ = make_test_type();
2066 typ.fields.push(FieldDef {
2067 name: "inner".to_string(),
2068 ty: TypeRef::Named("InnerConfig".to_string()),
2069 optional: false,
2070 default: None,
2071 doc: String::new(),
2072 sanitized: false,
2073 is_boxed: false,
2074 type_rust_path: None,
2075 cfg: None,
2076 typed_default: None,
2077 core_wrapper: CoreWrapper::None,
2078 vec_inner_core_wrapper: CoreWrapper::None,
2079 newtype_wrapper: None,
2080 serde_rename: None,
2081 serde_flatten: false,
2082 });
2083 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2084 assert!(
2085 output.contains("inner: opts.get(\"inner\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2086 "Named type with no default should use unwrap_or_default"
2087 );
2088 }
2089
2090 #[test]
2091 fn test_gen_rustler_kwargs_constructor_string_field_uses_unwrap_or_default() {
2092 let mut typ = make_test_type();
2095 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2097 assert!(
2098 output.contains("name: opts.get(\"name\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2099 "String field with quoted default should use unwrap_or_default"
2100 );
2101 typ.fields.push(FieldDef {
2103 name: "label".to_string(),
2104 ty: TypeRef::String,
2105 optional: false,
2106 default: None,
2107 doc: String::new(),
2108 sanitized: false,
2109 is_boxed: false,
2110 type_rust_path: None,
2111 cfg: None,
2112 typed_default: None,
2113 core_wrapper: CoreWrapper::None,
2114 vec_inner_core_wrapper: CoreWrapper::None,
2115 newtype_wrapper: None,
2116 serde_rename: None,
2117 serde_flatten: false,
2118 });
2119 let output2 = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2120 assert!(
2121 output2.contains("label: opts.get(\"label\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2122 "String field with no default should use unwrap_or_default"
2123 );
2124 }
2125
2126 #[test]
2131 fn test_gen_extendr_kwargs_constructor_basic() {
2132 let typ = make_test_type();
2133 let empty_enums = ahash::AHashSet::new();
2134 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper, &empty_enums);
2135
2136 assert!(output.contains("#[extendr]"), "should have extendr attribute");
2137 assert!(
2138 output.contains("pub fn new_config("),
2139 "function name should be lowercase type name"
2140 );
2141 assert!(
2143 output.contains("timeout: Option<u64>"),
2144 "should accept timeout as Option<u64>: {output}"
2145 );
2146 assert!(
2147 output.contains("enabled: Option<bool>"),
2148 "should accept enabled as Option<bool>: {output}"
2149 );
2150 assert!(
2151 output.contains("name: Option<String>"),
2152 "should accept name as Option<String>: {output}"
2153 );
2154 assert!(output.contains("-> Config {"), "should return Config");
2155 assert!(
2156 output.contains("let mut __out = <Config>::default();"),
2157 "should base on Default impl: {output}"
2158 );
2159 assert!(
2160 output.contains("if let Some(v) = timeout { __out.timeout = v; }"),
2161 "should overlay caller-provided timeout"
2162 );
2163 assert!(
2164 output.contains("if let Some(v) = enabled { __out.enabled = v; }"),
2165 "should overlay caller-provided enabled"
2166 );
2167 assert!(
2168 output.contains("if let Some(v) = name { __out.name = v; }"),
2169 "should overlay caller-provided name"
2170 );
2171 }
2172
2173 #[test]
2174 fn test_gen_extendr_kwargs_constructor_uses_option_for_all_fields() {
2175 let typ = make_test_type();
2179 let empty_enums = ahash::AHashSet::new();
2180 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper, &empty_enums);
2181 assert!(
2182 !output.contains("= TRUE") && !output.contains("= FALSE") && !output.contains("= \"default\""),
2183 "constructor must not use Rust-syntax param defaults: {output}"
2184 );
2185 }
2186
2187 #[test]
2192 fn test_gen_go_functional_options_skips_tuple_fields() {
2193 let mut typ = make_test_type();
2194 typ.fields.push(FieldDef {
2195 name: "_0".to_string(),
2196 ty: TypeRef::Primitive(PrimitiveType::U32),
2197 optional: false,
2198 default: None,
2199 doc: String::new(),
2200 sanitized: false,
2201 is_boxed: false,
2202 type_rust_path: None,
2203 cfg: None,
2204 typed_default: None,
2205 core_wrapper: CoreWrapper::None,
2206 vec_inner_core_wrapper: CoreWrapper::None,
2207 newtype_wrapper: None,
2208 serde_rename: None,
2209 serde_flatten: false,
2210 });
2211 let output = gen_go_functional_options(&typ, &simple_type_mapper);
2212 assert!(
2213 !output.contains("_0"),
2214 "tuple field _0 should be filtered out from Go output"
2215 );
2216 }
2217
2218 #[test]
2223 fn test_gen_magnus_hash_constructor_generic_type_prefix() {
2224 let fields: Vec<FieldDef> = (0..16)
2226 .map(|i| FieldDef {
2227 name: format!("field_{i}"),
2228 ty: if i == 0 {
2229 TypeRef::Vec(Box::new(TypeRef::String))
2230 } else {
2231 TypeRef::Primitive(PrimitiveType::U32)
2232 },
2233 optional: false,
2234 default: None,
2235 doc: String::new(),
2236 sanitized: false,
2237 is_boxed: false,
2238 type_rust_path: None,
2239 cfg: None,
2240 typed_default: None,
2241 core_wrapper: CoreWrapper::None,
2242 vec_inner_core_wrapper: CoreWrapper::None,
2243 newtype_wrapper: None,
2244 serde_rename: None,
2245 serde_flatten: false,
2246 })
2247 .collect();
2248 let typ = TypeDef {
2249 name: "WideConfig".to_string(),
2250 rust_path: "crate::WideConfig".to_string(),
2251 original_rust_path: String::new(),
2252 fields,
2253 methods: vec![],
2254 is_opaque: false,
2255 is_clone: true,
2256 is_copy: false,
2257 doc: String::new(),
2258 cfg: None,
2259 is_trait: false,
2260 has_default: true,
2261 has_stripped_cfg_fields: false,
2262 is_return_type: false,
2263 serde_rename_all: None,
2264 has_serde: false,
2265 super_traits: vec![],
2266 };
2267 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2268 assert!(
2270 output.contains("<Vec<String>>::try_convert"),
2271 "generic types should use UFCS angle-bracket prefix: {output}"
2272 );
2273 }
2274
2275 #[test]
2282 fn test_magnus_hash_constructor_no_double_option_when_ty_is_optional() {
2283 let field = FieldDef {
2287 name: "max_depth".to_string(),
2288 ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
2289 optional: true,
2290 default: None,
2291 doc: String::new(),
2292 sanitized: false,
2293 is_boxed: false,
2294 type_rust_path: None,
2295 cfg: None,
2296 typed_default: None,
2297 core_wrapper: CoreWrapper::None,
2298 vec_inner_core_wrapper: CoreWrapper::None,
2299 newtype_wrapper: None,
2300 serde_rename: None,
2301 serde_flatten: false,
2302 };
2303 let mut fields: Vec<FieldDef> = (0..15)
2305 .map(|i| FieldDef {
2306 name: format!("field_{i}"),
2307 ty: TypeRef::Primitive(PrimitiveType::U32),
2308 optional: false,
2309 default: None,
2310 doc: String::new(),
2311 sanitized: false,
2312 is_boxed: false,
2313 type_rust_path: None,
2314 cfg: None,
2315 typed_default: None,
2316 core_wrapper: CoreWrapper::None,
2317 vec_inner_core_wrapper: CoreWrapper::None,
2318 newtype_wrapper: None,
2319 serde_rename: None,
2320 serde_flatten: false,
2321 })
2322 .collect();
2323 fields.push(field);
2324 let typ = TypeDef {
2325 name: "UpdateConfig".to_string(),
2326 rust_path: "crate::UpdateConfig".to_string(),
2327 original_rust_path: String::new(),
2328 fields,
2329 methods: vec![],
2330 is_opaque: false,
2331 is_clone: true,
2332 is_copy: false,
2333 doc: String::new(),
2334 cfg: None,
2335 is_trait: false,
2336 has_default: true,
2337 has_stripped_cfg_fields: false,
2338 is_return_type: false,
2339 serde_rename_all: None,
2340 has_serde: false,
2341 super_traits: vec![],
2342 };
2343 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2344 assert!(
2347 !output.contains("Option<Option<"),
2348 "hash constructor must not emit double Option: {output}"
2349 );
2350 assert!(
2351 output.contains("i64::try_convert"),
2352 "hash constructor should call inner-type::try_convert, not Option<T>::try_convert: {output}"
2353 );
2354 }
2355
2356 #[test]
2357 fn test_magnus_positional_constructor_no_double_option_when_ty_is_optional() {
2358 let field = FieldDef {
2361 name: "max_depth".to_string(),
2362 ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
2363 optional: true,
2364 default: None,
2365 doc: String::new(),
2366 sanitized: false,
2367 is_boxed: false,
2368 type_rust_path: None,
2369 cfg: None,
2370 typed_default: None,
2371 core_wrapper: CoreWrapper::None,
2372 vec_inner_core_wrapper: CoreWrapper::None,
2373 newtype_wrapper: None,
2374 serde_rename: None,
2375 serde_flatten: false,
2376 };
2377 let typ = TypeDef {
2378 name: "SmallUpdate".to_string(),
2379 rust_path: "crate::SmallUpdate".to_string(),
2380 original_rust_path: String::new(),
2381 fields: vec![field],
2382 methods: vec![],
2383 is_opaque: false,
2384 is_clone: true,
2385 is_copy: false,
2386 doc: String::new(),
2387 cfg: None,
2388 is_trait: false,
2389 has_default: true,
2390 has_stripped_cfg_fields: false,
2391 is_return_type: false,
2392 serde_rename_all: None,
2393 has_serde: false,
2394 super_traits: vec![],
2395 };
2396 let output = gen_magnus_positional_constructor(&typ, &simple_type_mapper);
2397 assert!(
2400 !output.contains("Option<Option<"),
2401 "positional constructor must not emit double Option: {output}"
2402 );
2403 assert!(
2404 output.contains("Option<i64>"),
2405 "positional constructor should emit Option<inner> for optional Optional(T): {output}"
2406 );
2407 }
2408}