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 }
536 }
537
538 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
539 TypeDef {
540 name: name.to_string(),
541 rust_path: rust_path.to_string(),
542 original_rust_path: rust_path.to_string(),
543 fields: vec![],
544 methods,
545 is_opaque: true,
546 is_clone: false,
547 doc: String::new(),
548 cfg: None,
549 is_trait: true,
550 has_default: false,
551 has_stripped_cfg_fields: false,
552 is_return_type: false,
553 serde_rename_all: None,
554 has_serde: false,
555 super_traits: vec![],
556 }
557 }
558
559 fn make_method(
560 name: &str,
561 params: Vec<ParamDef>,
562 return_type: TypeRef,
563 is_async: bool,
564 has_default_impl: bool,
565 trait_source: Option<&str>,
566 error_type: Option<&str>,
567 ) -> MethodDef {
568 MethodDef {
569 name: name.to_string(),
570 params,
571 return_type,
572 is_async,
573 is_static: false,
574 error_type: error_type.map(str::to_string),
575 doc: String::new(),
576 receiver: Some(ReceiverKind::Ref),
577 sanitized: false,
578 trait_source: trait_source.map(str::to_string),
579 returns_ref: false,
580 returns_cow: false,
581 return_newtype_wrapper: None,
582 has_default_impl,
583 }
584 }
585
586 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
587 ParamDef {
588 name: name.to_string(),
589 ty,
590 optional: false,
591 default: None,
592 sanitized: false,
593 typed_default: None,
594 is_ref,
595 is_mut: false,
596 newtype_wrapper: None,
597 original_type: None,
598 }
599 }
600
601 fn make_spec<'a>(
602 trait_def: &'a TypeDef,
603 bridge_config: &'a TraitBridgeConfig,
604 wrapper_prefix: &'a str,
605 type_paths: HashMap<String, String>,
606 ) -> TraitBridgeSpec<'a> {
607 TraitBridgeSpec {
608 trait_def,
609 bridge_config,
610 core_import: "mylib",
611 wrapper_prefix,
612 type_paths,
613 error_type: "MyError".to_string(),
614 error_constructor: "MyError::from({msg})".to_string(),
615 }
616 }
617
618 struct MockBridgeGenerator;
623
624 impl TraitBridgeGenerator for MockBridgeGenerator {
625 fn foreign_object_type(&self) -> &str {
626 "Py<PyAny>"
627 }
628
629 fn bridge_imports(&self) -> Vec<String> {
630 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
631 }
632
633 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
634 format!("// sync body for {}", method.name)
635 }
636
637 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
638 format!("// async body for {}", method.name)
639 }
640
641 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
642 format!(
643 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
644 spec.wrapper_name()
645 )
646 }
647
648 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
649 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
650 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
651 }
652 }
653
654 #[test]
659 fn test_wrapper_name() {
660 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
661 let config = make_trait_bridge_config(None, None);
662 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
663 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
664 }
665
666 #[test]
667 fn test_trait_snake() {
668 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
669 let config = make_trait_bridge_config(None, None);
670 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
671 assert_eq!(spec.trait_snake(), "ocr_backend");
672 }
673
674 #[test]
675 fn test_trait_path_replaces_hyphens() {
676 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
677 let config = make_trait_bridge_config(None, None);
678 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
679 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
680 }
681
682 #[test]
683 fn test_required_methods_filters_no_default_impl() {
684 let methods = vec![
685 make_method("process", vec![], TypeRef::String, false, false, None, None),
686 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
687 make_method("detect", vec![], TypeRef::String, false, false, None, None),
688 ];
689 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
690 let config = make_trait_bridge_config(None, None);
691 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
692 let required = spec.required_methods();
693 assert_eq!(required.len(), 2);
694 assert!(required.iter().any(|m| m.name == "process"));
695 assert!(required.iter().any(|m| m.name == "detect"));
696 }
697
698 #[test]
699 fn test_optional_methods_filters_has_default_impl() {
700 let methods = vec![
701 make_method("process", vec![], TypeRef::String, false, false, None, None),
702 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
703 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
704 ];
705 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
706 let config = make_trait_bridge_config(None, None);
707 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
708 let optional = spec.optional_methods();
709 assert_eq!(optional.len(), 2);
710 assert!(optional.iter().any(|m| m.name == "initialize"));
711 assert!(optional.iter().any(|m| m.name == "shutdown"));
712 }
713
714 #[test]
715 fn test_error_path() {
716 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
717 let config = make_trait_bridge_config(None, None);
718 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
719 assert_eq!(spec.error_path(), "mylib::MyError");
720 }
721
722 #[test]
727 fn test_format_type_ref_primitives() {
728 let paths = HashMap::new();
729 let cases: Vec<(TypeRef, &str)> = vec![
730 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
731 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
732 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
733 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
734 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
735 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
736 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
737 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
738 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
739 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
740 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
741 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
742 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
743 ];
744 for (ty, expected) in cases {
745 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
746 }
747 }
748
749 #[test]
750 fn test_format_type_ref_string() {
751 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
752 }
753
754 #[test]
755 fn test_format_type_ref_char() {
756 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
757 }
758
759 #[test]
760 fn test_format_type_ref_bytes() {
761 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
762 }
763
764 #[test]
765 fn test_format_type_ref_path() {
766 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
767 }
768
769 #[test]
770 fn test_format_type_ref_unit() {
771 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
772 }
773
774 #[test]
775 fn test_format_type_ref_json() {
776 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
777 }
778
779 #[test]
780 fn test_format_type_ref_duration() {
781 assert_eq!(
782 format_type_ref(&TypeRef::Duration, &HashMap::new()),
783 "std::time::Duration"
784 );
785 }
786
787 #[test]
788 fn test_format_type_ref_optional() {
789 let ty = TypeRef::Optional(Box::new(TypeRef::String));
790 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
791 }
792
793 #[test]
794 fn test_format_type_ref_optional_nested() {
795 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
796 PrimitiveType::U32,
797 )))));
798 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
799 }
800
801 #[test]
802 fn test_format_type_ref_vec() {
803 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
804 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
805 }
806
807 #[test]
808 fn test_format_type_ref_vec_nested() {
809 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
810 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
811 }
812
813 #[test]
814 fn test_format_type_ref_map() {
815 let ty = TypeRef::Map(
816 Box::new(TypeRef::String),
817 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
818 );
819 assert_eq!(
820 format_type_ref(&ty, &HashMap::new()),
821 "std::collections::HashMap<String, i64>"
822 );
823 }
824
825 #[test]
826 fn test_format_type_ref_map_nested_value() {
827 let ty = TypeRef::Map(
828 Box::new(TypeRef::String),
829 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
830 );
831 assert_eq!(
832 format_type_ref(&ty, &HashMap::new()),
833 "std::collections::HashMap<String, Vec<String>>"
834 );
835 }
836
837 #[test]
838 fn test_format_type_ref_named_without_type_paths() {
839 let ty = TypeRef::Named("Config".to_string());
840 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
841 }
842
843 #[test]
844 fn test_format_type_ref_named_with_type_paths() {
845 let ty = TypeRef::Named("Config".to_string());
846 let mut paths = HashMap::new();
847 paths.insert("Config".to_string(), "mylib::Config".to_string());
848 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
849 }
850
851 #[test]
852 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
853 let ty = TypeRef::Named("Unknown".to_string());
854 let mut paths = HashMap::new();
855 paths.insert("Other".to_string(), "mylib::Other".to_string());
856 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
857 }
858
859 #[test]
864 fn test_format_param_type_string_ref() {
865 let param = make_param("input", TypeRef::String, true);
866 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
867 }
868
869 #[test]
870 fn test_format_param_type_string_owned() {
871 let param = make_param("input", TypeRef::String, false);
872 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
873 }
874
875 #[test]
876 fn test_format_param_type_bytes_ref() {
877 let param = make_param("data", TypeRef::Bytes, true);
878 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
879 }
880
881 #[test]
882 fn test_format_param_type_bytes_owned() {
883 let param = make_param("data", TypeRef::Bytes, false);
884 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
885 }
886
887 #[test]
888 fn test_format_param_type_path_ref() {
889 let param = make_param("path", TypeRef::Path, true);
890 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
891 }
892
893 #[test]
894 fn test_format_param_type_path_owned() {
895 let param = make_param("path", TypeRef::Path, false);
896 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
897 }
898
899 #[test]
900 fn test_format_param_type_vec_ref() {
901 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
902 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
903 }
904
905 #[test]
906 fn test_format_param_type_vec_owned() {
907 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
908 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
909 }
910
911 #[test]
912 fn test_format_param_type_named_ref_with_type_paths() {
913 let mut paths = HashMap::new();
914 paths.insert("Config".to_string(), "mylib::Config".to_string());
915 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
916 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
917 }
918
919 #[test]
920 fn test_format_param_type_named_ref_without_type_paths() {
921 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
922 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
923 }
924
925 #[test]
926 fn test_format_param_type_primitive_ref_passes_by_value() {
927 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
929 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
930 }
931
932 #[test]
933 fn test_format_param_type_unit_ref_passes_by_value() {
934 let param = make_param("nothing", TypeRef::Unit, true);
935 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
936 }
937
938 #[test]
943 fn test_format_return_type_without_error() {
944 let result = format_return_type(&TypeRef::String, None, &HashMap::new());
945 assert_eq!(result, "String");
946 }
947
948 #[test]
949 fn test_format_return_type_with_error() {
950 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new());
951 assert_eq!(result, "std::result::Result<String, MyError>");
952 }
953
954 #[test]
955 fn test_format_return_type_unit_with_error() {
956 let result = format_return_type(&TypeRef::Unit, Some("Box<dyn std::error::Error>"), &HashMap::new());
957 assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
958 }
959
960 #[test]
961 fn test_format_return_type_named_with_type_paths_and_error() {
962 let mut paths = HashMap::new();
963 paths.insert("Output".to_string(), "mylib::Output".to_string());
964 let result = format_return_type(&TypeRef::Named("Output".to_string()), Some("mylib::MyError"), &paths);
965 assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
966 }
967
968 #[test]
973 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
974 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
975 let config = make_trait_bridge_config(None, None);
976 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
977 let generator = MockBridgeGenerator;
978 let result = gen_bridge_wrapper_struct(&spec, &generator);
979 assert!(
980 result.contains("pub struct PyOcrBackendBridge"),
981 "missing struct declaration in:\n{result}"
982 );
983 }
984
985 #[test]
986 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
987 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
988 let config = make_trait_bridge_config(None, None);
989 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
990 let generator = MockBridgeGenerator;
991 let result = gen_bridge_wrapper_struct(&spec, &generator);
992 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
993 }
994
995 #[test]
996 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
997 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
998 let config = make_trait_bridge_config(None, None);
999 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1000 let generator = MockBridgeGenerator;
1001 let result = gen_bridge_wrapper_struct(&spec, &generator);
1002 assert!(
1003 result.contains("cached_name: String"),
1004 "missing cached_name field in:\n{result}"
1005 );
1006 }
1007
1008 #[test]
1013 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1014 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1015 let config = make_trait_bridge_config(None, None);
1016 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1017 let generator = MockBridgeGenerator;
1018 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1019 }
1020
1021 #[test]
1022 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1023 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1024 let config = make_trait_bridge_config(Some("Plugin"), None);
1025 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1026 let generator = MockBridgeGenerator;
1027 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1028 }
1029
1030 #[test]
1031 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1032 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1033 let config = make_trait_bridge_config(Some("Plugin"), None);
1034 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1035 let generator = MockBridgeGenerator;
1036 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1037 assert!(
1038 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1039 "missing qualified super-trait path in:\n{result}"
1040 );
1041 }
1042
1043 #[test]
1044 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1045 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1046 let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
1047 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1048 let generator = MockBridgeGenerator;
1049 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1050 assert!(
1051 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1052 "wrong super-trait path in:\n{result}"
1053 );
1054 }
1055
1056 #[test]
1057 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1058 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1059 let config = make_trait_bridge_config(Some("Plugin"), None);
1060 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1061 let generator = MockBridgeGenerator;
1062 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1063 assert!(
1064 result.contains("fn name(") && result.contains("cached_name"),
1065 "missing name() using cached_name in:\n{result}"
1066 );
1067 }
1068
1069 #[test]
1070 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1071 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1072 let config = make_trait_bridge_config(Some("Plugin"), None);
1073 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1074 let generator = MockBridgeGenerator;
1075 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1076 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1077 }
1078
1079 #[test]
1080 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1081 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1082 let config = make_trait_bridge_config(Some("Plugin"), None);
1083 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1084 let generator = MockBridgeGenerator;
1085 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1086 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1087 }
1088
1089 #[test]
1090 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1091 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1092 let config = make_trait_bridge_config(Some("Plugin"), None);
1093 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1094 let generator = MockBridgeGenerator;
1095 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1096 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1097 }
1098
1099 #[test]
1104 fn test_gen_bridge_trait_impl_includes_impl_header() {
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_trait_impl(&spec, &generator);
1110 assert!(
1111 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1112 "missing impl header in:\n{result}"
1113 );
1114 }
1115
1116 #[test]
1117 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1118 let methods = vec![make_method(
1119 "process",
1120 vec![],
1121 TypeRef::String,
1122 false,
1123 false,
1124 None,
1125 None,
1126 )];
1127 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1128 let config = make_trait_bridge_config(None, None);
1129 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1130 let generator = MockBridgeGenerator;
1131 let result = gen_bridge_trait_impl(&spec, &generator);
1132 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1133 }
1134
1135 #[test]
1136 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1137 let methods = vec![make_method(
1138 "process",
1139 vec![],
1140 TypeRef::String,
1141 false,
1142 false,
1143 None,
1144 None,
1145 )];
1146 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1147 let config = make_trait_bridge_config(None, None);
1148 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1149 let generator = MockBridgeGenerator;
1150 let result = gen_bridge_trait_impl(&spec, &generator);
1151 assert!(
1152 result.contains("// sync body for process"),
1153 "missing sync method body in:\n{result}"
1154 );
1155 }
1156
1157 #[test]
1158 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1159 let methods = vec![make_method(
1160 "process_async",
1161 vec![],
1162 TypeRef::String,
1163 true,
1164 false,
1165 None,
1166 None,
1167 )];
1168 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1169 let config = make_trait_bridge_config(None, None);
1170 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1171 let generator = MockBridgeGenerator;
1172 let result = gen_bridge_trait_impl(&spec, &generator);
1173 assert!(
1174 result.contains("// async body for process_async"),
1175 "missing async method body in:\n{result}"
1176 );
1177 assert!(
1178 result.contains("async fn process_async("),
1179 "missing async keyword in method signature in:\n{result}"
1180 );
1181 }
1182
1183 #[test]
1184 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1185 let methods = vec![
1187 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1188 make_method(
1189 "inherited_method",
1190 vec![],
1191 TypeRef::String,
1192 false,
1193 false,
1194 Some("other_crate::OtherTrait"),
1195 None,
1196 ),
1197 ];
1198 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1199 let config = make_trait_bridge_config(None, None);
1200 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1201 let generator = MockBridgeGenerator;
1202 let result = gen_bridge_trait_impl(&spec, &generator);
1203 assert!(
1204 result.contains("fn own_method("),
1205 "own method should be present in:\n{result}"
1206 );
1207 assert!(
1208 !result.contains("fn inherited_method("),
1209 "inherited method should be filtered out in:\n{result}"
1210 );
1211 }
1212
1213 #[test]
1214 fn test_gen_bridge_trait_impl_method_with_params() {
1215 let params = vec![
1216 make_param("input", TypeRef::String, true),
1217 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1218 ];
1219 let methods = vec![make_method(
1220 "process",
1221 params,
1222 TypeRef::String,
1223 false,
1224 false,
1225 None,
1226 None,
1227 )];
1228 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1229 let config = make_trait_bridge_config(None, None);
1230 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1231 let generator = MockBridgeGenerator;
1232 let result = gen_bridge_trait_impl(&spec, &generator);
1233 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1234 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1235 }
1236
1237 #[test]
1238 fn test_gen_bridge_trait_impl_return_type_with_error() {
1239 let methods = vec![make_method(
1240 "process",
1241 vec![],
1242 TypeRef::String,
1243 false,
1244 false,
1245 None,
1246 Some("MyError"),
1247 )];
1248 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
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 result = gen_bridge_trait_impl(&spec, &generator);
1253 assert!(
1254 result.contains("-> std::result::Result<String, mylib::MyError>"),
1255 "missing std::result::Result return type in:\n{result}"
1256 );
1257 }
1258
1259 #[test]
1264 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1265 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1266 let config = make_trait_bridge_config(None, None);
1267 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1268 let generator = MockBridgeGenerator;
1269 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1270 }
1271
1272 #[test]
1273 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1274 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1275 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1276 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1277 let generator = MockBridgeGenerator;
1278 let result = gen_bridge_registration_fn(&spec, &generator);
1279 assert!(result.is_some());
1280 let code = result.unwrap();
1281 assert!(
1282 code.contains("register_ocr_backend"),
1283 "missing register fn name in:\n{code}"
1284 );
1285 }
1286
1287 #[test]
1292 fn test_gen_bridge_all_includes_imports() {
1293 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1294 let config = make_trait_bridge_config(None, None);
1295 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1296 let generator = MockBridgeGenerator;
1297 let output = gen_bridge_all(&spec, &generator);
1298 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1299 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1300 }
1301
1302 #[test]
1303 fn test_gen_bridge_all_includes_wrapper_struct() {
1304 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1305 let config = make_trait_bridge_config(None, None);
1306 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1307 let generator = MockBridgeGenerator;
1308 let output = gen_bridge_all(&spec, &generator);
1309 assert!(
1310 output.code.contains("pub struct PyOcrBackendBridge"),
1311 "missing struct in:\n{}",
1312 output.code
1313 );
1314 }
1315
1316 #[test]
1317 fn test_gen_bridge_all_includes_constructor() {
1318 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1319 let config = make_trait_bridge_config(None, None);
1320 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1321 let generator = MockBridgeGenerator;
1322 let output = gen_bridge_all(&spec, &generator);
1323 assert!(
1324 output.code.contains("pub fn new("),
1325 "missing constructor in:\n{}",
1326 output.code
1327 );
1328 }
1329
1330 #[test]
1331 fn test_gen_bridge_all_includes_trait_impl() {
1332 let methods = vec![make_method(
1333 "process",
1334 vec![],
1335 TypeRef::String,
1336 false,
1337 false,
1338 None,
1339 None,
1340 )];
1341 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1342 let config = make_trait_bridge_config(None, None);
1343 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1344 let generator = MockBridgeGenerator;
1345 let output = gen_bridge_all(&spec, &generator);
1346 assert!(
1347 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1348 "missing trait impl in:\n{}",
1349 output.code
1350 );
1351 }
1352
1353 #[test]
1354 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1355 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1356 let config = make_trait_bridge_config(Some("Plugin"), None);
1357 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1358 let generator = MockBridgeGenerator;
1359 let output = gen_bridge_all(&spec, &generator);
1360 assert!(
1361 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1362 "missing plugin impl in:\n{}",
1363 output.code
1364 );
1365 }
1366
1367 #[test]
1368 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1369 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1370 let config = make_trait_bridge_config(None, None);
1371 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1372 let generator = MockBridgeGenerator;
1373 let output = gen_bridge_all(&spec, &generator);
1374 assert!(
1375 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1376 "unexpected plugin impl present without super_trait"
1377 );
1378 }
1379
1380 #[test]
1381 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1382 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1383 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1384 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1385 let generator = MockBridgeGenerator;
1386 let output = gen_bridge_all(&spec, &generator);
1387 assert!(
1388 output.code.contains("register_ocr_backend"),
1389 "missing registration fn in:\n{}",
1390 output.code
1391 );
1392 }
1393
1394 #[test]
1395 fn test_gen_bridge_all_no_registration_fn_when_absent() {
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 let output = gen_bridge_all(&spec, &generator);
1401 assert!(
1402 !output.code.contains("register_ocr_backend"),
1403 "unexpected registration fn present:\n{}",
1404 output.code
1405 );
1406 }
1407
1408 #[test]
1409 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1410 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1411 let config = make_trait_bridge_config(None, None);
1412 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1413 let generator = MockBridgeGenerator;
1414 let output = gen_bridge_all(&spec, &generator);
1415 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1416 let impl_pos = output
1417 .code
1418 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1419 .unwrap();
1420 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1421 }
1422}