1use alef_core::config::{BridgeBinding, TraitBridgeConfig};
9use alef_core::ir::{FieldDef, FunctionDef, MethodDef, ParamDef, PrimitiveType, TypeDef, TypeRef};
10use heck::ToSnakeCase;
11use std::collections::HashMap;
12
13pub struct TraitBridgeSpec<'a> {
15 pub trait_def: &'a TypeDef,
17 pub bridge_config: &'a TraitBridgeConfig,
19 pub core_import: &'a str,
21 pub wrapper_prefix: &'a str,
23 pub type_paths: HashMap<String, String>,
25 pub error_type: String,
27 pub error_constructor: String,
29}
30
31impl<'a> TraitBridgeSpec<'a> {
32 pub fn error_path(&self) -> String {
39 if self.error_type.contains("::") || self.error_type.contains('<') {
40 self.error_type.clone()
41 } else {
42 format!("{}::{}", self.core_import, self.error_type)
43 }
44 }
45
46 pub fn make_error(&self, msg_expr: &str) -> String {
48 self.error_constructor.replace("{msg}", msg_expr)
49 }
50
51 pub fn wrapper_name(&self) -> String {
53 format!("{}{}Bridge", self.wrapper_prefix, self.trait_def.name)
54 }
55
56 pub fn trait_snake(&self) -> String {
58 self.trait_def.name.to_snake_case()
59 }
60
61 pub fn trait_path(&self) -> String {
63 self.trait_def.rust_path.replace('-', "_")
64 }
65
66 pub fn required_methods(&self) -> Vec<&'a MethodDef> {
68 self.trait_def.methods.iter().filter(|m| !m.has_default_impl).collect()
69 }
70
71 pub fn optional_methods(&self) -> Vec<&'a MethodDef> {
73 self.trait_def.methods.iter().filter(|m| m.has_default_impl).collect()
74 }
75}
76
77pub trait TraitBridgeGenerator {
83 fn foreign_object_type(&self) -> &str;
85
86 fn bridge_imports(&self) -> Vec<String>;
88
89 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
94
95 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
99
100 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String;
105
106 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String;
112
113 fn gen_unregistration_fn(&self, _spec: &TraitBridgeSpec) -> String {
120 String::new()
121 }
122
123 fn gen_clear_fn(&self, _spec: &TraitBridgeSpec) -> String {
130 String::new()
131 }
132
133 fn async_trait_is_send(&self) -> bool {
138 true
139 }
140}
141
142pub fn gen_bridge_wrapper_struct(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
156 let wrapper = spec.wrapper_name();
157 let foreign_type = generator.foreign_object_type();
158
159 crate::template_env::render(
160 "generators/trait_bridge/wrapper_struct.jinja",
161 minijinja::context! {
162 wrapper_prefix => spec.wrapper_prefix,
163 trait_name => &spec.trait_def.name,
164 wrapper_name => wrapper,
165 foreign_type => foreign_type,
166 },
167 )
168}
169
170fn gen_bridge_debug_impl(spec: &TraitBridgeSpec) -> String {
176 let wrapper = spec.wrapper_name();
177 crate::template_env::render(
178 "generators/trait_bridge/debug_impl.jinja",
179 minijinja::context! {
180 wrapper_name => wrapper,
181 },
182 )
183}
184
185pub fn gen_bridge_plugin_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
193 let super_trait_name = spec.bridge_config.super_trait.as_deref()?;
194
195 let wrapper = spec.wrapper_name();
196 let core_import = spec.core_import;
197
198 let super_trait_path = if super_trait_name.contains("::") {
200 super_trait_name.to_string()
201 } else {
202 format!("{core_import}::{super_trait_name}")
203 };
204
205 let error_path = spec.error_path();
209
210 let version_method = MethodDef {
212 name: "version".to_string(),
213 params: vec![],
214 return_type: alef_core::ir::TypeRef::String,
215 is_async: false,
216 is_static: false,
217 error_type: None,
218 doc: String::new(),
219 receiver: Some(alef_core::ir::ReceiverKind::Ref),
220 sanitized: false,
221 trait_source: None,
222 returns_ref: false,
223 returns_cow: false,
224 return_newtype_wrapper: None,
225 has_default_impl: false,
226 };
227 let version_body = generator.gen_sync_method_body(&version_method, spec);
228
229 let init_method = MethodDef {
231 name: "initialize".to_string(),
232 params: vec![],
233 return_type: alef_core::ir::TypeRef::Unit,
234 is_async: false,
235 is_static: false,
236 error_type: Some(error_path.clone()),
237 doc: String::new(),
238 receiver: Some(alef_core::ir::ReceiverKind::Ref),
239 sanitized: false,
240 trait_source: None,
241 returns_ref: false,
242 returns_cow: false,
243 return_newtype_wrapper: None,
244 has_default_impl: true,
245 };
246 let init_body = generator.gen_sync_method_body(&init_method, spec);
247
248 let shutdown_method = MethodDef {
250 name: "shutdown".to_string(),
251 params: vec![],
252 return_type: alef_core::ir::TypeRef::Unit,
253 is_async: false,
254 is_static: false,
255 error_type: Some(error_path.clone()),
256 doc: String::new(),
257 receiver: Some(alef_core::ir::ReceiverKind::Ref),
258 sanitized: false,
259 trait_source: None,
260 returns_ref: false,
261 returns_cow: false,
262 return_newtype_wrapper: None,
263 has_default_impl: true,
264 };
265 let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
266
267 let version_lines: Vec<&str> = version_body.lines().collect();
269 let init_lines: Vec<&str> = init_body.lines().collect();
270 let shutdown_lines: Vec<&str> = shutdown_body.lines().collect();
271
272 Some(crate::template_env::render(
273 "generators/trait_bridge/plugin_impl.jinja",
274 minijinja::context! {
275 super_trait_path => super_trait_path,
276 wrapper_name => wrapper,
277 error_path => error_path,
278 version_lines => version_lines,
279 init_lines => init_lines,
280 shutdown_lines => shutdown_lines,
281 },
282 ))
283}
284
285pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
291 let wrapper = spec.wrapper_name();
292 let trait_path = spec.trait_path();
293
294 let has_async_methods = spec
297 .trait_def
298 .methods
299 .iter()
300 .any(|m| m.is_async && m.trait_source.is_none() && !m.has_default_impl);
301 let async_trait_is_send = generator.async_trait_is_send();
302
303 let own_methods: Vec<_> = spec
307 .trait_def
308 .methods
309 .iter()
310 .filter(|m| m.trait_source.is_none() && !m.has_default_impl)
311 .collect();
312
313 let mut methods_code = String::with_capacity(1024);
315 for (i, method) in own_methods.iter().enumerate() {
316 if i > 0 {
317 methods_code.push_str("\n\n");
318 }
319
320 let async_kw = if method.is_async { "async " } else { "" };
322 let receiver = match &method.receiver {
323 Some(alef_core::ir::ReceiverKind::Ref) => "&self",
324 Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
325 Some(alef_core::ir::ReceiverKind::Owned) => "self",
326 None => "",
327 };
328
329 let params: Vec<String> = method
331 .params
332 .iter()
333 .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
334 .collect();
335
336 let all_params = if receiver.is_empty() {
337 params.join(", ")
338 } else if params.is_empty() {
339 receiver.to_string()
340 } else {
341 format!("{}, {}", receiver, params.join(", "))
342 };
343
344 let error_override = method.error_type.as_ref().map(|_| spec.error_path());
349 let ret = format_return_type(
350 &method.return_type,
351 error_override.as_deref(),
352 &spec.type_paths,
353 method.returns_ref,
354 );
355
356 let raw_body = if method.is_async {
358 generator.gen_async_method_body(method, spec)
359 } else {
360 generator.gen_sync_method_body(method, spec)
361 };
362
363 let raw_body_trimmed = raw_body.trim();
373 let body_is_static_slice = raw_body_trimmed.starts_with("self.") && raw_body_trimmed.ends_with("_strs");
374 let body = if method.returns_ref
375 && matches!(&method.return_type, alef_core::ir::TypeRef::Vec(inner) if matches!(inner.as_ref(), alef_core::ir::TypeRef::String))
376 {
377 if body_is_static_slice {
378 raw_body
379 } else {
380 format!(
381 "let __types: Vec<String> = {{ {raw_body} }};\n\
382 let __strs: Vec<&'static str> = __types.into_iter()\n\
383 .map(|s| -> &'static str {{ Box::leak(s.into_boxed_str()) }})\n\
384 .collect();\n\
385 Box::leak(__strs.into_boxed_slice())"
386 )
387 }
388 } else {
389 raw_body
390 };
391
392 let indented_body = body
394 .lines()
395 .map(|line| format!(" {line}"))
396 .collect::<Vec<_>>()
397 .join("\n");
398
399 methods_code.push_str(&crate::template_env::render(
400 "generators/trait_bridge/trait_method.jinja",
401 minijinja::context! {
402 async_kw => async_kw,
403 method_name => &method.name,
404 all_params => all_params,
405 ret => ret,
406 indented_body => &indented_body,
407 },
408 ));
409 }
410
411 crate::template_env::render(
412 "generators/trait_bridge/trait_impl.jinja",
413 minijinja::context! {
414 has_async_methods => has_async_methods,
415 async_trait_is_send => async_trait_is_send,
416 trait_path => trait_path,
417 wrapper_name => wrapper,
418 methods_code => methods_code,
419 },
420 )
421}
422
423pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
430 spec.bridge_config.register_fn.as_deref()?;
431 Some(generator.gen_registration_fn(spec))
432}
433
434pub fn gen_bridge_unregistration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
441 spec.bridge_config.unregister_fn.as_deref()?;
442 let body = generator.gen_unregistration_fn(spec);
443 if body.is_empty() { None } else { Some(body) }
444}
445
446pub fn gen_bridge_clear_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
453 spec.bridge_config.clear_fn.as_deref()?;
454 let body = generator.gen_clear_fn(spec);
455 if body.is_empty() { None } else { Some(body) }
456}
457
458pub fn host_function_path(spec: &TraitBridgeSpec, fn_name: &str) -> String {
476 if let Some(getter) = spec.bridge_config.registry_getter.as_deref() {
477 let last = getter.rsplit("::").next().unwrap_or("");
478 if let Some(sub) = last.strip_prefix("get_").and_then(|s| s.strip_suffix("_registry")) {
479 let prefix_end = getter.len() - last.len();
480 let prefix = &getter[..prefix_end];
481 let prefix = prefix.trim_end_matches("registry::");
482 return format!("{prefix}{sub}::{fn_name}");
483 }
484 }
485 format!("{}::plugins::{}", spec.core_import, fn_name)
486}
487
488pub struct BridgeOutput {
491 pub imports: Vec<String>,
493 pub code: String,
495}
496
497pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
503 let imports = generator.bridge_imports();
504 let mut out = String::with_capacity(4096);
505
506 out.push_str(&gen_bridge_wrapper_struct(spec, generator));
508 out.push_str("\n\n");
509
510 out.push_str(&gen_bridge_debug_impl(spec));
512 out.push_str("\n\n");
513
514 out.push_str(&generator.gen_constructor(spec));
516 out.push_str("\n\n");
517
518 if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
520 out.push_str(&plugin_impl);
521 out.push_str("\n\n");
522 }
523
524 out.push_str(&gen_bridge_trait_impl(spec, generator));
526
527 if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
529 out.push_str("\n\n");
530 out.push_str(®_fn_code);
531 }
532
533 if let Some(unreg_fn_code) = gen_bridge_unregistration_fn(spec, generator) {
536 out.push_str("\n\n");
537 out.push_str(&unreg_fn_code);
538 }
539
540 if let Some(clear_fn_code) = gen_bridge_clear_fn(spec, generator) {
543 out.push_str("\n\n");
544 out.push_str(&clear_fn_code);
545 }
546
547 BridgeOutput { imports, code: out }
548}
549
550pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
559 use alef_core::ir::{PrimitiveType, TypeRef};
560 match ty {
561 TypeRef::Primitive(p) => match p {
562 PrimitiveType::Bool => "bool",
563 PrimitiveType::U8 => "u8",
564 PrimitiveType::U16 => "u16",
565 PrimitiveType::U32 => "u32",
566 PrimitiveType::U64 => "u64",
567 PrimitiveType::I8 => "i8",
568 PrimitiveType::I16 => "i16",
569 PrimitiveType::I32 => "i32",
570 PrimitiveType::I64 => "i64",
571 PrimitiveType::F32 => "f32",
572 PrimitiveType::F64 => "f64",
573 PrimitiveType::Usize => "usize",
574 PrimitiveType::Isize => "isize",
575 }
576 .to_string(),
577 TypeRef::String => "String".to_string(),
578 TypeRef::Char => "char".to_string(),
579 TypeRef::Bytes => "Vec<u8>".to_string(),
580 TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
581 TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
582 TypeRef::Map(k, v) => format!(
583 "std::collections::HashMap<{}, {}>",
584 format_type_ref(k, type_paths),
585 format_type_ref(v, type_paths)
586 ),
587 TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
588 TypeRef::Path => "std::path::PathBuf".to_string(),
589 TypeRef::Unit => "()".to_string(),
590 TypeRef::Json => "serde_json::Value".to_string(),
591 TypeRef::Duration => "std::time::Duration".to_string(),
592 }
593}
594
595pub fn format_return_type(
605 ty: &alef_core::ir::TypeRef,
606 error_type: Option<&str>,
607 type_paths: &HashMap<String, String>,
608 returns_ref: bool,
609) -> String {
610 let inner = if returns_ref {
611 if let alef_core::ir::TypeRef::Vec(elem) = ty {
613 let elem_str = match elem.as_ref() {
614 alef_core::ir::TypeRef::String => "&str".to_string(),
615 alef_core::ir::TypeRef::Bytes => "&[u8]".to_string(),
616 alef_core::ir::TypeRef::Named(name) => {
617 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
618 format!("&{qualified}")
619 }
620 other => format_type_ref(other, type_paths),
621 };
622 format!("&[{elem_str}]")
623 } else {
624 format_type_ref(ty, type_paths)
625 }
626 } else {
627 format_type_ref(ty, type_paths)
628 };
629 match error_type {
630 Some(err) => format!("std::result::Result<{inner}, {err}>"),
631 None => inner,
632 }
633}
634
635pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
647 use alef_core::ir::TypeRef;
648 let base = if param.is_ref {
649 let mutability = if param.is_mut { "mut " } else { "" };
650 match ¶m.ty {
651 TypeRef::String => format!("&{mutability}str"),
652 TypeRef::Bytes => format!("&{mutability}[u8]"),
653 TypeRef::Path => format!("&{mutability}std::path::Path"),
654 TypeRef::Vec(inner) => format!("&{mutability}[{}]", format_type_ref(inner, type_paths)),
655 TypeRef::Named(name) => {
656 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
657 format!("&{mutability}{qualified}")
658 }
659 TypeRef::Optional(inner) => {
660 let inner_type_str = match inner.as_ref() {
664 TypeRef::String => format!("&{mutability}str"),
665 TypeRef::Bytes => format!("&{mutability}[u8]"),
666 TypeRef::Path => format!("&{mutability}std::path::Path"),
667 TypeRef::Vec(v) => format!("&{mutability}[{}]", format_type_ref(v, type_paths)),
668 TypeRef::Named(name) => {
669 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
670 format!("&{mutability}{qualified}")
671 }
672 other => format_type_ref(other, type_paths),
674 };
675 return format!("Option<{inner_type_str}>");
677 }
678 other => format_type_ref(other, type_paths),
680 }
681 } else {
682 format_type_ref(¶m.ty, type_paths)
683 };
684
685 if param.optional {
689 format!("Option<{base}>")
690 } else {
691 base
692 }
693}
694
695pub fn prim(p: &PrimitiveType) -> &'static str {
701 use PrimitiveType::*;
702 match p {
703 Bool => "bool",
704 U8 => "u8",
705 U16 => "u16",
706 U32 => "u32",
707 U64 => "u64",
708 I8 => "i8",
709 I16 => "i16",
710 I32 => "i32",
711 I64 => "i64",
712 F32 => "f32",
713 F64 => "f64",
714 Usize => "usize",
715 Isize => "isize",
716 }
717}
718
719pub fn bridge_param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
723 match ty {
724 TypeRef::Bytes if is_ref => "&[u8]".into(),
725 TypeRef::Bytes => "Vec<u8>".into(),
726 TypeRef::String if is_ref => "&str".into(),
727 TypeRef::String => "String".into(),
728 TypeRef::Path if is_ref => "&std::path::Path".into(),
729 TypeRef::Path => "std::path::PathBuf".into(),
730 TypeRef::Named(n) => {
731 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
732 if is_ref { format!("&{qualified}") } else { qualified }
733 }
734 TypeRef::Vec(inner) => format!("Vec<{}>", bridge_param_type(inner, ci, false, tp)),
735 TypeRef::Optional(inner) => format!("Option<{}>", bridge_param_type(inner, ci, false, tp)),
736 TypeRef::Primitive(p) => prim(p).into(),
737 TypeRef::Unit => "()".into(),
738 TypeRef::Char => "char".into(),
739 TypeRef::Map(k, v) => format!(
740 "std::collections::HashMap<{}, {}>",
741 bridge_param_type(k, ci, false, tp),
742 bridge_param_type(v, ci, false, tp)
743 ),
744 TypeRef::Json => "serde_json::Value".into(),
745 TypeRef::Duration => "std::time::Duration".into(),
746 }
747}
748
749pub fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
755 if optional && matches!(ty, TypeRef::String) && is_ref {
756 return "Option<&str>".to_string();
757 }
758 if is_ref {
759 if let TypeRef::Vec(inner) = ty {
760 let inner_str = bridge_param_type(inner, "", false, tp);
761 return format!("&[{inner_str}]");
762 }
763 }
764 bridge_param_type(ty, "", is_ref, tp)
765}
766
767pub fn find_bridge_param<'a>(
774 func: &FunctionDef,
775 bridges: &'a [TraitBridgeConfig],
776) -> Option<(usize, &'a TraitBridgeConfig)> {
777 for (idx, param) in func.params.iter().enumerate() {
778 let named = match ¶m.ty {
779 TypeRef::Named(n) => Some(n.as_str()),
780 TypeRef::Optional(inner) => {
781 if let TypeRef::Named(n) = inner.as_ref() {
782 Some(n.as_str())
783 } else {
784 None
785 }
786 }
787 _ => None,
788 };
789 for bridge in bridges {
790 if bridge.bind_via != BridgeBinding::FunctionParam {
791 continue;
792 }
793 if let Some(type_name) = named {
794 if bridge.type_alias.as_deref() == Some(type_name) {
795 return Some((idx, bridge));
796 }
797 }
798 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
799 return Some((idx, bridge));
800 }
801 }
802 }
803 None
804}
805
806#[derive(Debug, Clone)]
809pub struct BridgeFieldMatch<'a> {
810 pub param_index: usize,
812 pub param_name: String,
814 pub options_type: String,
816 pub param_is_optional: bool,
818 pub field_name: String,
820 pub field: &'a FieldDef,
822 pub bridge: &'a TraitBridgeConfig,
824}
825
826pub fn find_bridge_field<'a>(
837 func: &FunctionDef,
838 types: &'a [TypeDef],
839 bridges: &'a [TraitBridgeConfig],
840) -> Option<BridgeFieldMatch<'a>> {
841 fn unwrap_named(ty: &TypeRef) -> Option<(&str, bool)> {
842 match ty {
843 TypeRef::Named(n) => Some((n.as_str(), false)),
844 TypeRef::Optional(inner) => {
845 if let TypeRef::Named(n) = inner.as_ref() {
846 Some((n.as_str(), true))
847 } else {
848 None
849 }
850 }
851 _ => None,
852 }
853 }
854
855 for (idx, param) in func.params.iter().enumerate() {
856 let Some((type_name, is_optional)) = unwrap_named(¶m.ty) else {
857 continue;
858 };
859 let Some(type_def) = types.iter().find(|t| t.name == type_name) else {
860 continue;
861 };
862 for bridge in bridges {
863 if bridge.bind_via != BridgeBinding::OptionsField {
864 continue;
865 }
866 if bridge.options_type.as_deref() != Some(type_name) {
867 continue;
868 }
869 let field_name = bridge.resolved_options_field();
870 for field in &type_def.fields {
871 let matches_name = field_name.is_some_and(|n| field.name == n);
872 let matches_alias = bridge
873 .type_alias
874 .as_deref()
875 .is_some_and(|alias| field_type_matches_alias(&field.ty, alias));
876 if matches_name || matches_alias {
877 return Some(BridgeFieldMatch {
878 param_index: idx,
879 param_name: param.name.clone(),
880 options_type: type_name.to_string(),
881 param_is_optional: is_optional,
882 field_name: field.name.clone(),
883 field,
884 bridge,
885 });
886 }
887 }
888 }
889 }
890 None
891}
892
893fn field_type_matches_alias(field_ty: &TypeRef, alias: &str) -> bool {
896 match field_ty {
897 TypeRef::Named(n) => n == alias,
898 TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_type_matches_alias(inner, alias),
899 _ => false,
900 }
901}
902
903pub fn to_camel_case(s: &str) -> String {
905 let mut result = String::new();
906 let mut capitalize_next = false;
907 for ch in s.chars() {
908 if ch == '_' {
909 capitalize_next = true;
910 } else if capitalize_next {
911 result.push(ch.to_ascii_uppercase());
912 capitalize_next = false;
913 } else {
914 result.push(ch);
915 }
916 }
917 result
918}
919
920#[cfg(test)]
921mod tests {
922 use super::*;
923 use alef_core::config::TraitBridgeConfig;
924 use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
925
926 fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
931 TraitBridgeConfig {
932 trait_name: "OcrBackend".to_string(),
933 super_trait: super_trait.map(str::to_string),
934 registry_getter: None,
935 register_fn: register_fn.map(str::to_string),
936 unregister_fn: None,
937 clear_fn: None,
938 type_alias: None,
939 param_name: None,
940 register_extra_args: None,
941 exclude_languages: Vec::new(),
942 ffi_skip_methods: Vec::new(),
943 bind_via: BridgeBinding::FunctionParam,
944 options_type: None,
945 options_field: None,
946 context_type: None,
947 result_type: None,
948 }
949 }
950
951 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
952 TypeDef {
953 name: name.to_string(),
954 rust_path: rust_path.to_string(),
955 original_rust_path: rust_path.to_string(),
956 fields: vec![],
957 methods,
958 is_opaque: true,
959 is_clone: false,
960 is_copy: false,
961 doc: String::new(),
962 cfg: None,
963 is_trait: true,
964 has_default: false,
965 has_stripped_cfg_fields: false,
966 is_return_type: false,
967 serde_rename_all: None,
968 has_serde: false,
969 super_traits: vec![],
970 }
971 }
972
973 fn make_method(
974 name: &str,
975 params: Vec<ParamDef>,
976 return_type: TypeRef,
977 is_async: bool,
978 has_default_impl: bool,
979 trait_source: Option<&str>,
980 error_type: Option<&str>,
981 ) -> MethodDef {
982 MethodDef {
983 name: name.to_string(),
984 params,
985 return_type,
986 is_async,
987 is_static: false,
988 error_type: error_type.map(str::to_string),
989 doc: String::new(),
990 receiver: Some(ReceiverKind::Ref),
991 sanitized: false,
992 trait_source: trait_source.map(str::to_string),
993 returns_ref: false,
994 returns_cow: false,
995 return_newtype_wrapper: None,
996 has_default_impl,
997 }
998 }
999
1000 fn make_func(name: &str, params: Vec<ParamDef>) -> FunctionDef {
1001 FunctionDef {
1002 name: name.to_string(),
1003 rust_path: format!("mylib::{name}"),
1004 original_rust_path: String::new(),
1005 params,
1006 return_type: TypeRef::Unit,
1007 is_async: false,
1008 error_type: None,
1009 doc: String::new(),
1010 cfg: None,
1011 sanitized: false,
1012 return_sanitized: false,
1013 returns_ref: false,
1014 returns_cow: false,
1015 return_newtype_wrapper: None,
1016 }
1017 }
1018
1019 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1020 FieldDef {
1021 name: name.to_string(),
1022 ty,
1023 optional: false,
1024 default: None,
1025 doc: String::new(),
1026 sanitized: false,
1027 is_boxed: false,
1028 type_rust_path: None,
1029 cfg: None,
1030 typed_default: None,
1031 core_wrapper: Default::default(),
1032 vec_inner_core_wrapper: Default::default(),
1033 newtype_wrapper: None,
1034 serde_rename: None,
1035 serde_flatten: false,
1036 }
1037 }
1038
1039 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
1040 ParamDef {
1041 name: name.to_string(),
1042 ty,
1043 optional: false,
1044 default: None,
1045 sanitized: false,
1046 typed_default: None,
1047 is_ref,
1048 is_mut: false,
1049 newtype_wrapper: None,
1050 original_type: None,
1051 }
1052 }
1053
1054 fn make_spec<'a>(
1055 trait_def: &'a TypeDef,
1056 bridge_config: &'a TraitBridgeConfig,
1057 wrapper_prefix: &'a str,
1058 type_paths: HashMap<String, String>,
1059 ) -> TraitBridgeSpec<'a> {
1060 TraitBridgeSpec {
1061 trait_def,
1062 bridge_config,
1063 core_import: "mylib",
1064 wrapper_prefix,
1065 type_paths,
1066 error_type: "MyError".to_string(),
1067 error_constructor: "MyError::from({msg})".to_string(),
1068 }
1069 }
1070
1071 struct MockBridgeGenerator;
1076
1077 impl TraitBridgeGenerator for MockBridgeGenerator {
1078 fn foreign_object_type(&self) -> &str {
1079 "Py<PyAny>"
1080 }
1081
1082 fn bridge_imports(&self) -> Vec<String> {
1083 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
1084 }
1085
1086 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1087 format!("// sync body for {}", method.name)
1088 }
1089
1090 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1091 format!("// async body for {}", method.name)
1092 }
1093
1094 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
1095 format!(
1096 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
1097 spec.wrapper_name()
1098 )
1099 }
1100
1101 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
1102 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
1103 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
1104 }
1105 }
1106
1107 #[test]
1112 fn test_wrapper_name() {
1113 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1114 let config = make_trait_bridge_config(None, None);
1115 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1116 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
1117 }
1118
1119 #[test]
1120 fn test_trait_snake() {
1121 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1122 let config = make_trait_bridge_config(None, None);
1123 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1124 assert_eq!(spec.trait_snake(), "ocr_backend");
1125 }
1126
1127 #[test]
1128 fn test_trait_path_replaces_hyphens() {
1129 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
1130 let config = make_trait_bridge_config(None, None);
1131 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1132 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
1133 }
1134
1135 #[test]
1136 fn test_required_methods_filters_no_default_impl() {
1137 let methods = vec![
1138 make_method("process", vec![], TypeRef::String, false, false, None, None),
1139 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1140 make_method("detect", vec![], TypeRef::String, false, false, None, None),
1141 ];
1142 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1143 let config = make_trait_bridge_config(None, None);
1144 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1145 let required = spec.required_methods();
1146 assert_eq!(required.len(), 2);
1147 assert!(required.iter().any(|m| m.name == "process"));
1148 assert!(required.iter().any(|m| m.name == "detect"));
1149 }
1150
1151 #[test]
1152 fn test_optional_methods_filters_has_default_impl() {
1153 let methods = vec![
1154 make_method("process", vec![], TypeRef::String, false, false, None, None),
1155 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1156 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
1157 ];
1158 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1159 let config = make_trait_bridge_config(None, None);
1160 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1161 let optional = spec.optional_methods();
1162 assert_eq!(optional.len(), 2);
1163 assert!(optional.iter().any(|m| m.name == "initialize"));
1164 assert!(optional.iter().any(|m| m.name == "shutdown"));
1165 }
1166
1167 #[test]
1168 fn test_error_path() {
1169 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1170 let config = make_trait_bridge_config(None, None);
1171 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1172 assert_eq!(spec.error_path(), "mylib::MyError");
1173 }
1174
1175 #[test]
1180 fn test_format_type_ref_primitives() {
1181 let paths = HashMap::new();
1182 let cases: Vec<(TypeRef, &str)> = vec![
1183 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
1184 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
1185 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
1186 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
1187 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
1188 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
1189 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
1190 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
1191 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
1192 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
1193 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
1194 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
1195 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
1196 ];
1197 for (ty, expected) in cases {
1198 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
1199 }
1200 }
1201
1202 #[test]
1203 fn test_format_type_ref_string() {
1204 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
1205 }
1206
1207 #[test]
1208 fn test_format_type_ref_char() {
1209 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
1210 }
1211
1212 #[test]
1213 fn test_format_type_ref_bytes() {
1214 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
1215 }
1216
1217 #[test]
1218 fn test_format_type_ref_path() {
1219 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
1220 }
1221
1222 #[test]
1223 fn test_format_type_ref_unit() {
1224 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
1225 }
1226
1227 #[test]
1228 fn test_format_type_ref_json() {
1229 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
1230 }
1231
1232 #[test]
1233 fn test_format_type_ref_duration() {
1234 assert_eq!(
1235 format_type_ref(&TypeRef::Duration, &HashMap::new()),
1236 "std::time::Duration"
1237 );
1238 }
1239
1240 #[test]
1241 fn test_format_type_ref_optional() {
1242 let ty = TypeRef::Optional(Box::new(TypeRef::String));
1243 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
1244 }
1245
1246 #[test]
1247 fn test_format_type_ref_optional_nested() {
1248 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
1249 PrimitiveType::U32,
1250 )))));
1251 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
1252 }
1253
1254 #[test]
1255 fn test_format_type_ref_vec() {
1256 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
1257 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
1258 }
1259
1260 #[test]
1261 fn test_format_type_ref_vec_nested() {
1262 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
1263 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
1264 }
1265
1266 #[test]
1267 fn test_format_type_ref_map() {
1268 let ty = TypeRef::Map(
1269 Box::new(TypeRef::String),
1270 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
1271 );
1272 assert_eq!(
1273 format_type_ref(&ty, &HashMap::new()),
1274 "std::collections::HashMap<String, i64>"
1275 );
1276 }
1277
1278 #[test]
1279 fn test_format_type_ref_map_nested_value() {
1280 let ty = TypeRef::Map(
1281 Box::new(TypeRef::String),
1282 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
1283 );
1284 assert_eq!(
1285 format_type_ref(&ty, &HashMap::new()),
1286 "std::collections::HashMap<String, Vec<String>>"
1287 );
1288 }
1289
1290 #[test]
1291 fn test_format_type_ref_named_without_type_paths() {
1292 let ty = TypeRef::Named("Config".to_string());
1293 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
1294 }
1295
1296 #[test]
1297 fn test_format_type_ref_named_with_type_paths() {
1298 let ty = TypeRef::Named("Config".to_string());
1299 let mut paths = HashMap::new();
1300 paths.insert("Config".to_string(), "mylib::Config".to_string());
1301 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
1302 }
1303
1304 #[test]
1305 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
1306 let ty = TypeRef::Named("Unknown".to_string());
1307 let mut paths = HashMap::new();
1308 paths.insert("Other".to_string(), "mylib::Other".to_string());
1309 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
1310 }
1311
1312 #[test]
1317 fn test_format_param_type_string_ref() {
1318 let param = make_param("input", TypeRef::String, true);
1319 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
1320 }
1321
1322 #[test]
1323 fn test_format_param_type_string_owned() {
1324 let param = make_param("input", TypeRef::String, false);
1325 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
1326 }
1327
1328 #[test]
1329 fn test_format_param_type_bytes_ref() {
1330 let param = make_param("data", TypeRef::Bytes, true);
1331 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
1332 }
1333
1334 #[test]
1335 fn test_format_param_type_bytes_owned() {
1336 let param = make_param("data", TypeRef::Bytes, false);
1337 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
1338 }
1339
1340 #[test]
1341 fn test_format_param_type_path_ref() {
1342 let param = make_param("path", TypeRef::Path, true);
1343 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
1344 }
1345
1346 #[test]
1347 fn test_format_param_type_path_owned() {
1348 let param = make_param("path", TypeRef::Path, false);
1349 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
1350 }
1351
1352 #[test]
1353 fn test_format_param_type_vec_ref() {
1354 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
1355 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
1356 }
1357
1358 #[test]
1359 fn test_format_param_type_vec_owned() {
1360 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
1361 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
1362 }
1363
1364 #[test]
1365 fn test_format_param_type_named_ref_with_type_paths() {
1366 let mut paths = HashMap::new();
1367 paths.insert("Config".to_string(), "mylib::Config".to_string());
1368 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1369 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
1370 }
1371
1372 #[test]
1373 fn test_format_param_type_named_ref_without_type_paths() {
1374 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1375 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
1376 }
1377
1378 #[test]
1379 fn test_format_param_type_primitive_ref_passes_by_value() {
1380 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
1382 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
1383 }
1384
1385 #[test]
1386 fn test_format_param_type_unit_ref_passes_by_value() {
1387 let param = make_param("nothing", TypeRef::Unit, true);
1388 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
1389 }
1390
1391 #[test]
1396 fn test_format_return_type_without_error() {
1397 let result = format_return_type(&TypeRef::String, None, &HashMap::new(), false);
1398 assert_eq!(result, "String");
1399 }
1400
1401 #[test]
1402 fn test_format_return_type_with_error() {
1403 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new(), false);
1404 assert_eq!(result, "std::result::Result<String, MyError>");
1405 }
1406
1407 #[test]
1408 fn test_format_return_type_unit_with_error() {
1409 let result = format_return_type(
1410 &TypeRef::Unit,
1411 Some("Box<dyn std::error::Error>"),
1412 &HashMap::new(),
1413 false,
1414 );
1415 assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
1416 }
1417
1418 #[test]
1419 fn test_format_return_type_named_with_type_paths_and_error() {
1420 let mut paths = HashMap::new();
1421 paths.insert("Output".to_string(), "mylib::Output".to_string());
1422 let result = format_return_type(
1423 &TypeRef::Named("Output".to_string()),
1424 Some("mylib::MyError"),
1425 &paths,
1426 false,
1427 );
1428 assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
1429 }
1430
1431 #[test]
1432 fn test_format_return_type_vec_string_with_returns_ref() {
1433 let result = format_return_type(&TypeRef::Vec(Box::new(TypeRef::String)), None, &HashMap::new(), true);
1437 assert_eq!(result, "&[&str]", "Vec<String> + returns_ref must yield &[&str]");
1438 }
1439
1440 #[test]
1441 fn test_format_return_type_vec_no_returns_ref_unchanged() {
1442 let result = format_return_type(&TypeRef::Vec(Box::new(TypeRef::String)), None, &HashMap::new(), false);
1444 assert_eq!(
1445 result, "Vec<String>",
1446 "Vec<String> without returns_ref must stay Vec<String>"
1447 );
1448 }
1449
1450 #[test]
1455 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
1456 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1457 let config = make_trait_bridge_config(None, None);
1458 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1459 let generator = MockBridgeGenerator;
1460 let result = gen_bridge_wrapper_struct(&spec, &generator);
1461 assert!(
1462 result.contains("pub struct PyOcrBackendBridge"),
1463 "missing struct declaration in:\n{result}"
1464 );
1465 }
1466
1467 #[test]
1468 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
1469 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1470 let config = make_trait_bridge_config(None, None);
1471 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1472 let generator = MockBridgeGenerator;
1473 let result = gen_bridge_wrapper_struct(&spec, &generator);
1474 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
1475 }
1476
1477 #[test]
1478 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
1479 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1480 let config = make_trait_bridge_config(None, None);
1481 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1482 let generator = MockBridgeGenerator;
1483 let result = gen_bridge_wrapper_struct(&spec, &generator);
1484 assert!(
1485 result.contains("cached_name: String"),
1486 "missing cached_name field in:\n{result}"
1487 );
1488 }
1489
1490 #[test]
1495 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1496 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1497 let config = make_trait_bridge_config(None, None);
1498 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1499 let generator = MockBridgeGenerator;
1500 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1501 }
1502
1503 #[test]
1504 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1505 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1506 let config = make_trait_bridge_config(Some("Plugin"), None);
1507 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1508 let generator = MockBridgeGenerator;
1509 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1510 }
1511
1512 #[test]
1513 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1514 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1515 let config = make_trait_bridge_config(Some("Plugin"), None);
1516 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1517 let generator = MockBridgeGenerator;
1518 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1519 assert!(
1520 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1521 "missing qualified super-trait path in:\n{result}"
1522 );
1523 }
1524
1525 #[test]
1526 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1527 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1528 let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
1529 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1530 let generator = MockBridgeGenerator;
1531 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1532 assert!(
1533 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1534 "wrong super-trait path in:\n{result}"
1535 );
1536 }
1537
1538 #[test]
1539 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1540 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1541 let config = make_trait_bridge_config(Some("Plugin"), None);
1542 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1543 let generator = MockBridgeGenerator;
1544 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1545 assert!(
1546 result.contains("fn name(") && result.contains("cached_name"),
1547 "missing name() using cached_name in:\n{result}"
1548 );
1549 }
1550
1551 #[test]
1552 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1553 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1554 let config = make_trait_bridge_config(Some("Plugin"), None);
1555 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1556 let generator = MockBridgeGenerator;
1557 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1558 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1559 }
1560
1561 #[test]
1562 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1563 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1564 let config = make_trait_bridge_config(Some("Plugin"), None);
1565 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1566 let generator = MockBridgeGenerator;
1567 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1568 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1569 }
1570
1571 #[test]
1572 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1573 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1574 let config = make_trait_bridge_config(Some("Plugin"), None);
1575 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1576 let generator = MockBridgeGenerator;
1577 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1578 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1579 }
1580
1581 #[test]
1586 fn test_gen_bridge_trait_impl_includes_impl_header() {
1587 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1588 let config = make_trait_bridge_config(None, None);
1589 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1590 let generator = MockBridgeGenerator;
1591 let result = gen_bridge_trait_impl(&spec, &generator);
1592 assert!(
1593 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1594 "missing impl header in:\n{result}"
1595 );
1596 }
1597
1598 #[test]
1599 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1600 let methods = vec![make_method(
1601 "process",
1602 vec![],
1603 TypeRef::String,
1604 false,
1605 false,
1606 None,
1607 None,
1608 )];
1609 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1610 let config = make_trait_bridge_config(None, None);
1611 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1612 let generator = MockBridgeGenerator;
1613 let result = gen_bridge_trait_impl(&spec, &generator);
1614 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1615 }
1616
1617 #[test]
1618 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1619 let methods = vec![make_method(
1620 "process",
1621 vec![],
1622 TypeRef::String,
1623 false,
1624 false,
1625 None,
1626 None,
1627 )];
1628 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1629 let config = make_trait_bridge_config(None, None);
1630 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1631 let generator = MockBridgeGenerator;
1632 let result = gen_bridge_trait_impl(&spec, &generator);
1633 assert!(
1634 result.contains("// sync body for process"),
1635 "missing sync method body in:\n{result}"
1636 );
1637 }
1638
1639 #[test]
1640 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1641 let methods = vec![make_method(
1642 "process_async",
1643 vec![],
1644 TypeRef::String,
1645 true,
1646 false,
1647 None,
1648 None,
1649 )];
1650 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1651 let config = make_trait_bridge_config(None, None);
1652 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1653 let generator = MockBridgeGenerator;
1654 let result = gen_bridge_trait_impl(&spec, &generator);
1655 assert!(
1656 result.contains("// async body for process_async"),
1657 "missing async method body in:\n{result}"
1658 );
1659 assert!(
1660 result.contains("async fn process_async("),
1661 "missing async keyword in method signature in:\n{result}"
1662 );
1663 }
1664
1665 #[test]
1666 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1667 let methods = vec![
1669 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1670 make_method(
1671 "inherited_method",
1672 vec![],
1673 TypeRef::String,
1674 false,
1675 false,
1676 Some("other_crate::OtherTrait"),
1677 None,
1678 ),
1679 ];
1680 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1681 let config = make_trait_bridge_config(None, None);
1682 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1683 let generator = MockBridgeGenerator;
1684 let result = gen_bridge_trait_impl(&spec, &generator);
1685 assert!(
1686 result.contains("fn own_method("),
1687 "own method should be present in:\n{result}"
1688 );
1689 assert!(
1690 !result.contains("fn inherited_method("),
1691 "inherited method should be filtered out in:\n{result}"
1692 );
1693 }
1694
1695 #[test]
1696 fn test_gen_bridge_trait_impl_method_with_params() {
1697 let params = vec![
1698 make_param("input", TypeRef::String, true),
1699 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1700 ];
1701 let methods = vec![make_method(
1702 "process",
1703 params,
1704 TypeRef::String,
1705 false,
1706 false,
1707 None,
1708 None,
1709 )];
1710 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1711 let config = make_trait_bridge_config(None, None);
1712 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1713 let generator = MockBridgeGenerator;
1714 let result = gen_bridge_trait_impl(&spec, &generator);
1715 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1716 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1717 }
1718
1719 #[test]
1720 fn test_gen_bridge_trait_impl_return_type_with_error() {
1721 let methods = vec![make_method(
1722 "process",
1723 vec![],
1724 TypeRef::String,
1725 false,
1726 false,
1727 None,
1728 Some("MyError"),
1729 )];
1730 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1731 let config = make_trait_bridge_config(None, None);
1732 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1733 let generator = MockBridgeGenerator;
1734 let result = gen_bridge_trait_impl(&spec, &generator);
1735 assert!(
1736 result.contains("-> std::result::Result<String, mylib::MyError>"),
1737 "missing std::result::Result return type in:\n{result}"
1738 );
1739 }
1740
1741 #[test]
1746 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1747 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1748 let config = make_trait_bridge_config(None, None);
1749 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1750 let generator = MockBridgeGenerator;
1751 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1752 }
1753
1754 #[test]
1755 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1756 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1757 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1758 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1759 let generator = MockBridgeGenerator;
1760 let result = gen_bridge_registration_fn(&spec, &generator);
1761 assert!(result.is_some());
1762 let code = result.unwrap();
1763 assert!(
1764 code.contains("register_ocr_backend"),
1765 "missing register fn name in:\n{code}"
1766 );
1767 }
1768
1769 #[test]
1774 fn test_gen_bridge_all_includes_imports() {
1775 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1776 let config = make_trait_bridge_config(None, None);
1777 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1778 let generator = MockBridgeGenerator;
1779 let output = gen_bridge_all(&spec, &generator);
1780 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1781 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1782 }
1783
1784 #[test]
1785 fn test_gen_bridge_all_includes_wrapper_struct() {
1786 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1787 let config = make_trait_bridge_config(None, None);
1788 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1789 let generator = MockBridgeGenerator;
1790 let output = gen_bridge_all(&spec, &generator);
1791 assert!(
1792 output.code.contains("pub struct PyOcrBackendBridge"),
1793 "missing struct in:\n{}",
1794 output.code
1795 );
1796 }
1797
1798 #[test]
1799 fn test_gen_bridge_all_includes_constructor() {
1800 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1801 let config = make_trait_bridge_config(None, None);
1802 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1803 let generator = MockBridgeGenerator;
1804 let output = gen_bridge_all(&spec, &generator);
1805 assert!(
1806 output.code.contains("pub fn new("),
1807 "missing constructor in:\n{}",
1808 output.code
1809 );
1810 }
1811
1812 #[test]
1813 fn test_gen_bridge_all_includes_trait_impl() {
1814 let methods = vec![make_method(
1815 "process",
1816 vec![],
1817 TypeRef::String,
1818 false,
1819 false,
1820 None,
1821 None,
1822 )];
1823 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1824 let config = make_trait_bridge_config(None, None);
1825 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1826 let generator = MockBridgeGenerator;
1827 let output = gen_bridge_all(&spec, &generator);
1828 assert!(
1829 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1830 "missing trait impl in:\n{}",
1831 output.code
1832 );
1833 }
1834
1835 #[test]
1836 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1837 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1838 let config = make_trait_bridge_config(Some("Plugin"), None);
1839 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1840 let generator = MockBridgeGenerator;
1841 let output = gen_bridge_all(&spec, &generator);
1842 assert!(
1843 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1844 "missing plugin impl in:\n{}",
1845 output.code
1846 );
1847 }
1848
1849 #[test]
1850 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1851 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1852 let config = make_trait_bridge_config(None, None);
1853 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1854 let generator = MockBridgeGenerator;
1855 let output = gen_bridge_all(&spec, &generator);
1856 assert!(
1857 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1858 "unexpected plugin impl present without super_trait"
1859 );
1860 }
1861
1862 #[test]
1863 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1864 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1865 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1866 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1867 let generator = MockBridgeGenerator;
1868 let output = gen_bridge_all(&spec, &generator);
1869 assert!(
1870 output.code.contains("register_ocr_backend"),
1871 "missing registration fn in:\n{}",
1872 output.code
1873 );
1874 }
1875
1876 #[test]
1877 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1878 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1879 let config = make_trait_bridge_config(None, None);
1880 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1881 let generator = MockBridgeGenerator;
1882 let output = gen_bridge_all(&spec, &generator);
1883 assert!(
1884 !output.code.contains("register_ocr_backend"),
1885 "unexpected registration fn present:\n{}",
1886 output.code
1887 );
1888 }
1889
1890 #[test]
1891 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1892 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1893 let config = make_trait_bridge_config(None, None);
1894 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1895 let generator = MockBridgeGenerator;
1896 let output = gen_bridge_all(&spec, &generator);
1897 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1898 let impl_pos = output
1899 .code
1900 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1901 .unwrap();
1902 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1903 }
1904
1905 fn make_bridge(
1910 type_alias: Option<&str>,
1911 param_name: Option<&str>,
1912 bind_via: BridgeBinding,
1913 options_type: Option<&str>,
1914 options_field: Option<&str>,
1915 context_type: Option<&str>,
1916 result_type: Option<&str>,
1917 ) -> TraitBridgeConfig {
1918 TraitBridgeConfig {
1919 trait_name: "HtmlVisitor".to_string(),
1920 super_trait: None,
1921 registry_getter: None,
1922 register_fn: None,
1923 unregister_fn: None,
1924 clear_fn: None,
1925 type_alias: type_alias.map(str::to_string),
1926 param_name: param_name.map(str::to_string),
1927 register_extra_args: None,
1928 exclude_languages: vec![],
1929 ffi_skip_methods: Vec::new(),
1930 bind_via,
1931 options_type: options_type.map(str::to_string),
1932 options_field: options_field.map(str::to_string),
1933 context_type: context_type.map(str::to_string),
1934 result_type: result_type.map(str::to_string),
1935 }
1936 }
1937
1938 #[test]
1939 fn find_bridge_param_returns_first_param_match_in_function_param_mode() {
1940 let func = make_func(
1941 "convert",
1942 vec![
1943 make_param("html", TypeRef::String, true),
1944 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1945 ],
1946 );
1947 let bridges = vec![make_bridge(
1948 Some("VisitorHandle"),
1949 Some("visitor"),
1950 BridgeBinding::FunctionParam,
1951 None,
1952 None,
1953 None,
1954 None,
1955 )];
1956 let result = find_bridge_param(&func, &bridges).expect("bridge match");
1957 assert_eq!(result.0, 1);
1958 }
1959
1960 #[test]
1961 fn find_bridge_param_skips_options_field_bridges() {
1962 let func = make_func(
1963 "convert",
1964 vec![
1965 make_param("html", TypeRef::String, true),
1966 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1967 ],
1968 );
1969 let bridges = vec![make_bridge(
1970 Some("VisitorHandle"),
1971 Some("visitor"),
1972 BridgeBinding::OptionsField,
1973 Some("ConversionOptions"),
1974 Some("visitor"),
1975 None,
1976 None,
1977 )];
1978 assert!(
1979 find_bridge_param(&func, &bridges).is_none(),
1980 "bridges configured with bind_via=options_field must not be returned by find_bridge_param"
1981 );
1982 }
1983
1984 #[test]
1985 fn find_bridge_field_detects_field_via_alias() {
1986 let opts_type = TypeDef {
1987 name: "ConversionOptions".to_string(),
1988 rust_path: "mylib::ConversionOptions".to_string(),
1989 original_rust_path: String::new(),
1990 fields: vec![
1991 make_field("debug", TypeRef::Primitive(PrimitiveType::Bool)),
1992 make_field(
1993 "visitor",
1994 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
1995 ),
1996 ],
1997 methods: vec![],
1998 is_opaque: false,
1999 is_clone: true,
2000 is_copy: false,
2001 doc: String::new(),
2002 cfg: None,
2003 is_trait: false,
2004 has_default: true,
2005 has_stripped_cfg_fields: false,
2006 is_return_type: false,
2007 serde_rename_all: None,
2008 has_serde: false,
2009 super_traits: vec![],
2010 };
2011 let func = make_func(
2012 "convert",
2013 vec![
2014 make_param("html", TypeRef::String, true),
2015 make_param(
2016 "options",
2017 TypeRef::Optional(Box::new(TypeRef::Named("ConversionOptions".to_string()))),
2018 false,
2019 ),
2020 ],
2021 );
2022 let bridges = vec![make_bridge(
2023 Some("VisitorHandle"),
2024 Some("visitor"),
2025 BridgeBinding::OptionsField,
2026 Some("ConversionOptions"),
2027 None,
2028 None,
2029 None,
2030 )];
2031 let m = find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).expect("bridge field match");
2032 assert_eq!(m.param_index, 1);
2033 assert_eq!(m.param_name, "options");
2034 assert_eq!(m.options_type, "ConversionOptions");
2035 assert!(m.param_is_optional);
2036 assert_eq!(m.field_name, "visitor");
2037 }
2038
2039 #[test]
2040 fn find_bridge_field_returns_none_for_function_param_bridge() {
2041 let opts_type = TypeDef {
2042 name: "ConversionOptions".to_string(),
2043 rust_path: "mylib::ConversionOptions".to_string(),
2044 original_rust_path: String::new(),
2045 fields: vec![make_field(
2046 "visitor",
2047 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
2048 )],
2049 methods: vec![],
2050 is_opaque: false,
2051 is_clone: true,
2052 is_copy: false,
2053 doc: String::new(),
2054 cfg: None,
2055 is_trait: false,
2056 has_default: true,
2057 has_stripped_cfg_fields: false,
2058 is_return_type: false,
2059 serde_rename_all: None,
2060 has_serde: false,
2061 super_traits: vec![],
2062 };
2063 let func = make_func(
2064 "convert",
2065 vec![make_param(
2066 "options",
2067 TypeRef::Named("ConversionOptions".to_string()),
2068 false,
2069 )],
2070 );
2071 let bridges = vec![make_bridge(
2072 Some("VisitorHandle"),
2073 Some("visitor"),
2074 BridgeBinding::FunctionParam,
2075 None,
2076 None,
2077 None,
2078 None,
2079 )];
2080 assert!(find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).is_none());
2081 }
2082}