1use alef_core::config::TraitBridgeConfig;
9use alef_core::ir::{MethodDef, ParamDef, TypeDef};
10use heck::ToSnakeCase;
11use std::collections::HashMap;
12use std::fmt::Write;
13
14pub struct TraitBridgeSpec<'a> {
16 pub trait_def: &'a TypeDef,
18 pub bridge_config: &'a TraitBridgeConfig,
20 pub core_import: &'a str,
22 pub wrapper_prefix: &'a str,
24 pub type_paths: HashMap<String, String>,
26 pub error_type: String,
28}
29
30impl<'a> TraitBridgeSpec<'a> {
31 pub fn error_path(&self) -> String {
33 format!("{}::{}", self.core_import, self.error_type)
34 }
35
36 pub fn wrapper_name(&self) -> String {
38 format!("{}{}Bridge", self.wrapper_prefix, self.trait_def.name)
39 }
40
41 pub fn trait_snake(&self) -> String {
43 self.trait_def.name.to_snake_case()
44 }
45
46 pub fn trait_path(&self) -> String {
48 self.trait_def.rust_path.replace('-', "_")
49 }
50
51 pub fn required_methods(&self) -> Vec<&'a MethodDef> {
53 self.trait_def.methods.iter().filter(|m| !m.has_default_impl).collect()
54 }
55
56 pub fn optional_methods(&self) -> Vec<&'a MethodDef> {
58 self.trait_def.methods.iter().filter(|m| m.has_default_impl).collect()
59 }
60}
61
62pub trait TraitBridgeGenerator {
68 fn foreign_object_type(&self) -> &str;
70
71 fn bridge_imports(&self) -> Vec<String>;
73
74 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
79
80 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
84
85 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String;
90
91 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String;
97}
98
99pub fn gen_bridge_wrapper_struct(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
113 let wrapper = spec.wrapper_name();
114 let foreign_type = generator.foreign_object_type();
115 let mut out = String::with_capacity(512);
116
117 writeln!(
118 out,
119 "/// Wrapper that bridges a foreign {prefix} object to the `{trait_name}` trait.",
120 prefix = spec.wrapper_prefix,
121 trait_name = spec.trait_def.name,
122 )
123 .ok();
124 writeln!(out, "pub struct {wrapper} {{").ok();
125 writeln!(out, " inner: {foreign_type},").ok();
126 writeln!(out, " cached_name: String,").ok();
127 write!(out, "}}").ok();
128 out
129}
130
131pub fn gen_bridge_plugin_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
139 let super_trait_name = spec.bridge_config.super_trait.as_deref()?;
140
141 let wrapper = spec.wrapper_name();
142 let core_import = spec.core_import;
143
144 let super_trait_path = if super_trait_name.contains("::") {
146 super_trait_name.to_string()
147 } else {
148 format!("{core_import}::{super_trait_name}")
149 };
150
151 let mut out = String::with_capacity(1024);
155 writeln!(out, "impl {super_trait_path} for {wrapper} {{").ok();
156
157 writeln!(out, " fn name(&self) -> &str {{").ok();
159 writeln!(out, " &self.cached_name").ok();
160 writeln!(out, " }}").ok();
161 writeln!(out).ok();
162
163 let error_path = spec.error_path();
164
165 writeln!(out, " fn version(&self) -> String {{").ok();
167 let version_method = MethodDef {
168 name: "version".to_string(),
169 params: vec![],
170 return_type: alef_core::ir::TypeRef::String,
171 is_async: false,
172 is_static: false,
173 error_type: None,
174 doc: String::new(),
175 receiver: Some(alef_core::ir::ReceiverKind::Ref),
176 sanitized: false,
177 trait_source: None,
178 returns_ref: false,
179 returns_cow: false,
180 return_newtype_wrapper: None,
181 has_default_impl: false,
182 };
183 let version_body = generator.gen_sync_method_body(&version_method, spec);
184 for line in version_body.lines() {
185 writeln!(out, " {}", line.trim_start()).ok();
186 }
187 writeln!(out, " }}").ok();
188 writeln!(out).ok();
189
190 writeln!(
192 out,
193 " fn initialize(&self) -> Result<(), {error_path}> {{"
194 )
195 .ok();
196 let init_method = MethodDef {
197 name: "initialize".to_string(),
198 params: vec![],
199 return_type: alef_core::ir::TypeRef::Unit,
200 is_async: false,
201 is_static: false,
202 error_type: Some(error_path.clone()),
203 doc: String::new(),
204 receiver: Some(alef_core::ir::ReceiverKind::Ref),
205 sanitized: false,
206 trait_source: None,
207 returns_ref: false,
208 returns_cow: false,
209 return_newtype_wrapper: None,
210 has_default_impl: true,
211 };
212 let init_body = generator.gen_sync_method_body(&init_method, spec);
213 for line in init_body.lines() {
214 writeln!(out, " {}", line.trim_start()).ok();
215 }
216 writeln!(out, " }}").ok();
217 writeln!(out).ok();
218
219 writeln!(
221 out,
222 " fn shutdown(&self) -> Result<(), {error_path}> {{"
223 )
224 .ok();
225 let shutdown_method = MethodDef {
226 name: "shutdown".to_string(),
227 params: vec![],
228 return_type: alef_core::ir::TypeRef::Unit,
229 is_async: false,
230 is_static: false,
231 error_type: Some(error_path.clone()),
232 doc: String::new(),
233 receiver: Some(alef_core::ir::ReceiverKind::Ref),
234 sanitized: false,
235 trait_source: None,
236 returns_ref: false,
237 returns_cow: false,
238 return_newtype_wrapper: None,
239 has_default_impl: true,
240 };
241 let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
242 for line in shutdown_body.lines() {
243 writeln!(out, " {}", line.trim_start()).ok();
244 }
245 writeln!(out, " }}").ok();
246 write!(out, "}}").ok();
247 Some(out)
248}
249
250pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
255 let wrapper = spec.wrapper_name();
256 let trait_path = spec.trait_path();
257 let mut out = String::with_capacity(2048);
258
259 writeln!(out, "impl {trait_path} for {wrapper} {{").ok();
260
261 let own_methods: Vec<_> = spec
263 .trait_def
264 .methods
265 .iter()
266 .filter(|m| m.trait_source.is_none())
267 .collect();
268
269 for (i, method) in own_methods.iter().enumerate() {
270 if i > 0 {
271 writeln!(out).ok();
272 }
273
274 let async_kw = if method.is_async { "async " } else { "" };
276 let receiver = match &method.receiver {
277 Some(alef_core::ir::ReceiverKind::Ref) => "&self",
278 Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
279 Some(alef_core::ir::ReceiverKind::Owned) => "self",
280 None => "",
281 };
282
283 let params: Vec<String> = method
285 .params
286 .iter()
287 .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
288 .collect();
289
290 let all_params = if receiver.is_empty() {
291 params.join(", ")
292 } else if params.is_empty() {
293 receiver.to_string()
294 } else {
295 format!("{}, {}", receiver, params.join(", "))
296 };
297
298 let error_override = method.error_type.as_ref().map(|_| spec.error_path());
302 let ret = format_return_type(&method.return_type, error_override.as_deref(), &spec.type_paths);
303
304 writeln!(out, " {async_kw}fn {}({all_params}) -> {ret} {{", method.name).ok();
305
306 let body = if method.is_async {
308 generator.gen_async_method_body(method, spec)
309 } else {
310 generator.gen_sync_method_body(method, spec)
311 };
312
313 for line in body.lines() {
314 writeln!(out, " {line}").ok();
315 }
316 writeln!(out, " }}").ok();
317 }
318
319 write!(out, "}}").ok();
320 out
321}
322
323pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
330 spec.bridge_config.register_fn.as_deref()?;
331 Some(generator.gen_registration_fn(spec))
332}
333
334pub struct BridgeOutput {
337 pub imports: Vec<String>,
339 pub code: String,
341}
342
343pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
349 let imports = generator.bridge_imports();
350 let mut out = String::with_capacity(4096);
351
352 out.push_str(&gen_bridge_wrapper_struct(spec, generator));
354 writeln!(out).ok();
355 writeln!(out).ok();
356
357 out.push_str(&generator.gen_constructor(spec));
359 writeln!(out).ok();
360 writeln!(out).ok();
361
362 if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
364 out.push_str(&plugin_impl);
365 writeln!(out).ok();
366 writeln!(out).ok();
367 }
368
369 out.push_str(&gen_bridge_trait_impl(spec, generator));
371
372 if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
374 writeln!(out).ok();
375 writeln!(out).ok();
376 out.push_str(®_fn_code);
377 }
378
379 BridgeOutput { imports, code: out }
380}
381
382pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
391 use alef_core::ir::{PrimitiveType, TypeRef};
392 match ty {
393 TypeRef::Primitive(p) => match p {
394 PrimitiveType::Bool => "bool",
395 PrimitiveType::U8 => "u8",
396 PrimitiveType::U16 => "u16",
397 PrimitiveType::U32 => "u32",
398 PrimitiveType::U64 => "u64",
399 PrimitiveType::I8 => "i8",
400 PrimitiveType::I16 => "i16",
401 PrimitiveType::I32 => "i32",
402 PrimitiveType::I64 => "i64",
403 PrimitiveType::F32 => "f32",
404 PrimitiveType::F64 => "f64",
405 PrimitiveType::Usize => "usize",
406 PrimitiveType::Isize => "isize",
407 }
408 .to_string(),
409 TypeRef::String => "String".to_string(),
410 TypeRef::Char => "char".to_string(),
411 TypeRef::Bytes => "Vec<u8>".to_string(),
412 TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
413 TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
414 TypeRef::Map(k, v) => format!(
415 "std::collections::HashMap<{}, {}>",
416 format_type_ref(k, type_paths),
417 format_type_ref(v, type_paths)
418 ),
419 TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
420 TypeRef::Path => "std::path::PathBuf".to_string(),
421 TypeRef::Unit => "()".to_string(),
422 TypeRef::Json => "serde_json::Value".to_string(),
423 TypeRef::Duration => "std::time::Duration".to_string(),
424 }
425}
426
427pub fn format_return_type(
429 ty: &alef_core::ir::TypeRef,
430 error_type: Option<&str>,
431 type_paths: &HashMap<String, String>,
432) -> String {
433 let inner = format_type_ref(ty, type_paths);
434 match error_type {
435 Some(err) => format!("Result<{inner}, {err}>"),
436 None => inner,
437 }
438}
439
440pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
450 use alef_core::ir::TypeRef;
451 if param.is_ref {
452 match ¶m.ty {
453 TypeRef::String => "&str".to_string(),
454 TypeRef::Bytes => "&[u8]".to_string(),
455 TypeRef::Path => "&std::path::Path".to_string(),
456 TypeRef::Vec(inner) => format!("&[{}]", format_type_ref(inner, type_paths)),
457 TypeRef::Named(name) => {
458 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
459 format!("&{qualified}")
460 }
461 other => format_type_ref(other, type_paths),
463 }
464 } else {
465 format_type_ref(¶m.ty, type_paths)
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use alef_core::config::TraitBridgeConfig;
473 use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
474
475 fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
480 TraitBridgeConfig {
481 trait_name: "OcrBackend".to_string(),
482 super_trait: super_trait.map(str::to_string),
483 registry_getter: None,
484 register_fn: register_fn.map(str::to_string),
485 type_alias: None,
486 param_name: None,
487 }
488 }
489
490 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
491 TypeDef {
492 name: name.to_string(),
493 rust_path: rust_path.to_string(),
494 original_rust_path: rust_path.to_string(),
495 fields: vec![],
496 methods,
497 is_opaque: true,
498 is_clone: false,
499 doc: String::new(),
500 cfg: None,
501 is_trait: true,
502 has_default: false,
503 has_stripped_cfg_fields: false,
504 is_return_type: false,
505 serde_rename_all: None,
506 has_serde: false,
507 super_traits: vec![],
508 }
509 }
510
511 fn make_method(
512 name: &str,
513 params: Vec<ParamDef>,
514 return_type: TypeRef,
515 is_async: bool,
516 has_default_impl: bool,
517 trait_source: Option<&str>,
518 error_type: Option<&str>,
519 ) -> MethodDef {
520 MethodDef {
521 name: name.to_string(),
522 params,
523 return_type,
524 is_async,
525 is_static: false,
526 error_type: error_type.map(str::to_string),
527 doc: String::new(),
528 receiver: Some(ReceiverKind::Ref),
529 sanitized: false,
530 trait_source: trait_source.map(str::to_string),
531 returns_ref: false,
532 returns_cow: false,
533 return_newtype_wrapper: None,
534 has_default_impl,
535 }
536 }
537
538 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
539 ParamDef {
540 name: name.to_string(),
541 ty,
542 optional: false,
543 default: None,
544 sanitized: false,
545 typed_default: None,
546 is_ref,
547 is_mut: false,
548 newtype_wrapper: None,
549 }
550 }
551
552 fn make_spec<'a>(
553 trait_def: &'a TypeDef,
554 bridge_config: &'a TraitBridgeConfig,
555 wrapper_prefix: &'a str,
556 type_paths: HashMap<String, String>,
557 ) -> TraitBridgeSpec<'a> {
558 TraitBridgeSpec {
559 trait_def,
560 bridge_config,
561 core_import: "mylib",
562 wrapper_prefix,
563 type_paths,
564 error_type: "MyError".to_string(),
565 }
566 }
567
568 struct MockBridgeGenerator;
573
574 impl TraitBridgeGenerator for MockBridgeGenerator {
575 fn foreign_object_type(&self) -> &str {
576 "Py<PyAny>"
577 }
578
579 fn bridge_imports(&self) -> Vec<String> {
580 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
581 }
582
583 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
584 format!("// sync body for {}", method.name)
585 }
586
587 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
588 format!("// async body for {}", method.name)
589 }
590
591 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
592 format!(
593 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
594 spec.wrapper_name()
595 )
596 }
597
598 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
599 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
600 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
601 }
602 }
603
604 #[test]
609 fn test_wrapper_name() {
610 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
611 let config = make_trait_bridge_config(None, None);
612 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
613 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
614 }
615
616 #[test]
617 fn test_trait_snake() {
618 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
619 let config = make_trait_bridge_config(None, None);
620 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
621 assert_eq!(spec.trait_snake(), "ocr_backend");
622 }
623
624 #[test]
625 fn test_trait_path_replaces_hyphens() {
626 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
627 let config = make_trait_bridge_config(None, None);
628 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
629 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
630 }
631
632 #[test]
633 fn test_required_methods_filters_no_default_impl() {
634 let methods = vec![
635 make_method("process", vec![], TypeRef::String, false, false, None, None),
636 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
637 make_method("detect", vec![], TypeRef::String, false, false, None, None),
638 ];
639 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
640 let config = make_trait_bridge_config(None, None);
641 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
642 let required = spec.required_methods();
643 assert_eq!(required.len(), 2);
644 assert!(required.iter().any(|m| m.name == "process"));
645 assert!(required.iter().any(|m| m.name == "detect"));
646 }
647
648 #[test]
649 fn test_optional_methods_filters_has_default_impl() {
650 let methods = vec![
651 make_method("process", vec![], TypeRef::String, false, false, None, None),
652 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
653 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
654 ];
655 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
656 let config = make_trait_bridge_config(None, None);
657 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
658 let optional = spec.optional_methods();
659 assert_eq!(optional.len(), 2);
660 assert!(optional.iter().any(|m| m.name == "initialize"));
661 assert!(optional.iter().any(|m| m.name == "shutdown"));
662 }
663
664 #[test]
665 fn test_error_path() {
666 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
667 let config = make_trait_bridge_config(None, None);
668 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
669 assert_eq!(spec.error_path(), "mylib::MyError");
670 }
671
672 #[test]
677 fn test_format_type_ref_primitives() {
678 let paths = HashMap::new();
679 let cases: Vec<(TypeRef, &str)> = vec![
680 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
681 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
682 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
683 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
684 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
685 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
686 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
687 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
688 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
689 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
690 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
691 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
692 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
693 ];
694 for (ty, expected) in cases {
695 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
696 }
697 }
698
699 #[test]
700 fn test_format_type_ref_string() {
701 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
702 }
703
704 #[test]
705 fn test_format_type_ref_char() {
706 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
707 }
708
709 #[test]
710 fn test_format_type_ref_bytes() {
711 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
712 }
713
714 #[test]
715 fn test_format_type_ref_path() {
716 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
717 }
718
719 #[test]
720 fn test_format_type_ref_unit() {
721 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
722 }
723
724 #[test]
725 fn test_format_type_ref_json() {
726 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
727 }
728
729 #[test]
730 fn test_format_type_ref_duration() {
731 assert_eq!(
732 format_type_ref(&TypeRef::Duration, &HashMap::new()),
733 "std::time::Duration"
734 );
735 }
736
737 #[test]
738 fn test_format_type_ref_optional() {
739 let ty = TypeRef::Optional(Box::new(TypeRef::String));
740 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
741 }
742
743 #[test]
744 fn test_format_type_ref_optional_nested() {
745 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
746 PrimitiveType::U32,
747 )))));
748 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
749 }
750
751 #[test]
752 fn test_format_type_ref_vec() {
753 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
754 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
755 }
756
757 #[test]
758 fn test_format_type_ref_vec_nested() {
759 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
760 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
761 }
762
763 #[test]
764 fn test_format_type_ref_map() {
765 let ty = TypeRef::Map(
766 Box::new(TypeRef::String),
767 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
768 );
769 assert_eq!(
770 format_type_ref(&ty, &HashMap::new()),
771 "std::collections::HashMap<String, i64>"
772 );
773 }
774
775 #[test]
776 fn test_format_type_ref_map_nested_value() {
777 let ty = TypeRef::Map(
778 Box::new(TypeRef::String),
779 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
780 );
781 assert_eq!(
782 format_type_ref(&ty, &HashMap::new()),
783 "std::collections::HashMap<String, Vec<String>>"
784 );
785 }
786
787 #[test]
788 fn test_format_type_ref_named_without_type_paths() {
789 let ty = TypeRef::Named("Config".to_string());
790 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
791 }
792
793 #[test]
794 fn test_format_type_ref_named_with_type_paths() {
795 let ty = TypeRef::Named("Config".to_string());
796 let mut paths = HashMap::new();
797 paths.insert("Config".to_string(), "mylib::Config".to_string());
798 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
799 }
800
801 #[test]
802 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
803 let ty = TypeRef::Named("Unknown".to_string());
804 let mut paths = HashMap::new();
805 paths.insert("Other".to_string(), "mylib::Other".to_string());
806 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
807 }
808
809 #[test]
814 fn test_format_param_type_string_ref() {
815 let param = make_param("input", TypeRef::String, true);
816 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
817 }
818
819 #[test]
820 fn test_format_param_type_string_owned() {
821 let param = make_param("input", TypeRef::String, false);
822 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
823 }
824
825 #[test]
826 fn test_format_param_type_bytes_ref() {
827 let param = make_param("data", TypeRef::Bytes, true);
828 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
829 }
830
831 #[test]
832 fn test_format_param_type_bytes_owned() {
833 let param = make_param("data", TypeRef::Bytes, false);
834 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
835 }
836
837 #[test]
838 fn test_format_param_type_path_ref() {
839 let param = make_param("path", TypeRef::Path, true);
840 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
841 }
842
843 #[test]
844 fn test_format_param_type_path_owned() {
845 let param = make_param("path", TypeRef::Path, false);
846 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
847 }
848
849 #[test]
850 fn test_format_param_type_vec_ref() {
851 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
852 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
853 }
854
855 #[test]
856 fn test_format_param_type_vec_owned() {
857 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
858 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
859 }
860
861 #[test]
862 fn test_format_param_type_named_ref_with_type_paths() {
863 let mut paths = HashMap::new();
864 paths.insert("Config".to_string(), "mylib::Config".to_string());
865 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
866 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
867 }
868
869 #[test]
870 fn test_format_param_type_named_ref_without_type_paths() {
871 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
872 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
873 }
874
875 #[test]
876 fn test_format_param_type_primitive_ref_passes_by_value() {
877 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
879 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
880 }
881
882 #[test]
883 fn test_format_param_type_unit_ref_passes_by_value() {
884 let param = make_param("nothing", TypeRef::Unit, true);
885 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
886 }
887
888 #[test]
893 fn test_format_return_type_without_error() {
894 let result = format_return_type(&TypeRef::String, None, &HashMap::new());
895 assert_eq!(result, "String");
896 }
897
898 #[test]
899 fn test_format_return_type_with_error() {
900 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new());
901 assert_eq!(result, "Result<String, MyError>");
902 }
903
904 #[test]
905 fn test_format_return_type_unit_with_error() {
906 let result = format_return_type(&TypeRef::Unit, Some("Box<dyn std::error::Error>"), &HashMap::new());
907 assert_eq!(result, "Result<(), Box<dyn std::error::Error>>");
908 }
909
910 #[test]
911 fn test_format_return_type_named_with_type_paths_and_error() {
912 let mut paths = HashMap::new();
913 paths.insert("Output".to_string(), "mylib::Output".to_string());
914 let result = format_return_type(&TypeRef::Named("Output".to_string()), Some("mylib::MyError"), &paths);
915 assert_eq!(result, "Result<mylib::Output, mylib::MyError>");
916 }
917
918 #[test]
923 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
924 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
925 let config = make_trait_bridge_config(None, None);
926 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
927 let generator = MockBridgeGenerator;
928 let result = gen_bridge_wrapper_struct(&spec, &generator);
929 assert!(
930 result.contains("pub struct PyOcrBackendBridge"),
931 "missing struct declaration in:\n{result}"
932 );
933 }
934
935 #[test]
936 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
937 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
938 let config = make_trait_bridge_config(None, None);
939 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
940 let generator = MockBridgeGenerator;
941 let result = gen_bridge_wrapper_struct(&spec, &generator);
942 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
943 }
944
945 #[test]
946 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
947 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
948 let config = make_trait_bridge_config(None, None);
949 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
950 let generator = MockBridgeGenerator;
951 let result = gen_bridge_wrapper_struct(&spec, &generator);
952 assert!(
953 result.contains("cached_name: String"),
954 "missing cached_name field in:\n{result}"
955 );
956 }
957
958 #[test]
963 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
964 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
965 let config = make_trait_bridge_config(None, None);
966 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
967 let generator = MockBridgeGenerator;
968 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
969 }
970
971 #[test]
972 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
973 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
974 let config = make_trait_bridge_config(Some("Plugin"), None);
975 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
976 let generator = MockBridgeGenerator;
977 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
978 }
979
980 #[test]
981 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
982 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
983 let config = make_trait_bridge_config(Some("Plugin"), None);
984 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
985 let generator = MockBridgeGenerator;
986 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
987 assert!(
988 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
989 "missing qualified super-trait path in:\n{result}"
990 );
991 }
992
993 #[test]
994 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
995 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
996 let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
997 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
998 let generator = MockBridgeGenerator;
999 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1000 assert!(
1001 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1002 "wrong super-trait path in:\n{result}"
1003 );
1004 }
1005
1006 #[test]
1007 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1008 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1009 let config = make_trait_bridge_config(Some("Plugin"), None);
1010 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1011 let generator = MockBridgeGenerator;
1012 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1013 assert!(
1014 result.contains("fn name(") && result.contains("cached_name"),
1015 "missing name() using cached_name in:\n{result}"
1016 );
1017 }
1018
1019 #[test]
1020 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1021 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1022 let config = make_trait_bridge_config(Some("Plugin"), None);
1023 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1024 let generator = MockBridgeGenerator;
1025 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1026 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1027 }
1028
1029 #[test]
1030 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1031 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1032 let config = make_trait_bridge_config(Some("Plugin"), None);
1033 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1034 let generator = MockBridgeGenerator;
1035 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1036 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1037 }
1038
1039 #[test]
1040 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1041 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1042 let config = make_trait_bridge_config(Some("Plugin"), None);
1043 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1044 let generator = MockBridgeGenerator;
1045 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1046 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1047 }
1048
1049 #[test]
1054 fn test_gen_bridge_trait_impl_includes_impl_header() {
1055 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1056 let config = make_trait_bridge_config(None, None);
1057 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1058 let generator = MockBridgeGenerator;
1059 let result = gen_bridge_trait_impl(&spec, &generator);
1060 assert!(
1061 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1062 "missing impl header in:\n{result}"
1063 );
1064 }
1065
1066 #[test]
1067 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1068 let methods = vec![make_method(
1069 "process",
1070 vec![],
1071 TypeRef::String,
1072 false,
1073 false,
1074 None,
1075 None,
1076 )];
1077 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1078 let config = make_trait_bridge_config(None, None);
1079 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1080 let generator = MockBridgeGenerator;
1081 let result = gen_bridge_trait_impl(&spec, &generator);
1082 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1083 }
1084
1085 #[test]
1086 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1087 let methods = vec![make_method(
1088 "process",
1089 vec![],
1090 TypeRef::String,
1091 false,
1092 false,
1093 None,
1094 None,
1095 )];
1096 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1097 let config = make_trait_bridge_config(None, None);
1098 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1099 let generator = MockBridgeGenerator;
1100 let result = gen_bridge_trait_impl(&spec, &generator);
1101 assert!(
1102 result.contains("// sync body for process"),
1103 "missing sync method body in:\n{result}"
1104 );
1105 }
1106
1107 #[test]
1108 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1109 let methods = vec![make_method(
1110 "process_async",
1111 vec![],
1112 TypeRef::String,
1113 true,
1114 false,
1115 None,
1116 None,
1117 )];
1118 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1119 let config = make_trait_bridge_config(None, None);
1120 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1121 let generator = MockBridgeGenerator;
1122 let result = gen_bridge_trait_impl(&spec, &generator);
1123 assert!(
1124 result.contains("// async body for process_async"),
1125 "missing async method body in:\n{result}"
1126 );
1127 assert!(
1128 result.contains("async fn process_async("),
1129 "missing async keyword in method signature in:\n{result}"
1130 );
1131 }
1132
1133 #[test]
1134 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1135 let methods = vec![
1137 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1138 make_method(
1139 "inherited_method",
1140 vec![],
1141 TypeRef::String,
1142 false,
1143 false,
1144 Some("other_crate::OtherTrait"),
1145 None,
1146 ),
1147 ];
1148 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1149 let config = make_trait_bridge_config(None, None);
1150 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1151 let generator = MockBridgeGenerator;
1152 let result = gen_bridge_trait_impl(&spec, &generator);
1153 assert!(
1154 result.contains("fn own_method("),
1155 "own method should be present in:\n{result}"
1156 );
1157 assert!(
1158 !result.contains("fn inherited_method("),
1159 "inherited method should be filtered out in:\n{result}"
1160 );
1161 }
1162
1163 #[test]
1164 fn test_gen_bridge_trait_impl_method_with_params() {
1165 let params = vec![
1166 make_param("input", TypeRef::String, true),
1167 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1168 ];
1169 let methods = vec![make_method(
1170 "process",
1171 params,
1172 TypeRef::String,
1173 false,
1174 false,
1175 None,
1176 None,
1177 )];
1178 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1179 let config = make_trait_bridge_config(None, None);
1180 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1181 let generator = MockBridgeGenerator;
1182 let result = gen_bridge_trait_impl(&spec, &generator);
1183 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1184 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1185 }
1186
1187 #[test]
1188 fn test_gen_bridge_trait_impl_return_type_with_error() {
1189 let methods = vec![make_method(
1190 "process",
1191 vec![],
1192 TypeRef::String,
1193 false,
1194 false,
1195 None,
1196 Some("MyError"),
1197 )];
1198 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1199 let config = make_trait_bridge_config(None, None);
1200 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1201 let generator = MockBridgeGenerator;
1202 let result = gen_bridge_trait_impl(&spec, &generator);
1203 assert!(
1204 result.contains("-> Result<String, MyError>"),
1205 "missing Result return type in:\n{result}"
1206 );
1207 }
1208
1209 #[test]
1214 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1215 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1216 let config = make_trait_bridge_config(None, None);
1217 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1218 let generator = MockBridgeGenerator;
1219 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1220 }
1221
1222 #[test]
1223 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1224 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1225 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1226 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1227 let generator = MockBridgeGenerator;
1228 let result = gen_bridge_registration_fn(&spec, &generator);
1229 assert!(result.is_some());
1230 let code = result.unwrap();
1231 assert!(
1232 code.contains("register_ocr_backend"),
1233 "missing register fn name in:\n{code}"
1234 );
1235 }
1236
1237 #[test]
1242 fn test_gen_bridge_all_includes_imports() {
1243 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1244 let config = make_trait_bridge_config(None, None);
1245 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1246 let generator = MockBridgeGenerator;
1247 let output = gen_bridge_all(&spec, &generator);
1248 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1249 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1250 }
1251
1252 #[test]
1253 fn test_gen_bridge_all_includes_wrapper_struct() {
1254 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1255 let config = make_trait_bridge_config(None, None);
1256 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1257 let generator = MockBridgeGenerator;
1258 let output = gen_bridge_all(&spec, &generator);
1259 assert!(
1260 output.code.contains("pub struct PyOcrBackendBridge"),
1261 "missing struct in:\n{}",
1262 output.code
1263 );
1264 }
1265
1266 #[test]
1267 fn test_gen_bridge_all_includes_constructor() {
1268 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1269 let config = make_trait_bridge_config(None, None);
1270 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1271 let generator = MockBridgeGenerator;
1272 let output = gen_bridge_all(&spec, &generator);
1273 assert!(
1274 output.code.contains("pub fn new("),
1275 "missing constructor in:\n{}",
1276 output.code
1277 );
1278 }
1279
1280 #[test]
1281 fn test_gen_bridge_all_includes_trait_impl() {
1282 let methods = vec![make_method(
1283 "process",
1284 vec![],
1285 TypeRef::String,
1286 false,
1287 false,
1288 None,
1289 None,
1290 )];
1291 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1292 let config = make_trait_bridge_config(None, None);
1293 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1294 let generator = MockBridgeGenerator;
1295 let output = gen_bridge_all(&spec, &generator);
1296 assert!(
1297 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1298 "missing trait impl in:\n{}",
1299 output.code
1300 );
1301 }
1302
1303 #[test]
1304 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1305 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1306 let config = make_trait_bridge_config(Some("Plugin"), None);
1307 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1308 let generator = MockBridgeGenerator;
1309 let output = gen_bridge_all(&spec, &generator);
1310 assert!(
1311 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1312 "missing plugin impl in:\n{}",
1313 output.code
1314 );
1315 }
1316
1317 #[test]
1318 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1319 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1320 let config = make_trait_bridge_config(None, None);
1321 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1322 let generator = MockBridgeGenerator;
1323 let output = gen_bridge_all(&spec, &generator);
1324 assert!(
1325 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1326 "unexpected plugin impl present without super_trait"
1327 );
1328 }
1329
1330 #[test]
1331 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1332 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1333 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1334 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1335 let generator = MockBridgeGenerator;
1336 let output = gen_bridge_all(&spec, &generator);
1337 assert!(
1338 output.code.contains("register_ocr_backend"),
1339 "missing registration fn in:\n{}",
1340 output.code
1341 );
1342 }
1343
1344 #[test]
1345 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1346 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1347 let config = make_trait_bridge_config(None, None);
1348 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1349 let generator = MockBridgeGenerator;
1350 let output = gen_bridge_all(&spec, &generator);
1351 assert!(
1352 !output.code.contains("register_ocr_backend"),
1353 "unexpected registration fn present:\n{}",
1354 output.code
1355 );
1356 }
1357
1358 #[test]
1359 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1360 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1361 let config = make_trait_bridge_config(None, None);
1362 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1363 let generator = MockBridgeGenerator;
1364 let output = gen_bridge_all(&spec, &generator);
1365 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1366 let impl_pos = output
1367 .code
1368 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1369 .unwrap();
1370 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1371 }
1372}