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