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