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