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