1use alef_core::config::TraitBridgeConfig;
9use alef_core::ir::{FunctionDef, MethodDef, ParamDef, PrimitiveType, TypeDef, TypeRef};
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 {
40 if self.error_type.contains("::") || self.error_type.contains('<') {
41 self.error_type.clone()
42 } else {
43 format!("{}::{}", self.core_import, self.error_type)
44 }
45 }
46
47 pub fn make_error(&self, msg_expr: &str) -> String {
49 self.error_constructor.replace("{msg}", msg_expr)
50 }
51
52 pub fn wrapper_name(&self) -> String {
54 format!("{}{}Bridge", self.wrapper_prefix, self.trait_def.name)
55 }
56
57 pub fn trait_snake(&self) -> String {
59 self.trait_def.name.to_snake_case()
60 }
61
62 pub fn trait_path(&self) -> String {
64 self.trait_def.rust_path.replace('-', "_")
65 }
66
67 pub fn required_methods(&self) -> Vec<&'a MethodDef> {
69 self.trait_def.methods.iter().filter(|m| !m.has_default_impl).collect()
70 }
71
72 pub fn optional_methods(&self) -> Vec<&'a MethodDef> {
74 self.trait_def.methods.iter().filter(|m| m.has_default_impl).collect()
75 }
76}
77
78pub trait TraitBridgeGenerator {
84 fn foreign_object_type(&self) -> &str;
86
87 fn bridge_imports(&self) -> Vec<String>;
89
90 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
95
96 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
100
101 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String;
106
107 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String;
113}
114
115pub fn gen_bridge_wrapper_struct(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
129 let wrapper = spec.wrapper_name();
130 let foreign_type = generator.foreign_object_type();
131 let mut out = String::with_capacity(512);
132
133 writeln!(
134 out,
135 "/// Wrapper that bridges a foreign {prefix} object to the `{trait_name}` trait.",
136 prefix = spec.wrapper_prefix,
137 trait_name = spec.trait_def.name,
138 )
139 .ok();
140 writeln!(out, "pub struct {wrapper} {{").ok();
141 writeln!(out, " inner: {foreign_type},").ok();
142 writeln!(out, " cached_name: String,").ok();
143 write!(out, "}}").ok();
144 out
145}
146
147pub fn gen_bridge_plugin_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
155 let super_trait_name = spec.bridge_config.super_trait.as_deref()?;
156
157 let wrapper = spec.wrapper_name();
158 let core_import = spec.core_import;
159
160 let super_trait_path = if super_trait_name.contains("::") {
162 super_trait_name.to_string()
163 } else {
164 format!("{core_import}::{super_trait_name}")
165 };
166
167 let mut out = String::with_capacity(1024);
171 writeln!(out, "impl {super_trait_path} for {wrapper} {{").ok();
172
173 writeln!(out, " fn name(&self) -> &str {{").ok();
175 writeln!(out, " &self.cached_name").ok();
176 writeln!(out, " }}").ok();
177 writeln!(out).ok();
178
179 let error_path = spec.error_path();
180
181 writeln!(out, " fn version(&self) -> String {{").ok();
183 let version_method = MethodDef {
184 name: "version".to_string(),
185 params: vec![],
186 return_type: alef_core::ir::TypeRef::String,
187 is_async: false,
188 is_static: false,
189 error_type: None,
190 doc: String::new(),
191 receiver: Some(alef_core::ir::ReceiverKind::Ref),
192 sanitized: false,
193 trait_source: None,
194 returns_ref: false,
195 returns_cow: false,
196 return_newtype_wrapper: None,
197 has_default_impl: false,
198 };
199 let version_body = generator.gen_sync_method_body(&version_method, spec);
200 for line in version_body.lines() {
201 writeln!(out, " {}", line.trim_start()).ok();
202 }
203 writeln!(out, " }}").ok();
204 writeln!(out).ok();
205
206 writeln!(
208 out,
209 " fn initialize(&self) -> std::result::Result<(), {error_path}> {{"
210 )
211 .ok();
212 let init_method = MethodDef {
213 name: "initialize".to_string(),
214 params: vec![],
215 return_type: alef_core::ir::TypeRef::Unit,
216 is_async: false,
217 is_static: false,
218 error_type: Some(error_path.clone()),
219 doc: String::new(),
220 receiver: Some(alef_core::ir::ReceiverKind::Ref),
221 sanitized: false,
222 trait_source: None,
223 returns_ref: false,
224 returns_cow: false,
225 return_newtype_wrapper: None,
226 has_default_impl: true,
227 };
228 let init_body = generator.gen_sync_method_body(&init_method, spec);
229 for line in init_body.lines() {
230 writeln!(out, " {}", line.trim_start()).ok();
231 }
232 writeln!(out, " }}").ok();
233 writeln!(out).ok();
234
235 writeln!(
237 out,
238 " fn shutdown(&self) -> std::result::Result<(), {error_path}> {{"
239 )
240 .ok();
241 let shutdown_method = MethodDef {
242 name: "shutdown".to_string(),
243 params: vec![],
244 return_type: alef_core::ir::TypeRef::Unit,
245 is_async: false,
246 is_static: false,
247 error_type: Some(error_path.clone()),
248 doc: String::new(),
249 receiver: Some(alef_core::ir::ReceiverKind::Ref),
250 sanitized: false,
251 trait_source: None,
252 returns_ref: false,
253 returns_cow: false,
254 return_newtype_wrapper: None,
255 has_default_impl: true,
256 };
257 let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
258 for line in shutdown_body.lines() {
259 writeln!(out, " {}", line.trim_start()).ok();
260 }
261 writeln!(out, " }}").ok();
262 write!(out, "}}").ok();
263 Some(out)
264}
265
266pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
271 let wrapper = spec.wrapper_name();
272 let trait_path = spec.trait_path();
273 let mut out = String::with_capacity(2048);
274
275 let has_async_methods = spec
277 .trait_def
278 .methods
279 .iter()
280 .any(|m| m.is_async && m.trait_source.is_none());
281 if has_async_methods {
282 writeln!(out, "#[async_trait::async_trait]").ok();
283 }
284 writeln!(out, "impl {trait_path} for {wrapper} {{").ok();
285
286 let own_methods: Vec<_> = spec
288 .trait_def
289 .methods
290 .iter()
291 .filter(|m| m.trait_source.is_none())
292 .collect();
293
294 for (i, method) in own_methods.iter().enumerate() {
295 if i > 0 {
296 writeln!(out).ok();
297 }
298
299 let async_kw = if method.is_async { "async " } else { "" };
301 let receiver = match &method.receiver {
302 Some(alef_core::ir::ReceiverKind::Ref) => "&self",
303 Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
304 Some(alef_core::ir::ReceiverKind::Owned) => "self",
305 None => "",
306 };
307
308 let params: Vec<String> = method
310 .params
311 .iter()
312 .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
313 .collect();
314
315 let all_params = if receiver.is_empty() {
316 params.join(", ")
317 } else if params.is_empty() {
318 receiver.to_string()
319 } else {
320 format!("{}, {}", receiver, params.join(", "))
321 };
322
323 let error_override = method.error_type.as_ref().map(|_| spec.error_path());
327 let ret = format_return_type(&method.return_type, error_override.as_deref(), &spec.type_paths);
328
329 writeln!(out, " {async_kw}fn {}({all_params}) -> {ret} {{", method.name).ok();
330
331 let body = if method.is_async {
333 generator.gen_async_method_body(method, spec)
334 } else {
335 generator.gen_sync_method_body(method, spec)
336 };
337
338 for line in body.lines() {
339 writeln!(out, " {line}").ok();
340 }
341 writeln!(out, " }}").ok();
342 }
343
344 write!(out, "}}").ok();
345 out
346}
347
348pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
355 spec.bridge_config.register_fn.as_deref()?;
356 Some(generator.gen_registration_fn(spec))
357}
358
359pub struct BridgeOutput {
362 pub imports: Vec<String>,
364 pub code: String,
366}
367
368pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
374 let imports = generator.bridge_imports();
375 let mut out = String::with_capacity(4096);
376
377 out.push_str(&gen_bridge_wrapper_struct(spec, generator));
379 writeln!(out).ok();
380 writeln!(out).ok();
381
382 out.push_str(&generator.gen_constructor(spec));
384 writeln!(out).ok();
385 writeln!(out).ok();
386
387 if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
389 out.push_str(&plugin_impl);
390 writeln!(out).ok();
391 writeln!(out).ok();
392 }
393
394 out.push_str(&gen_bridge_trait_impl(spec, generator));
396
397 if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
399 writeln!(out).ok();
400 writeln!(out).ok();
401 out.push_str(®_fn_code);
402 }
403
404 BridgeOutput { imports, code: out }
405}
406
407pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
416 use alef_core::ir::{PrimitiveType, TypeRef};
417 match ty {
418 TypeRef::Primitive(p) => match p {
419 PrimitiveType::Bool => "bool",
420 PrimitiveType::U8 => "u8",
421 PrimitiveType::U16 => "u16",
422 PrimitiveType::U32 => "u32",
423 PrimitiveType::U64 => "u64",
424 PrimitiveType::I8 => "i8",
425 PrimitiveType::I16 => "i16",
426 PrimitiveType::I32 => "i32",
427 PrimitiveType::I64 => "i64",
428 PrimitiveType::F32 => "f32",
429 PrimitiveType::F64 => "f64",
430 PrimitiveType::Usize => "usize",
431 PrimitiveType::Isize => "isize",
432 }
433 .to_string(),
434 TypeRef::String => "String".to_string(),
435 TypeRef::Char => "char".to_string(),
436 TypeRef::Bytes => "Vec<u8>".to_string(),
437 TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
438 TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
439 TypeRef::Map(k, v) => format!(
440 "std::collections::HashMap<{}, {}>",
441 format_type_ref(k, type_paths),
442 format_type_ref(v, type_paths)
443 ),
444 TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
445 TypeRef::Path => "std::path::PathBuf".to_string(),
446 TypeRef::Unit => "()".to_string(),
447 TypeRef::Json => "serde_json::Value".to_string(),
448 TypeRef::Duration => "std::time::Duration".to_string(),
449 }
450}
451
452pub fn format_return_type(
454 ty: &alef_core::ir::TypeRef,
455 error_type: Option<&str>,
456 type_paths: &HashMap<String, String>,
457) -> String {
458 let inner = format_type_ref(ty, type_paths);
459 match error_type {
460 Some(err) => format!("std::result::Result<{inner}, {err}>"),
461 None => inner,
462 }
463}
464
465pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
477 use alef_core::ir::TypeRef;
478 let base = if param.is_ref {
479 let mutability = if param.is_mut { "mut " } else { "" };
480 match ¶m.ty {
481 TypeRef::String => format!("&{mutability}str"),
482 TypeRef::Bytes => format!("&{mutability}[u8]"),
483 TypeRef::Path => format!("&{mutability}std::path::Path"),
484 TypeRef::Vec(inner) => format!("&{mutability}[{}]", format_type_ref(inner, type_paths)),
485 TypeRef::Named(name) => {
486 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
487 format!("&{mutability}{qualified}")
488 }
489 TypeRef::Optional(inner) => {
490 let inner_type_str = match inner.as_ref() {
494 TypeRef::String => format!("&{mutability}str"),
495 TypeRef::Bytes => format!("&{mutability}[u8]"),
496 TypeRef::Path => format!("&{mutability}std::path::Path"),
497 TypeRef::Vec(v) => format!("&{mutability}[{}]", format_type_ref(v, type_paths)),
498 TypeRef::Named(name) => {
499 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
500 format!("&{mutability}{qualified}")
501 }
502 other => format_type_ref(other, type_paths),
504 };
505 return format!("Option<{inner_type_str}>");
507 }
508 other => format_type_ref(other, type_paths),
510 }
511 } else {
512 format_type_ref(¶m.ty, type_paths)
513 };
514
515 if param.optional {
519 format!("Option<{base}>")
520 } else {
521 base
522 }
523}
524
525pub fn prim(p: &PrimitiveType) -> &'static str {
531 use PrimitiveType::*;
532 match p {
533 Bool => "bool",
534 U8 => "u8",
535 U16 => "u16",
536 U32 => "u32",
537 U64 => "u64",
538 I8 => "i8",
539 I16 => "i16",
540 I32 => "i32",
541 I64 => "i64",
542 F32 => "f32",
543 F64 => "f64",
544 Usize => "usize",
545 Isize => "isize",
546 }
547}
548
549pub fn bridge_param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
553 match ty {
554 TypeRef::Bytes if is_ref => "&[u8]".into(),
555 TypeRef::Bytes => "Vec<u8>".into(),
556 TypeRef::String if is_ref => "&str".into(),
557 TypeRef::String => "String".into(),
558 TypeRef::Path if is_ref => "&std::path::Path".into(),
559 TypeRef::Path => "std::path::PathBuf".into(),
560 TypeRef::Named(n) => {
561 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
562 if is_ref { format!("&{qualified}") } else { qualified }
563 }
564 TypeRef::Vec(inner) => format!("Vec<{}>", bridge_param_type(inner, ci, false, tp)),
565 TypeRef::Optional(inner) => format!("Option<{}>", bridge_param_type(inner, ci, false, tp)),
566 TypeRef::Primitive(p) => prim(p).into(),
567 TypeRef::Unit => "()".into(),
568 TypeRef::Char => "char".into(),
569 TypeRef::Map(k, v) => format!(
570 "std::collections::HashMap<{}, {}>",
571 bridge_param_type(k, ci, false, tp),
572 bridge_param_type(v, ci, false, tp)
573 ),
574 TypeRef::Json => "serde_json::Value".into(),
575 TypeRef::Duration => "std::time::Duration".into(),
576 }
577}
578
579pub fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
585 if optional && matches!(ty, TypeRef::String) && is_ref {
586 return "Option<&str>".to_string();
587 }
588 if is_ref {
589 if let TypeRef::Vec(inner) = ty {
590 let inner_str = bridge_param_type(inner, "", false, tp);
591 return format!("&[{inner_str}]");
592 }
593 }
594 bridge_param_type(ty, "", is_ref, tp)
595}
596
597pub fn find_bridge_param<'a>(
600 func: &FunctionDef,
601 bridges: &'a [TraitBridgeConfig],
602) -> Option<(usize, &'a TraitBridgeConfig)> {
603 for (idx, param) in func.params.iter().enumerate() {
604 let named = match ¶m.ty {
605 TypeRef::Named(n) => Some(n.as_str()),
606 TypeRef::Optional(inner) => {
607 if let TypeRef::Named(n) = inner.as_ref() {
608 Some(n.as_str())
609 } else {
610 None
611 }
612 }
613 _ => None,
614 };
615 for bridge in bridges {
616 if let Some(type_name) = named {
617 if bridge.type_alias.as_deref() == Some(type_name) {
618 return Some((idx, bridge));
619 }
620 }
621 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
622 return Some((idx, bridge));
623 }
624 }
625 }
626 None
627}
628
629pub fn to_camel_case(s: &str) -> String {
631 let mut result = String::new();
632 let mut capitalize_next = false;
633 for ch in s.chars() {
634 if ch == '_' {
635 capitalize_next = true;
636 } else if capitalize_next {
637 result.push(ch.to_ascii_uppercase());
638 capitalize_next = false;
639 } else {
640 result.push(ch);
641 }
642 }
643 result
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649 use alef_core::config::TraitBridgeConfig;
650 use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
651
652 fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
657 TraitBridgeConfig {
658 trait_name: "OcrBackend".to_string(),
659 super_trait: super_trait.map(str::to_string),
660 registry_getter: None,
661 register_fn: register_fn.map(str::to_string),
662 type_alias: None,
663 param_name: None,
664 register_extra_args: None,
665 exclude_languages: Vec::new(),
666 }
667 }
668
669 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
670 TypeDef {
671 name: name.to_string(),
672 rust_path: rust_path.to_string(),
673 original_rust_path: rust_path.to_string(),
674 fields: vec![],
675 methods,
676 is_opaque: true,
677 is_clone: false,
678 doc: String::new(),
679 cfg: None,
680 is_trait: true,
681 has_default: false,
682 has_stripped_cfg_fields: false,
683 is_return_type: false,
684 serde_rename_all: None,
685 has_serde: false,
686 super_traits: vec![],
687 }
688 }
689
690 fn make_method(
691 name: &str,
692 params: Vec<ParamDef>,
693 return_type: TypeRef,
694 is_async: bool,
695 has_default_impl: bool,
696 trait_source: Option<&str>,
697 error_type: Option<&str>,
698 ) -> MethodDef {
699 MethodDef {
700 name: name.to_string(),
701 params,
702 return_type,
703 is_async,
704 is_static: false,
705 error_type: error_type.map(str::to_string),
706 doc: String::new(),
707 receiver: Some(ReceiverKind::Ref),
708 sanitized: false,
709 trait_source: trait_source.map(str::to_string),
710 returns_ref: false,
711 returns_cow: false,
712 return_newtype_wrapper: None,
713 has_default_impl,
714 }
715 }
716
717 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
718 ParamDef {
719 name: name.to_string(),
720 ty,
721 optional: false,
722 default: None,
723 sanitized: false,
724 typed_default: None,
725 is_ref,
726 is_mut: false,
727 newtype_wrapper: None,
728 original_type: None,
729 }
730 }
731
732 fn make_spec<'a>(
733 trait_def: &'a TypeDef,
734 bridge_config: &'a TraitBridgeConfig,
735 wrapper_prefix: &'a str,
736 type_paths: HashMap<String, String>,
737 ) -> TraitBridgeSpec<'a> {
738 TraitBridgeSpec {
739 trait_def,
740 bridge_config,
741 core_import: "mylib",
742 wrapper_prefix,
743 type_paths,
744 error_type: "MyError".to_string(),
745 error_constructor: "MyError::from({msg})".to_string(),
746 }
747 }
748
749 struct MockBridgeGenerator;
754
755 impl TraitBridgeGenerator for MockBridgeGenerator {
756 fn foreign_object_type(&self) -> &str {
757 "Py<PyAny>"
758 }
759
760 fn bridge_imports(&self) -> Vec<String> {
761 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
762 }
763
764 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
765 format!("// sync body for {}", method.name)
766 }
767
768 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
769 format!("// async body for {}", method.name)
770 }
771
772 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
773 format!(
774 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
775 spec.wrapper_name()
776 )
777 }
778
779 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
780 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
781 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
782 }
783 }
784
785 #[test]
790 fn test_wrapper_name() {
791 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
792 let config = make_trait_bridge_config(None, None);
793 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
794 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
795 }
796
797 #[test]
798 fn test_trait_snake() {
799 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
800 let config = make_trait_bridge_config(None, None);
801 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
802 assert_eq!(spec.trait_snake(), "ocr_backend");
803 }
804
805 #[test]
806 fn test_trait_path_replaces_hyphens() {
807 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
808 let config = make_trait_bridge_config(None, None);
809 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
810 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
811 }
812
813 #[test]
814 fn test_required_methods_filters_no_default_impl() {
815 let methods = vec![
816 make_method("process", vec![], TypeRef::String, false, false, None, None),
817 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
818 make_method("detect", vec![], TypeRef::String, false, false, None, None),
819 ];
820 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
821 let config = make_trait_bridge_config(None, None);
822 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
823 let required = spec.required_methods();
824 assert_eq!(required.len(), 2);
825 assert!(required.iter().any(|m| m.name == "process"));
826 assert!(required.iter().any(|m| m.name == "detect"));
827 }
828
829 #[test]
830 fn test_optional_methods_filters_has_default_impl() {
831 let methods = vec![
832 make_method("process", vec![], TypeRef::String, false, false, None, None),
833 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
834 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
835 ];
836 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
837 let config = make_trait_bridge_config(None, None);
838 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
839 let optional = spec.optional_methods();
840 assert_eq!(optional.len(), 2);
841 assert!(optional.iter().any(|m| m.name == "initialize"));
842 assert!(optional.iter().any(|m| m.name == "shutdown"));
843 }
844
845 #[test]
846 fn test_error_path() {
847 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
848 let config = make_trait_bridge_config(None, None);
849 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
850 assert_eq!(spec.error_path(), "mylib::MyError");
851 }
852
853 #[test]
858 fn test_format_type_ref_primitives() {
859 let paths = HashMap::new();
860 let cases: Vec<(TypeRef, &str)> = vec![
861 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
862 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
863 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
864 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
865 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
866 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
867 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
868 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
869 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
870 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
871 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
872 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
873 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
874 ];
875 for (ty, expected) in cases {
876 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
877 }
878 }
879
880 #[test]
881 fn test_format_type_ref_string() {
882 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
883 }
884
885 #[test]
886 fn test_format_type_ref_char() {
887 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
888 }
889
890 #[test]
891 fn test_format_type_ref_bytes() {
892 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
893 }
894
895 #[test]
896 fn test_format_type_ref_path() {
897 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
898 }
899
900 #[test]
901 fn test_format_type_ref_unit() {
902 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
903 }
904
905 #[test]
906 fn test_format_type_ref_json() {
907 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
908 }
909
910 #[test]
911 fn test_format_type_ref_duration() {
912 assert_eq!(
913 format_type_ref(&TypeRef::Duration, &HashMap::new()),
914 "std::time::Duration"
915 );
916 }
917
918 #[test]
919 fn test_format_type_ref_optional() {
920 let ty = TypeRef::Optional(Box::new(TypeRef::String));
921 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
922 }
923
924 #[test]
925 fn test_format_type_ref_optional_nested() {
926 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
927 PrimitiveType::U32,
928 )))));
929 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
930 }
931
932 #[test]
933 fn test_format_type_ref_vec() {
934 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
935 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
936 }
937
938 #[test]
939 fn test_format_type_ref_vec_nested() {
940 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
941 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
942 }
943
944 #[test]
945 fn test_format_type_ref_map() {
946 let ty = TypeRef::Map(
947 Box::new(TypeRef::String),
948 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
949 );
950 assert_eq!(
951 format_type_ref(&ty, &HashMap::new()),
952 "std::collections::HashMap<String, i64>"
953 );
954 }
955
956 #[test]
957 fn test_format_type_ref_map_nested_value() {
958 let ty = TypeRef::Map(
959 Box::new(TypeRef::String),
960 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
961 );
962 assert_eq!(
963 format_type_ref(&ty, &HashMap::new()),
964 "std::collections::HashMap<String, Vec<String>>"
965 );
966 }
967
968 #[test]
969 fn test_format_type_ref_named_without_type_paths() {
970 let ty = TypeRef::Named("Config".to_string());
971 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
972 }
973
974 #[test]
975 fn test_format_type_ref_named_with_type_paths() {
976 let ty = TypeRef::Named("Config".to_string());
977 let mut paths = HashMap::new();
978 paths.insert("Config".to_string(), "mylib::Config".to_string());
979 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
980 }
981
982 #[test]
983 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
984 let ty = TypeRef::Named("Unknown".to_string());
985 let mut paths = HashMap::new();
986 paths.insert("Other".to_string(), "mylib::Other".to_string());
987 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
988 }
989
990 #[test]
995 fn test_format_param_type_string_ref() {
996 let param = make_param("input", TypeRef::String, true);
997 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
998 }
999
1000 #[test]
1001 fn test_format_param_type_string_owned() {
1002 let param = make_param("input", TypeRef::String, false);
1003 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
1004 }
1005
1006 #[test]
1007 fn test_format_param_type_bytes_ref() {
1008 let param = make_param("data", TypeRef::Bytes, true);
1009 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
1010 }
1011
1012 #[test]
1013 fn test_format_param_type_bytes_owned() {
1014 let param = make_param("data", TypeRef::Bytes, false);
1015 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
1016 }
1017
1018 #[test]
1019 fn test_format_param_type_path_ref() {
1020 let param = make_param("path", TypeRef::Path, true);
1021 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
1022 }
1023
1024 #[test]
1025 fn test_format_param_type_path_owned() {
1026 let param = make_param("path", TypeRef::Path, false);
1027 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
1028 }
1029
1030 #[test]
1031 fn test_format_param_type_vec_ref() {
1032 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
1033 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
1034 }
1035
1036 #[test]
1037 fn test_format_param_type_vec_owned() {
1038 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
1039 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
1040 }
1041
1042 #[test]
1043 fn test_format_param_type_named_ref_with_type_paths() {
1044 let mut paths = HashMap::new();
1045 paths.insert("Config".to_string(), "mylib::Config".to_string());
1046 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1047 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
1048 }
1049
1050 #[test]
1051 fn test_format_param_type_named_ref_without_type_paths() {
1052 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1053 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
1054 }
1055
1056 #[test]
1057 fn test_format_param_type_primitive_ref_passes_by_value() {
1058 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
1060 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
1061 }
1062
1063 #[test]
1064 fn test_format_param_type_unit_ref_passes_by_value() {
1065 let param = make_param("nothing", TypeRef::Unit, true);
1066 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
1067 }
1068
1069 #[test]
1074 fn test_format_return_type_without_error() {
1075 let result = format_return_type(&TypeRef::String, None, &HashMap::new());
1076 assert_eq!(result, "String");
1077 }
1078
1079 #[test]
1080 fn test_format_return_type_with_error() {
1081 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new());
1082 assert_eq!(result, "std::result::Result<String, MyError>");
1083 }
1084
1085 #[test]
1086 fn test_format_return_type_unit_with_error() {
1087 let result = format_return_type(&TypeRef::Unit, Some("Box<dyn std::error::Error>"), &HashMap::new());
1088 assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
1089 }
1090
1091 #[test]
1092 fn test_format_return_type_named_with_type_paths_and_error() {
1093 let mut paths = HashMap::new();
1094 paths.insert("Output".to_string(), "mylib::Output".to_string());
1095 let result = format_return_type(&TypeRef::Named("Output".to_string()), Some("mylib::MyError"), &paths);
1096 assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
1097 }
1098
1099 #[test]
1104 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
1105 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1106 let config = make_trait_bridge_config(None, None);
1107 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1108 let generator = MockBridgeGenerator;
1109 let result = gen_bridge_wrapper_struct(&spec, &generator);
1110 assert!(
1111 result.contains("pub struct PyOcrBackendBridge"),
1112 "missing struct declaration in:\n{result}"
1113 );
1114 }
1115
1116 #[test]
1117 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
1118 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
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_wrapper_struct(&spec, &generator);
1123 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
1124 }
1125
1126 #[test]
1127 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
1128 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1129 let config = make_trait_bridge_config(None, None);
1130 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1131 let generator = MockBridgeGenerator;
1132 let result = gen_bridge_wrapper_struct(&spec, &generator);
1133 assert!(
1134 result.contains("cached_name: String"),
1135 "missing cached_name field in:\n{result}"
1136 );
1137 }
1138
1139 #[test]
1144 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1145 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1146 let config = make_trait_bridge_config(None, None);
1147 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1148 let generator = MockBridgeGenerator;
1149 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1150 }
1151
1152 #[test]
1153 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1154 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1155 let config = make_trait_bridge_config(Some("Plugin"), None);
1156 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1157 let generator = MockBridgeGenerator;
1158 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1159 }
1160
1161 #[test]
1162 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1163 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1164 let config = make_trait_bridge_config(Some("Plugin"), None);
1165 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1166 let generator = MockBridgeGenerator;
1167 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1168 assert!(
1169 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1170 "missing qualified super-trait path in:\n{result}"
1171 );
1172 }
1173
1174 #[test]
1175 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1176 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1177 let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
1178 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1179 let generator = MockBridgeGenerator;
1180 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1181 assert!(
1182 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1183 "wrong super-trait path in:\n{result}"
1184 );
1185 }
1186
1187 #[test]
1188 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1189 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1190 let config = make_trait_bridge_config(Some("Plugin"), None);
1191 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1192 let generator = MockBridgeGenerator;
1193 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1194 assert!(
1195 result.contains("fn name(") && result.contains("cached_name"),
1196 "missing name() using cached_name in:\n{result}"
1197 );
1198 }
1199
1200 #[test]
1201 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1202 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1203 let config = make_trait_bridge_config(Some("Plugin"), None);
1204 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1205 let generator = MockBridgeGenerator;
1206 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1207 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1208 }
1209
1210 #[test]
1211 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1212 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1213 let config = make_trait_bridge_config(Some("Plugin"), None);
1214 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1215 let generator = MockBridgeGenerator;
1216 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1217 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1218 }
1219
1220 #[test]
1221 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1222 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1223 let config = make_trait_bridge_config(Some("Plugin"), None);
1224 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1225 let generator = MockBridgeGenerator;
1226 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1227 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1228 }
1229
1230 #[test]
1235 fn test_gen_bridge_trait_impl_includes_impl_header() {
1236 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1237 let config = make_trait_bridge_config(None, None);
1238 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1239 let generator = MockBridgeGenerator;
1240 let result = gen_bridge_trait_impl(&spec, &generator);
1241 assert!(
1242 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1243 "missing impl header in:\n{result}"
1244 );
1245 }
1246
1247 #[test]
1248 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1249 let methods = vec![make_method(
1250 "process",
1251 vec![],
1252 TypeRef::String,
1253 false,
1254 false,
1255 None,
1256 None,
1257 )];
1258 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1259 let config = make_trait_bridge_config(None, None);
1260 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1261 let generator = MockBridgeGenerator;
1262 let result = gen_bridge_trait_impl(&spec, &generator);
1263 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1264 }
1265
1266 #[test]
1267 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1268 let methods = vec![make_method(
1269 "process",
1270 vec![],
1271 TypeRef::String,
1272 false,
1273 false,
1274 None,
1275 None,
1276 )];
1277 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1278 let config = make_trait_bridge_config(None, None);
1279 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1280 let generator = MockBridgeGenerator;
1281 let result = gen_bridge_trait_impl(&spec, &generator);
1282 assert!(
1283 result.contains("// sync body for process"),
1284 "missing sync method body in:\n{result}"
1285 );
1286 }
1287
1288 #[test]
1289 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1290 let methods = vec![make_method(
1291 "process_async",
1292 vec![],
1293 TypeRef::String,
1294 true,
1295 false,
1296 None,
1297 None,
1298 )];
1299 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1300 let config = make_trait_bridge_config(None, None);
1301 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1302 let generator = MockBridgeGenerator;
1303 let result = gen_bridge_trait_impl(&spec, &generator);
1304 assert!(
1305 result.contains("// async body for process_async"),
1306 "missing async method body in:\n{result}"
1307 );
1308 assert!(
1309 result.contains("async fn process_async("),
1310 "missing async keyword in method signature in:\n{result}"
1311 );
1312 }
1313
1314 #[test]
1315 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1316 let methods = vec![
1318 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1319 make_method(
1320 "inherited_method",
1321 vec![],
1322 TypeRef::String,
1323 false,
1324 false,
1325 Some("other_crate::OtherTrait"),
1326 None,
1327 ),
1328 ];
1329 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1330 let config = make_trait_bridge_config(None, None);
1331 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1332 let generator = MockBridgeGenerator;
1333 let result = gen_bridge_trait_impl(&spec, &generator);
1334 assert!(
1335 result.contains("fn own_method("),
1336 "own method should be present in:\n{result}"
1337 );
1338 assert!(
1339 !result.contains("fn inherited_method("),
1340 "inherited method should be filtered out in:\n{result}"
1341 );
1342 }
1343
1344 #[test]
1345 fn test_gen_bridge_trait_impl_method_with_params() {
1346 let params = vec![
1347 make_param("input", TypeRef::String, true),
1348 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1349 ];
1350 let methods = vec![make_method(
1351 "process",
1352 params,
1353 TypeRef::String,
1354 false,
1355 false,
1356 None,
1357 None,
1358 )];
1359 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1360 let config = make_trait_bridge_config(None, None);
1361 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1362 let generator = MockBridgeGenerator;
1363 let result = gen_bridge_trait_impl(&spec, &generator);
1364 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1365 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1366 }
1367
1368 #[test]
1369 fn test_gen_bridge_trait_impl_return_type_with_error() {
1370 let methods = vec![make_method(
1371 "process",
1372 vec![],
1373 TypeRef::String,
1374 false,
1375 false,
1376 None,
1377 Some("MyError"),
1378 )];
1379 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1380 let config = make_trait_bridge_config(None, None);
1381 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1382 let generator = MockBridgeGenerator;
1383 let result = gen_bridge_trait_impl(&spec, &generator);
1384 assert!(
1385 result.contains("-> std::result::Result<String, mylib::MyError>"),
1386 "missing std::result::Result return type in:\n{result}"
1387 );
1388 }
1389
1390 #[test]
1395 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1396 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1397 let config = make_trait_bridge_config(None, None);
1398 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1399 let generator = MockBridgeGenerator;
1400 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1401 }
1402
1403 #[test]
1404 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1405 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1406 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1407 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1408 let generator = MockBridgeGenerator;
1409 let result = gen_bridge_registration_fn(&spec, &generator);
1410 assert!(result.is_some());
1411 let code = result.unwrap();
1412 assert!(
1413 code.contains("register_ocr_backend"),
1414 "missing register fn name in:\n{code}"
1415 );
1416 }
1417
1418 #[test]
1423 fn test_gen_bridge_all_includes_imports() {
1424 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1425 let config = make_trait_bridge_config(None, None);
1426 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1427 let generator = MockBridgeGenerator;
1428 let output = gen_bridge_all(&spec, &generator);
1429 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1430 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1431 }
1432
1433 #[test]
1434 fn test_gen_bridge_all_includes_wrapper_struct() {
1435 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1436 let config = make_trait_bridge_config(None, None);
1437 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1438 let generator = MockBridgeGenerator;
1439 let output = gen_bridge_all(&spec, &generator);
1440 assert!(
1441 output.code.contains("pub struct PyOcrBackendBridge"),
1442 "missing struct in:\n{}",
1443 output.code
1444 );
1445 }
1446
1447 #[test]
1448 fn test_gen_bridge_all_includes_constructor() {
1449 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1450 let config = make_trait_bridge_config(None, None);
1451 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1452 let generator = MockBridgeGenerator;
1453 let output = gen_bridge_all(&spec, &generator);
1454 assert!(
1455 output.code.contains("pub fn new("),
1456 "missing constructor in:\n{}",
1457 output.code
1458 );
1459 }
1460
1461 #[test]
1462 fn test_gen_bridge_all_includes_trait_impl() {
1463 let methods = vec![make_method(
1464 "process",
1465 vec![],
1466 TypeRef::String,
1467 false,
1468 false,
1469 None,
1470 None,
1471 )];
1472 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1473 let config = make_trait_bridge_config(None, None);
1474 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1475 let generator = MockBridgeGenerator;
1476 let output = gen_bridge_all(&spec, &generator);
1477 assert!(
1478 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1479 "missing trait impl in:\n{}",
1480 output.code
1481 );
1482 }
1483
1484 #[test]
1485 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1486 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1487 let config = make_trait_bridge_config(Some("Plugin"), None);
1488 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1489 let generator = MockBridgeGenerator;
1490 let output = gen_bridge_all(&spec, &generator);
1491 assert!(
1492 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1493 "missing plugin impl in:\n{}",
1494 output.code
1495 );
1496 }
1497
1498 #[test]
1499 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1500 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1501 let config = make_trait_bridge_config(None, None);
1502 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1503 let generator = MockBridgeGenerator;
1504 let output = gen_bridge_all(&spec, &generator);
1505 assert!(
1506 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1507 "unexpected plugin impl present without super_trait"
1508 );
1509 }
1510
1511 #[test]
1512 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1513 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1514 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1515 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1516 let generator = MockBridgeGenerator;
1517 let output = gen_bridge_all(&spec, &generator);
1518 assert!(
1519 output.code.contains("register_ocr_backend"),
1520 "missing registration fn in:\n{}",
1521 output.code
1522 );
1523 }
1524
1525 #[test]
1526 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1527 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1528 let config = make_trait_bridge_config(None, None);
1529 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1530 let generator = MockBridgeGenerator;
1531 let output = gen_bridge_all(&spec, &generator);
1532 assert!(
1533 !output.code.contains("register_ocr_backend"),
1534 "unexpected registration fn present:\n{}",
1535 output.code
1536 );
1537 }
1538
1539 #[test]
1540 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1541 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1542 let config = make_trait_bridge_config(None, None);
1543 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1544 let generator = MockBridgeGenerator;
1545 let output = gen_bridge_all(&spec, &generator);
1546 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1547 let impl_pos = output
1548 .code
1549 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1550 .unwrap();
1551 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1552 }
1553}