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!(
199 out,
200 " fn initialize(&self) -> std::result::Result<(), {error_path}> {{"
201 )
202 .ok();
203 let init_method = MethodDef {
204 name: "initialize".to_string(),
205 params: vec![],
206 return_type: alef_core::ir::TypeRef::Unit,
207 is_async: false,
208 is_static: false,
209 error_type: Some(error_path.clone()),
210 doc: String::new(),
211 receiver: Some(alef_core::ir::ReceiverKind::Ref),
212 sanitized: false,
213 trait_source: None,
214 returns_ref: false,
215 returns_cow: false,
216 return_newtype_wrapper: None,
217 has_default_impl: true,
218 };
219 let init_body = generator.gen_sync_method_body(&init_method, spec);
220 for line in init_body.lines() {
221 writeln!(out, " {}", line.trim_start()).ok();
222 }
223 writeln!(out, " }}").ok();
224 writeln!(out).ok();
225
226 writeln!(
228 out,
229 " fn shutdown(&self) -> std::result::Result<(), {error_path}> {{"
230 )
231 .ok();
232 let shutdown_method = MethodDef {
233 name: "shutdown".to_string(),
234 params: vec![],
235 return_type: alef_core::ir::TypeRef::Unit,
236 is_async: false,
237 is_static: false,
238 error_type: Some(error_path.clone()),
239 doc: String::new(),
240 receiver: Some(alef_core::ir::ReceiverKind::Ref),
241 sanitized: false,
242 trait_source: None,
243 returns_ref: false,
244 returns_cow: false,
245 return_newtype_wrapper: None,
246 has_default_impl: true,
247 };
248 let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
249 for line in shutdown_body.lines() {
250 writeln!(out, " {}", line.trim_start()).ok();
251 }
252 writeln!(out, " }}").ok();
253 write!(out, "}}").ok();
254 Some(out)
255}
256
257pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
262 let wrapper = spec.wrapper_name();
263 let trait_path = spec.trait_path();
264 let mut out = String::with_capacity(2048);
265
266 let has_async_methods = spec
268 .trait_def
269 .methods
270 .iter()
271 .any(|m| m.is_async && m.trait_source.is_none());
272 if has_async_methods {
273 writeln!(out, "#[async_trait::async_trait]").ok();
274 }
275 writeln!(out, "impl {trait_path} for {wrapper} {{").ok();
276
277 let own_methods: Vec<_> = spec
279 .trait_def
280 .methods
281 .iter()
282 .filter(|m| m.trait_source.is_none())
283 .collect();
284
285 for (i, method) in own_methods.iter().enumerate() {
286 if i > 0 {
287 writeln!(out).ok();
288 }
289
290 let async_kw = if method.is_async { "async " } else { "" };
292 let receiver = match &method.receiver {
293 Some(alef_core::ir::ReceiverKind::Ref) => "&self",
294 Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
295 Some(alef_core::ir::ReceiverKind::Owned) => "self",
296 None => "",
297 };
298
299 let params: Vec<String> = method
301 .params
302 .iter()
303 .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
304 .collect();
305
306 let all_params = if receiver.is_empty() {
307 params.join(", ")
308 } else if params.is_empty() {
309 receiver.to_string()
310 } else {
311 format!("{}, {}", receiver, params.join(", "))
312 };
313
314 let error_override = method.error_type.as_ref().map(|_| spec.error_path());
318 let ret = format_return_type(&method.return_type, error_override.as_deref(), &spec.type_paths);
319
320 writeln!(out, " {async_kw}fn {}({all_params}) -> {ret} {{", method.name).ok();
321
322 let body = if method.is_async {
324 generator.gen_async_method_body(method, spec)
325 } else {
326 generator.gen_sync_method_body(method, spec)
327 };
328
329 for line in body.lines() {
330 writeln!(out, " {line}").ok();
331 }
332 writeln!(out, " }}").ok();
333 }
334
335 write!(out, "}}").ok();
336 out
337}
338
339pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
346 spec.bridge_config.register_fn.as_deref()?;
347 Some(generator.gen_registration_fn(spec))
348}
349
350pub struct BridgeOutput {
353 pub imports: Vec<String>,
355 pub code: String,
357}
358
359pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
365 let imports = generator.bridge_imports();
366 let mut out = String::with_capacity(4096);
367
368 out.push_str(&gen_bridge_wrapper_struct(spec, generator));
370 writeln!(out).ok();
371 writeln!(out).ok();
372
373 out.push_str(&generator.gen_constructor(spec));
375 writeln!(out).ok();
376 writeln!(out).ok();
377
378 if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
380 out.push_str(&plugin_impl);
381 writeln!(out).ok();
382 writeln!(out).ok();
383 }
384
385 out.push_str(&gen_bridge_trait_impl(spec, generator));
387
388 if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
390 writeln!(out).ok();
391 writeln!(out).ok();
392 out.push_str(®_fn_code);
393 }
394
395 BridgeOutput { imports, code: out }
396}
397
398pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
407 use alef_core::ir::{PrimitiveType, TypeRef};
408 match ty {
409 TypeRef::Primitive(p) => match p {
410 PrimitiveType::Bool => "bool",
411 PrimitiveType::U8 => "u8",
412 PrimitiveType::U16 => "u16",
413 PrimitiveType::U32 => "u32",
414 PrimitiveType::U64 => "u64",
415 PrimitiveType::I8 => "i8",
416 PrimitiveType::I16 => "i16",
417 PrimitiveType::I32 => "i32",
418 PrimitiveType::I64 => "i64",
419 PrimitiveType::F32 => "f32",
420 PrimitiveType::F64 => "f64",
421 PrimitiveType::Usize => "usize",
422 PrimitiveType::Isize => "isize",
423 }
424 .to_string(),
425 TypeRef::String => "String".to_string(),
426 TypeRef::Char => "char".to_string(),
427 TypeRef::Bytes => "Vec<u8>".to_string(),
428 TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
429 TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
430 TypeRef::Map(k, v) => format!(
431 "std::collections::HashMap<{}, {}>",
432 format_type_ref(k, type_paths),
433 format_type_ref(v, type_paths)
434 ),
435 TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
436 TypeRef::Path => "std::path::PathBuf".to_string(),
437 TypeRef::Unit => "()".to_string(),
438 TypeRef::Json => "serde_json::Value".to_string(),
439 TypeRef::Duration => "std::time::Duration".to_string(),
440 }
441}
442
443pub fn format_return_type(
445 ty: &alef_core::ir::TypeRef,
446 error_type: Option<&str>,
447 type_paths: &HashMap<String, String>,
448) -> String {
449 let inner = format_type_ref(ty, type_paths);
450 match error_type {
451 Some(err) => format!("std::result::Result<{inner}, {err}>"),
452 None => inner,
453 }
454}
455
456pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
468 use alef_core::ir::TypeRef;
469 let base = if param.is_ref {
470 let mutability = if param.is_mut { "mut " } else { "" };
471 match ¶m.ty {
472 TypeRef::String => format!("&{mutability}str"),
473 TypeRef::Bytes => format!("&{mutability}[u8]"),
474 TypeRef::Path => format!("&{mutability}std::path::Path"),
475 TypeRef::Vec(inner) => format!("&{mutability}[{}]", format_type_ref(inner, type_paths)),
476 TypeRef::Named(name) => {
477 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
478 format!("&{mutability}{qualified}")
479 }
480 TypeRef::Optional(inner) => {
481 let inner_type_str = match inner.as_ref() {
485 TypeRef::String => format!("&{mutability}str"),
486 TypeRef::Bytes => format!("&{mutability}[u8]"),
487 TypeRef::Path => format!("&{mutability}std::path::Path"),
488 TypeRef::Vec(v) => format!("&{mutability}[{}]", format_type_ref(v, type_paths)),
489 TypeRef::Named(name) => {
490 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
491 format!("&{mutability}{qualified}")
492 }
493 other => format_type_ref(other, type_paths),
495 };
496 return format!("Option<{inner_type_str}>");
498 }
499 other => format_type_ref(other, type_paths),
501 }
502 } else {
503 format_type_ref(¶m.ty, type_paths)
504 };
505
506 if param.optional {
510 format!("Option<{base}>")
511 } else {
512 base
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use alef_core::config::TraitBridgeConfig;
520 use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
521
522 fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
527 TraitBridgeConfig {
528 trait_name: "OcrBackend".to_string(),
529 super_trait: super_trait.map(str::to_string),
530 registry_getter: None,
531 register_fn: register_fn.map(str::to_string),
532 type_alias: None,
533 param_name: None,
534 register_extra_args: None,
535 exclude_languages: Vec::new(),
536 }
537 }
538
539 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
540 TypeDef {
541 name: name.to_string(),
542 rust_path: rust_path.to_string(),
543 original_rust_path: rust_path.to_string(),
544 fields: vec![],
545 methods,
546 is_opaque: true,
547 is_clone: false,
548 doc: String::new(),
549 cfg: None,
550 is_trait: true,
551 has_default: false,
552 has_stripped_cfg_fields: false,
553 is_return_type: false,
554 serde_rename_all: None,
555 has_serde: false,
556 super_traits: vec![],
557 }
558 }
559
560 fn make_method(
561 name: &str,
562 params: Vec<ParamDef>,
563 return_type: TypeRef,
564 is_async: bool,
565 has_default_impl: bool,
566 trait_source: Option<&str>,
567 error_type: Option<&str>,
568 ) -> MethodDef {
569 MethodDef {
570 name: name.to_string(),
571 params,
572 return_type,
573 is_async,
574 is_static: false,
575 error_type: error_type.map(str::to_string),
576 doc: String::new(),
577 receiver: Some(ReceiverKind::Ref),
578 sanitized: false,
579 trait_source: trait_source.map(str::to_string),
580 returns_ref: false,
581 returns_cow: false,
582 return_newtype_wrapper: None,
583 has_default_impl,
584 }
585 }
586
587 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
588 ParamDef {
589 name: name.to_string(),
590 ty,
591 optional: false,
592 default: None,
593 sanitized: false,
594 typed_default: None,
595 is_ref,
596 is_mut: false,
597 newtype_wrapper: None,
598 original_type: None,
599 }
600 }
601
602 fn make_spec<'a>(
603 trait_def: &'a TypeDef,
604 bridge_config: &'a TraitBridgeConfig,
605 wrapper_prefix: &'a str,
606 type_paths: HashMap<String, String>,
607 ) -> TraitBridgeSpec<'a> {
608 TraitBridgeSpec {
609 trait_def,
610 bridge_config,
611 core_import: "mylib",
612 wrapper_prefix,
613 type_paths,
614 error_type: "MyError".to_string(),
615 error_constructor: "MyError::from({msg})".to_string(),
616 }
617 }
618
619 struct MockBridgeGenerator;
624
625 impl TraitBridgeGenerator for MockBridgeGenerator {
626 fn foreign_object_type(&self) -> &str {
627 "Py<PyAny>"
628 }
629
630 fn bridge_imports(&self) -> Vec<String> {
631 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
632 }
633
634 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
635 format!("// sync body for {}", method.name)
636 }
637
638 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
639 format!("// async body for {}", method.name)
640 }
641
642 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
643 format!(
644 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
645 spec.wrapper_name()
646 )
647 }
648
649 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
650 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
651 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
652 }
653 }
654
655 #[test]
660 fn test_wrapper_name() {
661 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
662 let config = make_trait_bridge_config(None, None);
663 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
664 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
665 }
666
667 #[test]
668 fn test_trait_snake() {
669 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
670 let config = make_trait_bridge_config(None, None);
671 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
672 assert_eq!(spec.trait_snake(), "ocr_backend");
673 }
674
675 #[test]
676 fn test_trait_path_replaces_hyphens() {
677 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
678 let config = make_trait_bridge_config(None, None);
679 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
680 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
681 }
682
683 #[test]
684 fn test_required_methods_filters_no_default_impl() {
685 let methods = vec![
686 make_method("process", vec![], TypeRef::String, false, false, None, None),
687 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
688 make_method("detect", vec![], TypeRef::String, false, false, None, None),
689 ];
690 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
691 let config = make_trait_bridge_config(None, None);
692 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
693 let required = spec.required_methods();
694 assert_eq!(required.len(), 2);
695 assert!(required.iter().any(|m| m.name == "process"));
696 assert!(required.iter().any(|m| m.name == "detect"));
697 }
698
699 #[test]
700 fn test_optional_methods_filters_has_default_impl() {
701 let methods = vec![
702 make_method("process", vec![], TypeRef::String, false, false, None, None),
703 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
704 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
705 ];
706 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
707 let config = make_trait_bridge_config(None, None);
708 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
709 let optional = spec.optional_methods();
710 assert_eq!(optional.len(), 2);
711 assert!(optional.iter().any(|m| m.name == "initialize"));
712 assert!(optional.iter().any(|m| m.name == "shutdown"));
713 }
714
715 #[test]
716 fn test_error_path() {
717 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
718 let config = make_trait_bridge_config(None, None);
719 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
720 assert_eq!(spec.error_path(), "mylib::MyError");
721 }
722
723 #[test]
728 fn test_format_type_ref_primitives() {
729 let paths = HashMap::new();
730 let cases: Vec<(TypeRef, &str)> = vec![
731 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
732 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
733 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
734 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
735 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
736 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
737 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
738 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
739 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
740 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
741 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
742 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
743 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
744 ];
745 for (ty, expected) in cases {
746 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
747 }
748 }
749
750 #[test]
751 fn test_format_type_ref_string() {
752 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
753 }
754
755 #[test]
756 fn test_format_type_ref_char() {
757 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
758 }
759
760 #[test]
761 fn test_format_type_ref_bytes() {
762 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
763 }
764
765 #[test]
766 fn test_format_type_ref_path() {
767 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
768 }
769
770 #[test]
771 fn test_format_type_ref_unit() {
772 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
773 }
774
775 #[test]
776 fn test_format_type_ref_json() {
777 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
778 }
779
780 #[test]
781 fn test_format_type_ref_duration() {
782 assert_eq!(
783 format_type_ref(&TypeRef::Duration, &HashMap::new()),
784 "std::time::Duration"
785 );
786 }
787
788 #[test]
789 fn test_format_type_ref_optional() {
790 let ty = TypeRef::Optional(Box::new(TypeRef::String));
791 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
792 }
793
794 #[test]
795 fn test_format_type_ref_optional_nested() {
796 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
797 PrimitiveType::U32,
798 )))));
799 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
800 }
801
802 #[test]
803 fn test_format_type_ref_vec() {
804 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
805 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
806 }
807
808 #[test]
809 fn test_format_type_ref_vec_nested() {
810 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
811 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
812 }
813
814 #[test]
815 fn test_format_type_ref_map() {
816 let ty = TypeRef::Map(
817 Box::new(TypeRef::String),
818 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
819 );
820 assert_eq!(
821 format_type_ref(&ty, &HashMap::new()),
822 "std::collections::HashMap<String, i64>"
823 );
824 }
825
826 #[test]
827 fn test_format_type_ref_map_nested_value() {
828 let ty = TypeRef::Map(
829 Box::new(TypeRef::String),
830 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
831 );
832 assert_eq!(
833 format_type_ref(&ty, &HashMap::new()),
834 "std::collections::HashMap<String, Vec<String>>"
835 );
836 }
837
838 #[test]
839 fn test_format_type_ref_named_without_type_paths() {
840 let ty = TypeRef::Named("Config".to_string());
841 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
842 }
843
844 #[test]
845 fn test_format_type_ref_named_with_type_paths() {
846 let ty = TypeRef::Named("Config".to_string());
847 let mut paths = HashMap::new();
848 paths.insert("Config".to_string(), "mylib::Config".to_string());
849 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
850 }
851
852 #[test]
853 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
854 let ty = TypeRef::Named("Unknown".to_string());
855 let mut paths = HashMap::new();
856 paths.insert("Other".to_string(), "mylib::Other".to_string());
857 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
858 }
859
860 #[test]
865 fn test_format_param_type_string_ref() {
866 let param = make_param("input", TypeRef::String, true);
867 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
868 }
869
870 #[test]
871 fn test_format_param_type_string_owned() {
872 let param = make_param("input", TypeRef::String, false);
873 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
874 }
875
876 #[test]
877 fn test_format_param_type_bytes_ref() {
878 let param = make_param("data", TypeRef::Bytes, true);
879 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
880 }
881
882 #[test]
883 fn test_format_param_type_bytes_owned() {
884 let param = make_param("data", TypeRef::Bytes, false);
885 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
886 }
887
888 #[test]
889 fn test_format_param_type_path_ref() {
890 let param = make_param("path", TypeRef::Path, true);
891 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
892 }
893
894 #[test]
895 fn test_format_param_type_path_owned() {
896 let param = make_param("path", TypeRef::Path, false);
897 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
898 }
899
900 #[test]
901 fn test_format_param_type_vec_ref() {
902 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
903 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
904 }
905
906 #[test]
907 fn test_format_param_type_vec_owned() {
908 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
909 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
910 }
911
912 #[test]
913 fn test_format_param_type_named_ref_with_type_paths() {
914 let mut paths = HashMap::new();
915 paths.insert("Config".to_string(), "mylib::Config".to_string());
916 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
917 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
918 }
919
920 #[test]
921 fn test_format_param_type_named_ref_without_type_paths() {
922 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
923 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
924 }
925
926 #[test]
927 fn test_format_param_type_primitive_ref_passes_by_value() {
928 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
930 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
931 }
932
933 #[test]
934 fn test_format_param_type_unit_ref_passes_by_value() {
935 let param = make_param("nothing", TypeRef::Unit, true);
936 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
937 }
938
939 #[test]
944 fn test_format_return_type_without_error() {
945 let result = format_return_type(&TypeRef::String, None, &HashMap::new());
946 assert_eq!(result, "String");
947 }
948
949 #[test]
950 fn test_format_return_type_with_error() {
951 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new());
952 assert_eq!(result, "std::result::Result<String, MyError>");
953 }
954
955 #[test]
956 fn test_format_return_type_unit_with_error() {
957 let result = format_return_type(&TypeRef::Unit, Some("Box<dyn std::error::Error>"), &HashMap::new());
958 assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
959 }
960
961 #[test]
962 fn test_format_return_type_named_with_type_paths_and_error() {
963 let mut paths = HashMap::new();
964 paths.insert("Output".to_string(), "mylib::Output".to_string());
965 let result = format_return_type(&TypeRef::Named("Output".to_string()), Some("mylib::MyError"), &paths);
966 assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
967 }
968
969 #[test]
974 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
975 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
976 let config = make_trait_bridge_config(None, None);
977 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
978 let generator = MockBridgeGenerator;
979 let result = gen_bridge_wrapper_struct(&spec, &generator);
980 assert!(
981 result.contains("pub struct PyOcrBackendBridge"),
982 "missing struct declaration in:\n{result}"
983 );
984 }
985
986 #[test]
987 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
988 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
989 let config = make_trait_bridge_config(None, None);
990 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
991 let generator = MockBridgeGenerator;
992 let result = gen_bridge_wrapper_struct(&spec, &generator);
993 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
994 }
995
996 #[test]
997 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
998 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
999 let config = make_trait_bridge_config(None, None);
1000 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1001 let generator = MockBridgeGenerator;
1002 let result = gen_bridge_wrapper_struct(&spec, &generator);
1003 assert!(
1004 result.contains("cached_name: String"),
1005 "missing cached_name field in:\n{result}"
1006 );
1007 }
1008
1009 #[test]
1014 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1015 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1016 let config = make_trait_bridge_config(None, None);
1017 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1018 let generator = MockBridgeGenerator;
1019 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1020 }
1021
1022 #[test]
1023 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1024 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1025 let config = make_trait_bridge_config(Some("Plugin"), None);
1026 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1027 let generator = MockBridgeGenerator;
1028 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1029 }
1030
1031 #[test]
1032 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1033 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1034 let config = make_trait_bridge_config(Some("Plugin"), None);
1035 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1036 let generator = MockBridgeGenerator;
1037 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1038 assert!(
1039 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1040 "missing qualified super-trait path in:\n{result}"
1041 );
1042 }
1043
1044 #[test]
1045 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1046 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1047 let config = make_trait_bridge_config(Some("other_crate::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!(
1052 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1053 "wrong super-trait path in:\n{result}"
1054 );
1055 }
1056
1057 #[test]
1058 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1059 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1060 let config = make_trait_bridge_config(Some("Plugin"), None);
1061 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1062 let generator = MockBridgeGenerator;
1063 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1064 assert!(
1065 result.contains("fn name(") && result.contains("cached_name"),
1066 "missing name() using cached_name in:\n{result}"
1067 );
1068 }
1069
1070 #[test]
1071 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1072 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1073 let config = make_trait_bridge_config(Some("Plugin"), None);
1074 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1075 let generator = MockBridgeGenerator;
1076 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1077 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1078 }
1079
1080 #[test]
1081 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1082 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1083 let config = make_trait_bridge_config(Some("Plugin"), None);
1084 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1085 let generator = MockBridgeGenerator;
1086 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1087 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1088 }
1089
1090 #[test]
1091 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1092 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1093 let config = make_trait_bridge_config(Some("Plugin"), None);
1094 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1095 let generator = MockBridgeGenerator;
1096 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1097 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1098 }
1099
1100 #[test]
1105 fn test_gen_bridge_trait_impl_includes_impl_header() {
1106 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1107 let config = make_trait_bridge_config(None, None);
1108 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1109 let generator = MockBridgeGenerator;
1110 let result = gen_bridge_trait_impl(&spec, &generator);
1111 assert!(
1112 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1113 "missing impl header in:\n{result}"
1114 );
1115 }
1116
1117 #[test]
1118 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1119 let methods = vec![make_method(
1120 "process",
1121 vec![],
1122 TypeRef::String,
1123 false,
1124 false,
1125 None,
1126 None,
1127 )];
1128 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
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_trait_impl(&spec, &generator);
1133 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1134 }
1135
1136 #[test]
1137 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1138 let methods = vec![make_method(
1139 "process",
1140 vec![],
1141 TypeRef::String,
1142 false,
1143 false,
1144 None,
1145 None,
1146 )];
1147 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1148 let config = make_trait_bridge_config(None, None);
1149 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1150 let generator = MockBridgeGenerator;
1151 let result = gen_bridge_trait_impl(&spec, &generator);
1152 assert!(
1153 result.contains("// sync body for process"),
1154 "missing sync method body in:\n{result}"
1155 );
1156 }
1157
1158 #[test]
1159 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1160 let methods = vec![make_method(
1161 "process_async",
1162 vec![],
1163 TypeRef::String,
1164 true,
1165 false,
1166 None,
1167 None,
1168 )];
1169 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1170 let config = make_trait_bridge_config(None, None);
1171 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1172 let generator = MockBridgeGenerator;
1173 let result = gen_bridge_trait_impl(&spec, &generator);
1174 assert!(
1175 result.contains("// async body for process_async"),
1176 "missing async method body in:\n{result}"
1177 );
1178 assert!(
1179 result.contains("async fn process_async("),
1180 "missing async keyword in method signature in:\n{result}"
1181 );
1182 }
1183
1184 #[test]
1185 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1186 let methods = vec![
1188 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1189 make_method(
1190 "inherited_method",
1191 vec![],
1192 TypeRef::String,
1193 false,
1194 false,
1195 Some("other_crate::OtherTrait"),
1196 None,
1197 ),
1198 ];
1199 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1200 let config = make_trait_bridge_config(None, None);
1201 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1202 let generator = MockBridgeGenerator;
1203 let result = gen_bridge_trait_impl(&spec, &generator);
1204 assert!(
1205 result.contains("fn own_method("),
1206 "own method should be present in:\n{result}"
1207 );
1208 assert!(
1209 !result.contains("fn inherited_method("),
1210 "inherited method should be filtered out in:\n{result}"
1211 );
1212 }
1213
1214 #[test]
1215 fn test_gen_bridge_trait_impl_method_with_params() {
1216 let params = vec![
1217 make_param("input", TypeRef::String, true),
1218 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1219 ];
1220 let methods = vec![make_method(
1221 "process",
1222 params,
1223 TypeRef::String,
1224 false,
1225 false,
1226 None,
1227 None,
1228 )];
1229 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1230 let config = make_trait_bridge_config(None, None);
1231 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1232 let generator = MockBridgeGenerator;
1233 let result = gen_bridge_trait_impl(&spec, &generator);
1234 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1235 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1236 }
1237
1238 #[test]
1239 fn test_gen_bridge_trait_impl_return_type_with_error() {
1240 let methods = vec![make_method(
1241 "process",
1242 vec![],
1243 TypeRef::String,
1244 false,
1245 false,
1246 None,
1247 Some("MyError"),
1248 )];
1249 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1250 let config = make_trait_bridge_config(None, None);
1251 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1252 let generator = MockBridgeGenerator;
1253 let result = gen_bridge_trait_impl(&spec, &generator);
1254 assert!(
1255 result.contains("-> std::result::Result<String, mylib::MyError>"),
1256 "missing std::result::Result return type in:\n{result}"
1257 );
1258 }
1259
1260 #[test]
1265 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1266 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1267 let config = make_trait_bridge_config(None, None);
1268 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1269 let generator = MockBridgeGenerator;
1270 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1271 }
1272
1273 #[test]
1274 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1275 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1276 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1277 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1278 let generator = MockBridgeGenerator;
1279 let result = gen_bridge_registration_fn(&spec, &generator);
1280 assert!(result.is_some());
1281 let code = result.unwrap();
1282 assert!(
1283 code.contains("register_ocr_backend"),
1284 "missing register fn name in:\n{code}"
1285 );
1286 }
1287
1288 #[test]
1293 fn test_gen_bridge_all_includes_imports() {
1294 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1295 let config = make_trait_bridge_config(None, None);
1296 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1297 let generator = MockBridgeGenerator;
1298 let output = gen_bridge_all(&spec, &generator);
1299 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1300 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1301 }
1302
1303 #[test]
1304 fn test_gen_bridge_all_includes_wrapper_struct() {
1305 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1306 let config = make_trait_bridge_config(None, 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("pub struct PyOcrBackendBridge"),
1312 "missing struct in:\n{}",
1313 output.code
1314 );
1315 }
1316
1317 #[test]
1318 fn test_gen_bridge_all_includes_constructor() {
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("pub fn new("),
1326 "missing constructor in:\n{}",
1327 output.code
1328 );
1329 }
1330
1331 #[test]
1332 fn test_gen_bridge_all_includes_trait_impl() {
1333 let methods = vec![make_method(
1334 "process",
1335 vec![],
1336 TypeRef::String,
1337 false,
1338 false,
1339 None,
1340 None,
1341 )];
1342 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1343 let config = make_trait_bridge_config(None, None);
1344 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1345 let generator = MockBridgeGenerator;
1346 let output = gen_bridge_all(&spec, &generator);
1347 assert!(
1348 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1349 "missing trait impl in:\n{}",
1350 output.code
1351 );
1352 }
1353
1354 #[test]
1355 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1356 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1357 let config = make_trait_bridge_config(Some("Plugin"), None);
1358 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1359 let generator = MockBridgeGenerator;
1360 let output = gen_bridge_all(&spec, &generator);
1361 assert!(
1362 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1363 "missing plugin impl in:\n{}",
1364 output.code
1365 );
1366 }
1367
1368 #[test]
1369 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1370 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1371 let config = make_trait_bridge_config(None, None);
1372 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1373 let generator = MockBridgeGenerator;
1374 let output = gen_bridge_all(&spec, &generator);
1375 assert!(
1376 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1377 "unexpected plugin impl present without super_trait"
1378 );
1379 }
1380
1381 #[test]
1382 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1383 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1384 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1385 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1386 let generator = MockBridgeGenerator;
1387 let output = gen_bridge_all(&spec, &generator);
1388 assert!(
1389 output.code.contains("register_ocr_backend"),
1390 "missing registration fn in:\n{}",
1391 output.code
1392 );
1393 }
1394
1395 #[test]
1396 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1397 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1398 let config = make_trait_bridge_config(None, None);
1399 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1400 let generator = MockBridgeGenerator;
1401 let output = gen_bridge_all(&spec, &generator);
1402 assert!(
1403 !output.code.contains("register_ocr_backend"),
1404 "unexpected registration fn present:\n{}",
1405 output.code
1406 );
1407 }
1408
1409 #[test]
1410 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1411 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1412 let config = make_trait_bridge_config(None, None);
1413 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1414 let generator = MockBridgeGenerator;
1415 let output = gen_bridge_all(&spec, &generator);
1416 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1417 let impl_pos = output
1418 .code
1419 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1420 .unwrap();
1421 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1422 }
1423}