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