1use alef_core::ir::{DefaultValue, FieldDef, PrimitiveType, TypeDef, TypeRef};
2use heck::{ToPascalCase, ToShoutySnakeCase};
3
4pub fn gen_pyo3_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
7 let mut lines = Vec::new();
8 lines.push("#[new]".to_string());
9
10 let mut sig_parts = Vec::new();
12 for field in &typ.fields {
13 let default_str = default_value_for_field(field, "python");
14 sig_parts.push(format!("{}={}", field.name, default_str));
15 }
16 let signature = format!("#[pyo3(signature = ({}))]", sig_parts.join(", "));
17 lines.push(signature);
18
19 lines.push("fn new(".to_string());
21 for (i, field) in typ.fields.iter().enumerate() {
22 let type_str = type_mapper(&field.ty);
23 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
24 lines.push(format!(" {}: {}{}", field.name, type_str, comma));
25 }
26 lines.push(") -> Self {".to_string());
27
28 lines.push(" Self {".to_string());
30 for field in &typ.fields {
31 lines.push(format!(" {},", field.name));
32 }
33 lines.push(" }".to_string());
34 lines.push("}".to_string());
35
36 lines.join("\n")
37}
38
39pub fn gen_napi_defaults_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
41 let mut lines = Vec::new();
42 lines.push("pub fn new(mut env: napi::Env, obj: napi::Object) -> napi::Result<Self> {".to_string());
43
44 for field in &typ.fields {
46 let type_str = type_mapper(&field.ty);
47 let default_str = default_value_for_field(field, "rust");
48 lines.push(format!(
49 " let {}: {} = obj.get(\"{}\").unwrap_or({})?;",
50 field.name, type_str, field.name, default_str
51 ));
52 }
53
54 lines.push(" Ok(Self {".to_string());
55 for field in &typ.fields {
56 lines.push(format!(" {},", field.name));
57 }
58 lines.push(" })".to_string());
59 lines.push("}".to_string());
60
61 lines.join("\n")
62}
63
64pub fn gen_go_functional_options(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
67 let mut lines = Vec::new();
68
69 lines.push(format!("// {} is a configuration type.", typ.name));
71 lines.push(format!("type {} struct {{", typ.name));
72 for field in &typ.fields {
73 let go_type = type_mapper(&field.ty);
74 lines.push(format!(" {} {}", field.name.to_pascal_case(), go_type));
75 }
76 lines.push("}".to_string());
77 lines.push("".to_string());
78
79 lines.push(format!(
81 "// {}Option is a functional option for {}.",
82 typ.name, typ.name
83 ));
84 lines.push(format!("type {}Option func(*{})", typ.name, typ.name));
85 lines.push("".to_string());
86
87 for field in &typ.fields {
89 let option_name = format!("With{}", field.name.to_pascal_case());
90 let go_type = type_mapper(&field.ty);
91 lines.push(format!("// {} sets the {}.", option_name, field.name));
92 lines.push(format!("func {}(val {}) {}Option {{", option_name, go_type, typ.name));
93 lines.push(format!(" return func(c *{}) {{", typ.name));
94 lines.push(format!(" c.{} = val", field.name.to_pascal_case()));
95 lines.push(" }".to_string());
96 lines.push("}".to_string());
97 lines.push("".to_string());
98 }
99
100 lines.push(format!(
102 "// New{} creates a new {} with default values and applies options.",
103 typ.name, typ.name
104 ));
105 lines.push(format!(
106 "func New{}(opts ...{}Option) *{} {{",
107 typ.name, typ.name, typ.name
108 ));
109 lines.push(format!(" c := &{} {{", typ.name));
110 for field in &typ.fields {
111 let default_str = default_value_for_field(field, "go");
112 lines.push(format!(" {}: {},", field.name.to_pascal_case(), default_str));
113 }
114 lines.push(" }".to_string());
115 lines.push(" for _, opt := range opts {".to_string());
116 lines.push(" opt(c)".to_string());
117 lines.push(" }".to_string());
118 lines.push(" return c".to_string());
119 lines.push("}".to_string());
120
121 lines.join("\n")
122}
123
124pub fn gen_java_builder(typ: &TypeDef, package: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
127 let mut lines = Vec::new();
128
129 lines.push(format!(
130 "// DO NOT EDIT - auto-generated by alef\npackage {};\n",
131 package
132 ));
133 lines.push("/// Builder for creating instances of {} with sensible defaults".to_string());
134 lines.push(format!("public class {}Builder {{", typ.name));
135
136 for field in &typ.fields {
138 let java_type = type_mapper(&field.ty);
139 lines.push(format!(" private {} {};", java_type, field.name.to_lowercase()));
140 }
141 lines.push("".to_string());
142
143 lines.push(format!(" public {}Builder() {{", typ.name));
145 for field in &typ.fields {
146 let default_str = default_value_for_field(field, "java");
147 lines.push(format!(" this.{} = {};", field.name.to_lowercase(), default_str));
148 }
149 lines.push(" }".to_string());
150 lines.push("".to_string());
151
152 for field in &typ.fields {
154 let java_type = type_mapper(&field.ty);
155 let method_name = format!("with{}", field.name.to_pascal_case());
156 lines.push(format!(
157 " public {}Builder {}({} value) {{",
158 typ.name, method_name, java_type
159 ));
160 lines.push(format!(" this.{} = value;", field.name.to_lowercase()));
161 lines.push(" return this;".to_string());
162 lines.push(" }".to_string());
163 lines.push("".to_string());
164 }
165
166 lines.push(format!(" public {} build() {{", typ.name));
168 lines.push(format!(" return new {}(", typ.name));
169 for (i, field) in typ.fields.iter().enumerate() {
170 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
171 lines.push(format!(" this.{}{}", field.name.to_lowercase(), comma));
172 }
173 lines.push(" );".to_string());
174 lines.push(" }".to_string());
175 lines.push("}".to_string());
176
177 lines.join("\n")
178}
179
180pub fn gen_csharp_record(typ: &TypeDef, namespace: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
182 let mut lines = Vec::new();
183
184 lines.push("// This file is auto-generated by alef. DO NOT EDIT.".to_string());
185 lines.push("using System;".to_string());
186 lines.push("".to_string());
187 lines.push(format!("namespace {};\n", namespace));
188
189 lines.push(format!("/// Configuration record: {}", typ.name));
190 lines.push(format!("public record {} {{", typ.name));
191
192 for field in &typ.fields {
193 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
195 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
196 {
197 continue;
198 }
199
200 let cs_type = type_mapper(&field.ty);
201 let default_str = default_value_for_field(field, "csharp");
202 lines.push(format!(
203 " public {} {} {{ get; init; }} = {};",
204 cs_type,
205 field.name.to_pascal_case(),
206 default_str
207 ));
208 }
209
210 lines.push("}".to_string());
211
212 lines.join("\n")
213}
214
215pub fn default_value_for_field(field: &FieldDef, language: &str) -> String {
218 if let Some(typed_default) = &field.typed_default {
220 return match typed_default {
221 DefaultValue::BoolLiteral(b) => match language {
222 "python" => {
223 if *b {
224 "True".to_string()
225 } else {
226 "False".to_string()
227 }
228 }
229 "ruby" => {
230 if *b {
231 "true".to_string()
232 } else {
233 "false".to_string()
234 }
235 }
236 "go" => {
237 if *b {
238 "true".to_string()
239 } else {
240 "false".to_string()
241 }
242 }
243 "java" => {
244 if *b {
245 "true".to_string()
246 } else {
247 "false".to_string()
248 }
249 }
250 "csharp" => {
251 if *b {
252 "true".to_string()
253 } else {
254 "false".to_string()
255 }
256 }
257 "php" => {
258 if *b {
259 "true".to_string()
260 } else {
261 "false".to_string()
262 }
263 }
264 "r" => {
265 if *b {
266 "TRUE".to_string()
267 } else {
268 "FALSE".to_string()
269 }
270 }
271 "rust" => {
272 if *b {
273 "true".to_string()
274 } else {
275 "false".to_string()
276 }
277 }
278 _ => {
279 if *b {
280 "true".to_string()
281 } else {
282 "false".to_string()
283 }
284 }
285 },
286 DefaultValue::StringLiteral(s) => format!("\"{}\"", s.replace('"', "\\\"")),
287 DefaultValue::IntLiteral(n) => n.to_string(),
288 DefaultValue::FloatLiteral(f) => {
289 let s = f.to_string();
290 if !s.contains('.') { format!("{}.0", s) } else { s }
291 }
292 DefaultValue::EnumVariant(v) => match language {
293 "python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
294 "ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
295 "go" => format!("{}{}()", field.ty.type_name(), v.to_pascal_case()),
296 "java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
297 "csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
298 "php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
299 "r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
300 "rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
301 _ => v.clone(),
302 },
303 DefaultValue::Empty => {
304 match &field.ty {
306 TypeRef::Vec(_) => match language {
307 "python" | "ruby" | "csharp" => "[]".to_string(),
308 "go" => "nil".to_string(),
309 "java" => "List.of()".to_string(),
310 "php" => "[]".to_string(),
311 "r" => "c()".to_string(),
312 "rust" => "vec![]".to_string(),
313 _ => "null".to_string(),
314 },
315 TypeRef::Map(_, _) => match language {
316 "python" => "{}".to_string(),
317 "go" => "nil".to_string(),
318 "java" => "Map.of()".to_string(),
319 "rust" => "Default::default()".to_string(),
320 _ => "null".to_string(),
321 },
322 TypeRef::Primitive(p) => match p {
323 PrimitiveType::Bool => match language {
324 "python" => "False".to_string(),
325 "ruby" => "false".to_string(),
326 _ => "false".to_string(),
327 },
328 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
329 _ => "0".to_string(),
330 },
331 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => match language {
332 "rust" => "String::new()".to_string(),
333 _ => "\"\"".to_string(),
334 },
335 _ => match language {
336 "python" => "None".to_string(),
337 "ruby" => "nil".to_string(),
338 "go" => "nil".to_string(),
339 "rust" => "Default::default()".to_string(),
340 _ => "null".to_string(),
341 },
342 }
343 }
344 DefaultValue::None => match language {
345 "python" => "None".to_string(),
346 "ruby" => "nil".to_string(),
347 "go" => "nil".to_string(),
348 "java" => "null".to_string(),
349 "csharp" => "null".to_string(),
350 "php" => "null".to_string(),
351 "r" => "NULL".to_string(),
352 "rust" => "None".to_string(),
353 _ => "null".to_string(),
354 },
355 };
356 }
357
358 if let Some(default_str) = &field.default {
360 return default_str.clone();
361 }
362
363 match &field.ty {
365 TypeRef::Primitive(p) => match p {
366 alef_core::ir::PrimitiveType::Bool => match language {
367 "python" => "False".to_string(),
368 "ruby" => "false".to_string(),
369 "csharp" => "false".to_string(),
370 "java" => "false".to_string(),
371 "php" => "false".to_string(),
372 "r" => "FALSE".to_string(),
373 _ => "false".to_string(),
374 },
375 alef_core::ir::PrimitiveType::U8
376 | alef_core::ir::PrimitiveType::U16
377 | alef_core::ir::PrimitiveType::U32
378 | alef_core::ir::PrimitiveType::U64
379 | alef_core::ir::PrimitiveType::I8
380 | alef_core::ir::PrimitiveType::I16
381 | alef_core::ir::PrimitiveType::I32
382 | alef_core::ir::PrimitiveType::I64
383 | alef_core::ir::PrimitiveType::Usize
384 | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
385 alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
386 },
387 TypeRef::String | TypeRef::Char => match language {
388 "python" => "\"\"".to_string(),
389 "ruby" => "\"\"".to_string(),
390 "go" => "\"\"".to_string(),
391 "java" => "\"\"".to_string(),
392 "csharp" => "\"\"".to_string(),
393 "php" => "\"\"".to_string(),
394 "r" => "\"\"".to_string(),
395 "rust" => "String::new()".to_string(),
396 _ => "\"\"".to_string(),
397 },
398 TypeRef::Bytes => match language {
399 "python" => "b\"\"".to_string(),
400 "ruby" => "\"\"".to_string(),
401 "go" => "[]byte{}".to_string(),
402 "java" => "new byte[]{}".to_string(),
403 "csharp" => "new byte[]{}".to_string(),
404 "php" => "\"\"".to_string(),
405 "r" => "raw()".to_string(),
406 "rust" => "vec![]".to_string(),
407 _ => "[]".to_string(),
408 },
409 TypeRef::Optional(_) => match language {
410 "python" => "None".to_string(),
411 "ruby" => "nil".to_string(),
412 "go" => "nil".to_string(),
413 "java" => "null".to_string(),
414 "csharp" => "null".to_string(),
415 "php" => "null".to_string(),
416 "r" => "NULL".to_string(),
417 "rust" => "None".to_string(),
418 _ => "null".to_string(),
419 },
420 TypeRef::Vec(_) => match language {
421 "python" => "[]".to_string(),
422 "ruby" => "[]".to_string(),
423 "go" => "[]interface{}{}".to_string(),
424 "java" => "new java.util.ArrayList<>()".to_string(),
425 "csharp" => "[]".to_string(),
426 "php" => "[]".to_string(),
427 "r" => "c()".to_string(),
428 "rust" => "vec![]".to_string(),
429 _ => "[]".to_string(),
430 },
431 TypeRef::Map(_, _) => match language {
432 "python" => "{}".to_string(),
433 "ruby" => "{}".to_string(),
434 "go" => "make(map[string]interface{})".to_string(),
435 "java" => "new java.util.HashMap<>()".to_string(),
436 "csharp" => "new Dictionary<string, object>()".to_string(),
437 "php" => "[]".to_string(),
438 "r" => "list()".to_string(),
439 "rust" => "std::collections::HashMap::new()".to_string(),
440 _ => "{}".to_string(),
441 },
442 TypeRef::Json => match language {
443 "python" => "{}".to_string(),
444 "ruby" => "{}".to_string(),
445 "go" => "make(map[string]interface{})".to_string(),
446 "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
447 "csharp" => "JObject.Parse(\"{}\")".to_string(),
448 "php" => "[]".to_string(),
449 "r" => "list()".to_string(),
450 "rust" => "serde_json::json!({})".to_string(),
451 _ => "{}".to_string(),
452 },
453 _ => match language {
454 "python" => "None".to_string(),
455 "ruby" => "nil".to_string(),
456 "go" => "nil".to_string(),
457 "java" => "null".to_string(),
458 "csharp" => "null".to_string(),
459 "php" => "null".to_string(),
460 "r" => "NULL".to_string(),
461 "rust" => "Default::default()".to_string(),
462 _ => "null".to_string(),
463 },
464 }
465}
466
467trait TypeRefExt {
469 fn type_name(&self) -> String;
470}
471
472impl TypeRefExt for TypeRef {
473 fn type_name(&self) -> String {
474 match self {
475 TypeRef::Named(n) => n.clone(),
476 TypeRef::Primitive(p) => format!("{:?}", p),
477 TypeRef::String | TypeRef::Char => "String".to_string(),
478 TypeRef::Bytes => "Bytes".to_string(),
479 TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
480 TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
481 TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
482 TypeRef::Path => "Path".to_string(),
483 TypeRef::Unit => "()".to_string(),
484 TypeRef::Json => "Json".to_string(),
485 TypeRef::Duration => "Duration".to_string(),
486 }
487 }
488}
489
490pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
493 use std::fmt::Write;
494 let mut out = String::with_capacity(512);
495
496 writeln!(out, "fn new(").ok();
498
499 for (i, field) in typ.fields.iter().enumerate() {
501 let field_type = type_mapper(&field.ty);
502 let default_str = default_value_for_field(field, "ruby");
503 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
504 writeln!(out, " {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
505 }
506
507 writeln!(out, ") -> Self {{").ok();
508 writeln!(out, " Self {{").ok();
509
510 for field in &typ.fields {
512 writeln!(out, " {},", field.name).ok();
513 }
514
515 writeln!(out, " }}").ok();
516 writeln!(out, "}}").ok();
517
518 out
519}
520
521pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
525 use std::fmt::Write;
526 let mut out = String::with_capacity(512);
527
528 writeln!(out, "pub fn __construct(").ok();
529
530 for (i, field) in typ.fields.iter().enumerate() {
532 let mapped = type_mapper(&field.ty);
533 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
534 writeln!(out, " {}: Option<{}>{}", field.name, mapped, comma).ok();
535 }
536
537 writeln!(out, ") -> Self {{").ok();
538 writeln!(out, " Self {{").ok();
539
540 for field in &typ.fields {
541 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
542 if is_optional_field {
543 writeln!(out, " {},", field.name).ok();
545 } else {
546 let default_str = default_value_for_field(field, "php");
548 writeln!(
549 out,
550 " {}: {}.unwrap_or({}),",
551 field.name, field.name, default_str
552 )
553 .ok();
554 }
555 }
556
557 writeln!(out, " }}").ok();
558 writeln!(out, "}}").ok();
559
560 out
561}
562
563pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
566 use std::fmt::Write;
567 let mut out = String::with_capacity(512);
568
569 writeln!(
572 out,
573 "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
574 )
575 .ok();
576 writeln!(out, " Self {{").ok();
577
578 for field in &typ.fields {
580 let default_str = default_value_for_field(field, "rust");
581 writeln!(
582 out,
583 " {}: opts.get(\"{}\").and_then(|t| t.decode()).unwrap_or({}),",
584 field.name, field.name, default_str
585 )
586 .ok();
587 }
588
589 writeln!(out, " }}").ok();
590 writeln!(out, "}}").ok();
591
592 out
593}
594
595pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
598 use std::fmt::Write;
599 let mut out = String::with_capacity(512);
600
601 writeln!(out, "#[extendr]").ok();
602 writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
603
604 for (i, field) in typ.fields.iter().enumerate() {
606 let field_type = type_mapper(&field.ty);
607 let default_str = default_value_for_field(field, "r");
608 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
609 writeln!(out, " {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
610 }
611
612 writeln!(out, ") -> {} {{", typ.name).ok();
613 writeln!(out, " {} {{", typ.name).ok();
614
615 for field in &typ.fields {
617 writeln!(out, " {},", field.name).ok();
618 }
619
620 writeln!(out, " }}").ok();
621 writeln!(out, "}}").ok();
622
623 out
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 use alef_core::ir::{FieldDef, PrimitiveType, TypeRef};
630
631 fn make_test_type() -> TypeDef {
632 TypeDef {
633 name: "Config".to_string(),
634 rust_path: "my_crate::Config".to_string(),
635 fields: vec![
636 FieldDef {
637 name: "timeout".to_string(),
638 ty: TypeRef::Primitive(PrimitiveType::U64),
639 optional: false,
640 default: Some("30".to_string()),
641 doc: "Timeout in seconds".to_string(),
642 sanitized: false,
643 is_boxed: false,
644 type_rust_path: None,
645 cfg: None,
646 typed_default: Some(DefaultValue::IntLiteral(30)),
647 },
648 FieldDef {
649 name: "enabled".to_string(),
650 ty: TypeRef::Primitive(PrimitiveType::Bool),
651 optional: false,
652 default: None,
653 doc: "Enable feature".to_string(),
654 sanitized: false,
655 is_boxed: false,
656 type_rust_path: None,
657 cfg: None,
658 typed_default: Some(DefaultValue::BoolLiteral(true)),
659 },
660 FieldDef {
661 name: "name".to_string(),
662 ty: TypeRef::String,
663 optional: false,
664 default: None,
665 doc: "Config name".to_string(),
666 sanitized: false,
667 is_boxed: false,
668 type_rust_path: None,
669 cfg: None,
670 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
671 },
672 ],
673 methods: vec![],
674 is_opaque: false,
675 is_clone: true,
676 doc: "Configuration type".to_string(),
677 cfg: None,
678 is_trait: false,
679 has_default: true,
680 has_stripped_cfg_fields: false,
681 is_return_type: false,
682 }
683 }
684
685 #[test]
686 fn test_default_value_bool_true_python() {
687 let field = FieldDef {
688 name: "enabled".to_string(),
689 ty: TypeRef::Primitive(PrimitiveType::Bool),
690 optional: false,
691 default: None,
692 doc: String::new(),
693 sanitized: false,
694 is_boxed: false,
695 type_rust_path: None,
696 cfg: None,
697 typed_default: Some(DefaultValue::BoolLiteral(true)),
698 };
699 assert_eq!(default_value_for_field(&field, "python"), "True");
700 }
701
702 #[test]
703 fn test_default_value_bool_false_go() {
704 let field = FieldDef {
705 name: "enabled".to_string(),
706 ty: TypeRef::Primitive(PrimitiveType::Bool),
707 optional: false,
708 default: None,
709 doc: String::new(),
710 sanitized: false,
711 is_boxed: false,
712 type_rust_path: None,
713 cfg: None,
714 typed_default: Some(DefaultValue::BoolLiteral(false)),
715 };
716 assert_eq!(default_value_for_field(&field, "go"), "false");
717 }
718
719 #[test]
720 fn test_default_value_string_literal() {
721 let field = FieldDef {
722 name: "name".to_string(),
723 ty: TypeRef::String,
724 optional: false,
725 default: None,
726 doc: String::new(),
727 sanitized: false,
728 is_boxed: false,
729 type_rust_path: None,
730 cfg: None,
731 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
732 };
733 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
734 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
735 }
736
737 #[test]
738 fn test_default_value_int_literal() {
739 let field = FieldDef {
740 name: "timeout".to_string(),
741 ty: TypeRef::Primitive(PrimitiveType::U64),
742 optional: false,
743 default: None,
744 doc: String::new(),
745 sanitized: false,
746 is_boxed: false,
747 type_rust_path: None,
748 cfg: None,
749 typed_default: Some(DefaultValue::IntLiteral(42)),
750 };
751 let result = default_value_for_field(&field, "python");
752 assert_eq!(result, "42");
753 }
754
755 #[test]
756 fn test_default_value_none() {
757 let field = FieldDef {
758 name: "maybe".to_string(),
759 ty: TypeRef::Optional(Box::new(TypeRef::String)),
760 optional: true,
761 default: None,
762 doc: String::new(),
763 sanitized: false,
764 is_boxed: false,
765 type_rust_path: None,
766 cfg: None,
767 typed_default: Some(DefaultValue::None),
768 };
769 assert_eq!(default_value_for_field(&field, "python"), "None");
770 assert_eq!(default_value_for_field(&field, "go"), "nil");
771 assert_eq!(default_value_for_field(&field, "java"), "null");
772 assert_eq!(default_value_for_field(&field, "csharp"), "null");
773 }
774
775 #[test]
776 fn test_default_value_fallback_string() {
777 let field = FieldDef {
778 name: "name".to_string(),
779 ty: TypeRef::String,
780 optional: false,
781 default: Some("\"custom\"".to_string()),
782 doc: String::new(),
783 sanitized: false,
784 is_boxed: false,
785 type_rust_path: None,
786 cfg: None,
787 typed_default: None,
788 };
789 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
790 }
791
792 #[test]
793 fn test_gen_pyo3_kwargs_constructor() {
794 let typ = make_test_type();
795 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
796 TypeRef::Primitive(p) => format!("{:?}", p),
797 TypeRef::String | TypeRef::Char => "str".to_string(),
798 _ => "Any".to_string(),
799 });
800
801 assert!(output.contains("#[new]"));
802 assert!(output.contains("#[pyo3(signature = ("));
803 assert!(output.contains("timeout=30"));
804 assert!(output.contains("enabled=True"));
805 assert!(output.contains("name=\"default\""));
806 assert!(output.contains("fn new("));
807 }
808
809 #[test]
810 fn test_gen_napi_defaults_constructor() {
811 let typ = make_test_type();
812 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
813 TypeRef::Primitive(p) => format!("{:?}", p),
814 TypeRef::String | TypeRef::Char => "String".to_string(),
815 _ => "Value".to_string(),
816 });
817
818 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
819 assert!(output.contains("timeout"));
820 assert!(output.contains("enabled"));
821 assert!(output.contains("name"));
822 }
823
824 #[test]
825 fn test_gen_go_functional_options() {
826 let typ = make_test_type();
827 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
828 TypeRef::Primitive(p) => match p {
829 PrimitiveType::U64 => "uint64".to_string(),
830 PrimitiveType::Bool => "bool".to_string(),
831 _ => "interface{}".to_string(),
832 },
833 TypeRef::String | TypeRef::Char => "string".to_string(),
834 _ => "interface{}".to_string(),
835 });
836
837 assert!(output.contains("type Config struct {"));
838 assert!(output.contains("type ConfigOption func(*Config)"));
839 assert!(output.contains("func WithTimeout(val uint64) ConfigOption"));
840 assert!(output.contains("func WithEnabled(val bool) ConfigOption"));
841 assert!(output.contains("func WithName(val string) ConfigOption"));
842 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
843 }
844
845 #[test]
846 fn test_gen_java_builder() {
847 let typ = make_test_type();
848 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
849 TypeRef::Primitive(p) => match p {
850 PrimitiveType::U64 => "long".to_string(),
851 PrimitiveType::Bool => "boolean".to_string(),
852 _ => "int".to_string(),
853 },
854 TypeRef::String | TypeRef::Char => "String".to_string(),
855 _ => "Object".to_string(),
856 });
857
858 assert!(output.contains("package dev.test;"));
859 assert!(output.contains("public class ConfigBuilder"));
860 assert!(output.contains("withTimeout"));
861 assert!(output.contains("withEnabled"));
862 assert!(output.contains("withName"));
863 assert!(output.contains("public Config build()"));
864 }
865
866 #[test]
867 fn test_gen_csharp_record() {
868 let typ = make_test_type();
869 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
870 TypeRef::Primitive(p) => match p {
871 PrimitiveType::U64 => "ulong".to_string(),
872 PrimitiveType::Bool => "bool".to_string(),
873 _ => "int".to_string(),
874 },
875 TypeRef::String | TypeRef::Char => "string".to_string(),
876 _ => "object".to_string(),
877 });
878
879 assert!(output.contains("namespace MyNamespace;"));
880 assert!(output.contains("public record Config"));
881 assert!(output.contains("public ulong Timeout"));
882 assert!(output.contains("public bool Enabled"));
883 assert!(output.contains("public string Name"));
884 assert!(output.contains("init;"));
885 }
886
887 #[test]
888 fn test_default_value_float_literal() {
889 let field = FieldDef {
890 name: "ratio".to_string(),
891 ty: TypeRef::Primitive(PrimitiveType::F64),
892 optional: false,
893 default: None,
894 doc: String::new(),
895 sanitized: false,
896 is_boxed: false,
897 type_rust_path: None,
898 cfg: None,
899 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
900 };
901 let result = default_value_for_field(&field, "python");
902 assert!(result.contains("1.5"));
903 }
904
905 #[test]
906 fn test_default_value_no_typed_no_default() {
907 let field = FieldDef {
908 name: "count".to_string(),
909 ty: TypeRef::Primitive(PrimitiveType::U32),
910 optional: false,
911 default: None,
912 doc: String::new(),
913 sanitized: false,
914 is_boxed: false,
915 type_rust_path: None,
916 cfg: None,
917 typed_default: None,
918 };
919 assert_eq!(default_value_for_field(&field, "python"), "0");
921 assert_eq!(default_value_for_field(&field, "go"), "0");
922 }
923}