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 bind_via: BridgeBinding::FunctionParam,
943 options_type: None,
944 options_field: None,
945 }
946 }
947
948 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
949 TypeDef {
950 name: name.to_string(),
951 rust_path: rust_path.to_string(),
952 original_rust_path: rust_path.to_string(),
953 fields: vec![],
954 methods,
955 is_opaque: true,
956 is_clone: false,
957 is_copy: false,
958 doc: String::new(),
959 cfg: None,
960 is_trait: true,
961 has_default: false,
962 has_stripped_cfg_fields: false,
963 is_return_type: false,
964 serde_rename_all: None,
965 has_serde: false,
966 super_traits: vec![],
967 }
968 }
969
970 fn make_method(
971 name: &str,
972 params: Vec<ParamDef>,
973 return_type: TypeRef,
974 is_async: bool,
975 has_default_impl: bool,
976 trait_source: Option<&str>,
977 error_type: Option<&str>,
978 ) -> MethodDef {
979 MethodDef {
980 name: name.to_string(),
981 params,
982 return_type,
983 is_async,
984 is_static: false,
985 error_type: error_type.map(str::to_string),
986 doc: String::new(),
987 receiver: Some(ReceiverKind::Ref),
988 sanitized: false,
989 trait_source: trait_source.map(str::to_string),
990 returns_ref: false,
991 returns_cow: false,
992 return_newtype_wrapper: None,
993 has_default_impl,
994 }
995 }
996
997 fn make_func(name: &str, params: Vec<ParamDef>) -> FunctionDef {
998 FunctionDef {
999 name: name.to_string(),
1000 rust_path: format!("mylib::{name}"),
1001 original_rust_path: String::new(),
1002 params,
1003 return_type: TypeRef::Unit,
1004 is_async: false,
1005 error_type: None,
1006 doc: String::new(),
1007 cfg: None,
1008 sanitized: false,
1009 return_sanitized: false,
1010 returns_ref: false,
1011 returns_cow: false,
1012 return_newtype_wrapper: None,
1013 }
1014 }
1015
1016 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1017 FieldDef {
1018 name: name.to_string(),
1019 ty,
1020 optional: false,
1021 default: None,
1022 doc: String::new(),
1023 sanitized: false,
1024 is_boxed: false,
1025 type_rust_path: None,
1026 cfg: None,
1027 typed_default: None,
1028 core_wrapper: Default::default(),
1029 vec_inner_core_wrapper: Default::default(),
1030 newtype_wrapper: None,
1031 serde_rename: None,
1032 serde_flatten: false,
1033 }
1034 }
1035
1036 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
1037 ParamDef {
1038 name: name.to_string(),
1039 ty,
1040 optional: false,
1041 default: None,
1042 sanitized: false,
1043 typed_default: None,
1044 is_ref,
1045 is_mut: false,
1046 newtype_wrapper: None,
1047 original_type: None,
1048 }
1049 }
1050
1051 fn make_spec<'a>(
1052 trait_def: &'a TypeDef,
1053 bridge_config: &'a TraitBridgeConfig,
1054 wrapper_prefix: &'a str,
1055 type_paths: HashMap<String, String>,
1056 ) -> TraitBridgeSpec<'a> {
1057 TraitBridgeSpec {
1058 trait_def,
1059 bridge_config,
1060 core_import: "mylib",
1061 wrapper_prefix,
1062 type_paths,
1063 error_type: "MyError".to_string(),
1064 error_constructor: "MyError::from({msg})".to_string(),
1065 }
1066 }
1067
1068 struct MockBridgeGenerator;
1073
1074 impl TraitBridgeGenerator for MockBridgeGenerator {
1075 fn foreign_object_type(&self) -> &str {
1076 "Py<PyAny>"
1077 }
1078
1079 fn bridge_imports(&self) -> Vec<String> {
1080 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
1081 }
1082
1083 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1084 format!("// sync body for {}", method.name)
1085 }
1086
1087 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1088 format!("// async body for {}", method.name)
1089 }
1090
1091 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
1092 format!(
1093 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
1094 spec.wrapper_name()
1095 )
1096 }
1097
1098 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
1099 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
1100 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
1101 }
1102 }
1103
1104 #[test]
1109 fn test_wrapper_name() {
1110 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1111 let config = make_trait_bridge_config(None, None);
1112 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1113 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
1114 }
1115
1116 #[test]
1117 fn test_trait_snake() {
1118 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1119 let config = make_trait_bridge_config(None, None);
1120 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1121 assert_eq!(spec.trait_snake(), "ocr_backend");
1122 }
1123
1124 #[test]
1125 fn test_trait_path_replaces_hyphens() {
1126 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
1127 let config = make_trait_bridge_config(None, None);
1128 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1129 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
1130 }
1131
1132 #[test]
1133 fn test_required_methods_filters_no_default_impl() {
1134 let methods = vec![
1135 make_method("process", vec![], TypeRef::String, false, false, None, None),
1136 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1137 make_method("detect", vec![], TypeRef::String, false, false, None, None),
1138 ];
1139 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1140 let config = make_trait_bridge_config(None, None);
1141 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1142 let required = spec.required_methods();
1143 assert_eq!(required.len(), 2);
1144 assert!(required.iter().any(|m| m.name == "process"));
1145 assert!(required.iter().any(|m| m.name == "detect"));
1146 }
1147
1148 #[test]
1149 fn test_optional_methods_filters_has_default_impl() {
1150 let methods = vec![
1151 make_method("process", vec![], TypeRef::String, false, false, None, None),
1152 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1153 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
1154 ];
1155 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1156 let config = make_trait_bridge_config(None, None);
1157 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1158 let optional = spec.optional_methods();
1159 assert_eq!(optional.len(), 2);
1160 assert!(optional.iter().any(|m| m.name == "initialize"));
1161 assert!(optional.iter().any(|m| m.name == "shutdown"));
1162 }
1163
1164 #[test]
1165 fn test_error_path() {
1166 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1167 let config = make_trait_bridge_config(None, None);
1168 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1169 assert_eq!(spec.error_path(), "mylib::MyError");
1170 }
1171
1172 #[test]
1177 fn test_format_type_ref_primitives() {
1178 let paths = HashMap::new();
1179 let cases: Vec<(TypeRef, &str)> = vec![
1180 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
1181 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
1182 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
1183 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
1184 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
1185 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
1186 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
1187 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
1188 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
1189 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
1190 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
1191 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
1192 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
1193 ];
1194 for (ty, expected) in cases {
1195 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
1196 }
1197 }
1198
1199 #[test]
1200 fn test_format_type_ref_string() {
1201 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
1202 }
1203
1204 #[test]
1205 fn test_format_type_ref_char() {
1206 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
1207 }
1208
1209 #[test]
1210 fn test_format_type_ref_bytes() {
1211 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
1212 }
1213
1214 #[test]
1215 fn test_format_type_ref_path() {
1216 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
1217 }
1218
1219 #[test]
1220 fn test_format_type_ref_unit() {
1221 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
1222 }
1223
1224 #[test]
1225 fn test_format_type_ref_json() {
1226 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
1227 }
1228
1229 #[test]
1230 fn test_format_type_ref_duration() {
1231 assert_eq!(
1232 format_type_ref(&TypeRef::Duration, &HashMap::new()),
1233 "std::time::Duration"
1234 );
1235 }
1236
1237 #[test]
1238 fn test_format_type_ref_optional() {
1239 let ty = TypeRef::Optional(Box::new(TypeRef::String));
1240 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
1241 }
1242
1243 #[test]
1244 fn test_format_type_ref_optional_nested() {
1245 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
1246 PrimitiveType::U32,
1247 )))));
1248 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
1249 }
1250
1251 #[test]
1252 fn test_format_type_ref_vec() {
1253 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
1254 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
1255 }
1256
1257 #[test]
1258 fn test_format_type_ref_vec_nested() {
1259 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
1260 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
1261 }
1262
1263 #[test]
1264 fn test_format_type_ref_map() {
1265 let ty = TypeRef::Map(
1266 Box::new(TypeRef::String),
1267 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
1268 );
1269 assert_eq!(
1270 format_type_ref(&ty, &HashMap::new()),
1271 "std::collections::HashMap<String, i64>"
1272 );
1273 }
1274
1275 #[test]
1276 fn test_format_type_ref_map_nested_value() {
1277 let ty = TypeRef::Map(
1278 Box::new(TypeRef::String),
1279 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
1280 );
1281 assert_eq!(
1282 format_type_ref(&ty, &HashMap::new()),
1283 "std::collections::HashMap<String, Vec<String>>"
1284 );
1285 }
1286
1287 #[test]
1288 fn test_format_type_ref_named_without_type_paths() {
1289 let ty = TypeRef::Named("Config".to_string());
1290 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
1291 }
1292
1293 #[test]
1294 fn test_format_type_ref_named_with_type_paths() {
1295 let ty = TypeRef::Named("Config".to_string());
1296 let mut paths = HashMap::new();
1297 paths.insert("Config".to_string(), "mylib::Config".to_string());
1298 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
1299 }
1300
1301 #[test]
1302 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
1303 let ty = TypeRef::Named("Unknown".to_string());
1304 let mut paths = HashMap::new();
1305 paths.insert("Other".to_string(), "mylib::Other".to_string());
1306 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
1307 }
1308
1309 #[test]
1314 fn test_format_param_type_string_ref() {
1315 let param = make_param("input", TypeRef::String, true);
1316 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
1317 }
1318
1319 #[test]
1320 fn test_format_param_type_string_owned() {
1321 let param = make_param("input", TypeRef::String, false);
1322 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
1323 }
1324
1325 #[test]
1326 fn test_format_param_type_bytes_ref() {
1327 let param = make_param("data", TypeRef::Bytes, true);
1328 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
1329 }
1330
1331 #[test]
1332 fn test_format_param_type_bytes_owned() {
1333 let param = make_param("data", TypeRef::Bytes, false);
1334 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
1335 }
1336
1337 #[test]
1338 fn test_format_param_type_path_ref() {
1339 let param = make_param("path", TypeRef::Path, true);
1340 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
1341 }
1342
1343 #[test]
1344 fn test_format_param_type_path_owned() {
1345 let param = make_param("path", TypeRef::Path, false);
1346 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
1347 }
1348
1349 #[test]
1350 fn test_format_param_type_vec_ref() {
1351 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
1352 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
1353 }
1354
1355 #[test]
1356 fn test_format_param_type_vec_owned() {
1357 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
1358 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
1359 }
1360
1361 #[test]
1362 fn test_format_param_type_named_ref_with_type_paths() {
1363 let mut paths = HashMap::new();
1364 paths.insert("Config".to_string(), "mylib::Config".to_string());
1365 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1366 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
1367 }
1368
1369 #[test]
1370 fn test_format_param_type_named_ref_without_type_paths() {
1371 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1372 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
1373 }
1374
1375 #[test]
1376 fn test_format_param_type_primitive_ref_passes_by_value() {
1377 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
1379 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
1380 }
1381
1382 #[test]
1383 fn test_format_param_type_unit_ref_passes_by_value() {
1384 let param = make_param("nothing", TypeRef::Unit, true);
1385 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
1386 }
1387
1388 #[test]
1393 fn test_format_return_type_without_error() {
1394 let result = format_return_type(&TypeRef::String, None, &HashMap::new(), false);
1395 assert_eq!(result, "String");
1396 }
1397
1398 #[test]
1399 fn test_format_return_type_with_error() {
1400 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new(), false);
1401 assert_eq!(result, "std::result::Result<String, MyError>");
1402 }
1403
1404 #[test]
1405 fn test_format_return_type_unit_with_error() {
1406 let result = format_return_type(
1407 &TypeRef::Unit,
1408 Some("Box<dyn std::error::Error>"),
1409 &HashMap::new(),
1410 false,
1411 );
1412 assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
1413 }
1414
1415 #[test]
1416 fn test_format_return_type_named_with_type_paths_and_error() {
1417 let mut paths = HashMap::new();
1418 paths.insert("Output".to_string(), "mylib::Output".to_string());
1419 let result = format_return_type(
1420 &TypeRef::Named("Output".to_string()),
1421 Some("mylib::MyError"),
1422 &paths,
1423 false,
1424 );
1425 assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
1426 }
1427
1428 #[test]
1429 fn test_format_return_type_vec_string_with_returns_ref() {
1430 let result = format_return_type(&TypeRef::Vec(Box::new(TypeRef::String)), None, &HashMap::new(), true);
1434 assert_eq!(result, "&[&str]", "Vec<String> + returns_ref must yield &[&str]");
1435 }
1436
1437 #[test]
1438 fn test_format_return_type_vec_no_returns_ref_unchanged() {
1439 let result = format_return_type(&TypeRef::Vec(Box::new(TypeRef::String)), None, &HashMap::new(), false);
1441 assert_eq!(
1442 result, "Vec<String>",
1443 "Vec<String> without returns_ref must stay Vec<String>"
1444 );
1445 }
1446
1447 #[test]
1452 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
1453 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1454 let config = make_trait_bridge_config(None, None);
1455 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1456 let generator = MockBridgeGenerator;
1457 let result = gen_bridge_wrapper_struct(&spec, &generator);
1458 assert!(
1459 result.contains("pub struct PyOcrBackendBridge"),
1460 "missing struct declaration in:\n{result}"
1461 );
1462 }
1463
1464 #[test]
1465 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
1466 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1467 let config = make_trait_bridge_config(None, None);
1468 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1469 let generator = MockBridgeGenerator;
1470 let result = gen_bridge_wrapper_struct(&spec, &generator);
1471 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
1472 }
1473
1474 #[test]
1475 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
1476 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1477 let config = make_trait_bridge_config(None, None);
1478 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1479 let generator = MockBridgeGenerator;
1480 let result = gen_bridge_wrapper_struct(&spec, &generator);
1481 assert!(
1482 result.contains("cached_name: String"),
1483 "missing cached_name field in:\n{result}"
1484 );
1485 }
1486
1487 #[test]
1492 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1493 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1494 let config = make_trait_bridge_config(None, None);
1495 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1496 let generator = MockBridgeGenerator;
1497 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1498 }
1499
1500 #[test]
1501 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1502 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1503 let config = make_trait_bridge_config(Some("Plugin"), None);
1504 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1505 let generator = MockBridgeGenerator;
1506 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1507 }
1508
1509 #[test]
1510 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1511 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1512 let config = make_trait_bridge_config(Some("Plugin"), None);
1513 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1514 let generator = MockBridgeGenerator;
1515 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1516 assert!(
1517 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1518 "missing qualified super-trait path in:\n{result}"
1519 );
1520 }
1521
1522 #[test]
1523 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1524 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1525 let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
1526 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1527 let generator = MockBridgeGenerator;
1528 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1529 assert!(
1530 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1531 "wrong super-trait path in:\n{result}"
1532 );
1533 }
1534
1535 #[test]
1536 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1537 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1538 let config = make_trait_bridge_config(Some("Plugin"), None);
1539 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1540 let generator = MockBridgeGenerator;
1541 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1542 assert!(
1543 result.contains("fn name(") && result.contains("cached_name"),
1544 "missing name() using cached_name in:\n{result}"
1545 );
1546 }
1547
1548 #[test]
1549 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1550 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1551 let config = make_trait_bridge_config(Some("Plugin"), None);
1552 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1553 let generator = MockBridgeGenerator;
1554 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1555 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1556 }
1557
1558 #[test]
1559 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1560 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1561 let config = make_trait_bridge_config(Some("Plugin"), None);
1562 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1563 let generator = MockBridgeGenerator;
1564 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1565 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1566 }
1567
1568 #[test]
1569 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1570 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1571 let config = make_trait_bridge_config(Some("Plugin"), None);
1572 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1573 let generator = MockBridgeGenerator;
1574 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1575 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1576 }
1577
1578 #[test]
1583 fn test_gen_bridge_trait_impl_includes_impl_header() {
1584 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1585 let config = make_trait_bridge_config(None, None);
1586 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1587 let generator = MockBridgeGenerator;
1588 let result = gen_bridge_trait_impl(&spec, &generator);
1589 assert!(
1590 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1591 "missing impl header in:\n{result}"
1592 );
1593 }
1594
1595 #[test]
1596 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1597 let methods = vec![make_method(
1598 "process",
1599 vec![],
1600 TypeRef::String,
1601 false,
1602 false,
1603 None,
1604 None,
1605 )];
1606 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1607 let config = make_trait_bridge_config(None, None);
1608 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1609 let generator = MockBridgeGenerator;
1610 let result = gen_bridge_trait_impl(&spec, &generator);
1611 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1612 }
1613
1614 #[test]
1615 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1616 let methods = vec![make_method(
1617 "process",
1618 vec![],
1619 TypeRef::String,
1620 false,
1621 false,
1622 None,
1623 None,
1624 )];
1625 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1626 let config = make_trait_bridge_config(None, None);
1627 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1628 let generator = MockBridgeGenerator;
1629 let result = gen_bridge_trait_impl(&spec, &generator);
1630 assert!(
1631 result.contains("// sync body for process"),
1632 "missing sync method body in:\n{result}"
1633 );
1634 }
1635
1636 #[test]
1637 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1638 let methods = vec![make_method(
1639 "process_async",
1640 vec![],
1641 TypeRef::String,
1642 true,
1643 false,
1644 None,
1645 None,
1646 )];
1647 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1648 let config = make_trait_bridge_config(None, None);
1649 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1650 let generator = MockBridgeGenerator;
1651 let result = gen_bridge_trait_impl(&spec, &generator);
1652 assert!(
1653 result.contains("// async body for process_async"),
1654 "missing async method body in:\n{result}"
1655 );
1656 assert!(
1657 result.contains("async fn process_async("),
1658 "missing async keyword in method signature in:\n{result}"
1659 );
1660 }
1661
1662 #[test]
1663 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1664 let methods = vec![
1666 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1667 make_method(
1668 "inherited_method",
1669 vec![],
1670 TypeRef::String,
1671 false,
1672 false,
1673 Some("other_crate::OtherTrait"),
1674 None,
1675 ),
1676 ];
1677 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1678 let config = make_trait_bridge_config(None, None);
1679 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1680 let generator = MockBridgeGenerator;
1681 let result = gen_bridge_trait_impl(&spec, &generator);
1682 assert!(
1683 result.contains("fn own_method("),
1684 "own method should be present in:\n{result}"
1685 );
1686 assert!(
1687 !result.contains("fn inherited_method("),
1688 "inherited method should be filtered out in:\n{result}"
1689 );
1690 }
1691
1692 #[test]
1693 fn test_gen_bridge_trait_impl_method_with_params() {
1694 let params = vec![
1695 make_param("input", TypeRef::String, true),
1696 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1697 ];
1698 let methods = vec![make_method(
1699 "process",
1700 params,
1701 TypeRef::String,
1702 false,
1703 false,
1704 None,
1705 None,
1706 )];
1707 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1708 let config = make_trait_bridge_config(None, None);
1709 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1710 let generator = MockBridgeGenerator;
1711 let result = gen_bridge_trait_impl(&spec, &generator);
1712 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1713 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1714 }
1715
1716 #[test]
1717 fn test_gen_bridge_trait_impl_return_type_with_error() {
1718 let methods = vec![make_method(
1719 "process",
1720 vec![],
1721 TypeRef::String,
1722 false,
1723 false,
1724 None,
1725 Some("MyError"),
1726 )];
1727 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1728 let config = make_trait_bridge_config(None, None);
1729 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1730 let generator = MockBridgeGenerator;
1731 let result = gen_bridge_trait_impl(&spec, &generator);
1732 assert!(
1733 result.contains("-> std::result::Result<String, mylib::MyError>"),
1734 "missing std::result::Result return type in:\n{result}"
1735 );
1736 }
1737
1738 #[test]
1743 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1744 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1745 let config = make_trait_bridge_config(None, None);
1746 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1747 let generator = MockBridgeGenerator;
1748 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1749 }
1750
1751 #[test]
1752 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1753 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1754 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1755 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1756 let generator = MockBridgeGenerator;
1757 let result = gen_bridge_registration_fn(&spec, &generator);
1758 assert!(result.is_some());
1759 let code = result.unwrap();
1760 assert!(
1761 code.contains("register_ocr_backend"),
1762 "missing register fn name in:\n{code}"
1763 );
1764 }
1765
1766 #[test]
1771 fn test_gen_bridge_all_includes_imports() {
1772 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1773 let config = make_trait_bridge_config(None, None);
1774 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1775 let generator = MockBridgeGenerator;
1776 let output = gen_bridge_all(&spec, &generator);
1777 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1778 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1779 }
1780
1781 #[test]
1782 fn test_gen_bridge_all_includes_wrapper_struct() {
1783 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1784 let config = make_trait_bridge_config(None, None);
1785 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1786 let generator = MockBridgeGenerator;
1787 let output = gen_bridge_all(&spec, &generator);
1788 assert!(
1789 output.code.contains("pub struct PyOcrBackendBridge"),
1790 "missing struct in:\n{}",
1791 output.code
1792 );
1793 }
1794
1795 #[test]
1796 fn test_gen_bridge_all_includes_constructor() {
1797 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1798 let config = make_trait_bridge_config(None, None);
1799 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1800 let generator = MockBridgeGenerator;
1801 let output = gen_bridge_all(&spec, &generator);
1802 assert!(
1803 output.code.contains("pub fn new("),
1804 "missing constructor in:\n{}",
1805 output.code
1806 );
1807 }
1808
1809 #[test]
1810 fn test_gen_bridge_all_includes_trait_impl() {
1811 let methods = vec![make_method(
1812 "process",
1813 vec![],
1814 TypeRef::String,
1815 false,
1816 false,
1817 None,
1818 None,
1819 )];
1820 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1821 let config = make_trait_bridge_config(None, None);
1822 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1823 let generator = MockBridgeGenerator;
1824 let output = gen_bridge_all(&spec, &generator);
1825 assert!(
1826 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1827 "missing trait impl in:\n{}",
1828 output.code
1829 );
1830 }
1831
1832 #[test]
1833 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1834 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1835 let config = make_trait_bridge_config(Some("Plugin"), None);
1836 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1837 let generator = MockBridgeGenerator;
1838 let output = gen_bridge_all(&spec, &generator);
1839 assert!(
1840 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1841 "missing plugin impl in:\n{}",
1842 output.code
1843 );
1844 }
1845
1846 #[test]
1847 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1848 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1849 let config = make_trait_bridge_config(None, None);
1850 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1851 let generator = MockBridgeGenerator;
1852 let output = gen_bridge_all(&spec, &generator);
1853 assert!(
1854 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1855 "unexpected plugin impl present without super_trait"
1856 );
1857 }
1858
1859 #[test]
1860 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1861 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1862 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1863 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1864 let generator = MockBridgeGenerator;
1865 let output = gen_bridge_all(&spec, &generator);
1866 assert!(
1867 output.code.contains("register_ocr_backend"),
1868 "missing registration fn in:\n{}",
1869 output.code
1870 );
1871 }
1872
1873 #[test]
1874 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1875 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1876 let config = make_trait_bridge_config(None, None);
1877 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1878 let generator = MockBridgeGenerator;
1879 let output = gen_bridge_all(&spec, &generator);
1880 assert!(
1881 !output.code.contains("register_ocr_backend"),
1882 "unexpected registration fn present:\n{}",
1883 output.code
1884 );
1885 }
1886
1887 #[test]
1888 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1889 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1890 let config = make_trait_bridge_config(None, None);
1891 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1892 let generator = MockBridgeGenerator;
1893 let output = gen_bridge_all(&spec, &generator);
1894 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1895 let impl_pos = output
1896 .code
1897 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1898 .unwrap();
1899 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1900 }
1901
1902 fn make_bridge(
1907 type_alias: Option<&str>,
1908 param_name: Option<&str>,
1909 bind_via: BridgeBinding,
1910 options_type: Option<&str>,
1911 options_field: Option<&str>,
1912 ) -> TraitBridgeConfig {
1913 TraitBridgeConfig {
1914 trait_name: "HtmlVisitor".to_string(),
1915 super_trait: None,
1916 registry_getter: None,
1917 register_fn: None,
1918 unregister_fn: None,
1919 clear_fn: None,
1920 type_alias: type_alias.map(str::to_string),
1921 param_name: param_name.map(str::to_string),
1922 register_extra_args: None,
1923 exclude_languages: vec![],
1924 bind_via,
1925 options_type: options_type.map(str::to_string),
1926 options_field: options_field.map(str::to_string),
1927 }
1928 }
1929
1930 #[test]
1931 fn find_bridge_param_returns_first_param_match_in_function_param_mode() {
1932 let func = make_func(
1933 "convert",
1934 vec![
1935 make_param("html", TypeRef::String, true),
1936 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1937 ],
1938 );
1939 let bridges = vec![make_bridge(
1940 Some("VisitorHandle"),
1941 Some("visitor"),
1942 BridgeBinding::FunctionParam,
1943 None,
1944 None,
1945 )];
1946 let result = find_bridge_param(&func, &bridges).expect("bridge match");
1947 assert_eq!(result.0, 1);
1948 }
1949
1950 #[test]
1951 fn find_bridge_param_skips_options_field_bridges() {
1952 let func = make_func(
1953 "convert",
1954 vec![
1955 make_param("html", TypeRef::String, true),
1956 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1957 ],
1958 );
1959 let bridges = vec![make_bridge(
1960 Some("VisitorHandle"),
1961 Some("visitor"),
1962 BridgeBinding::OptionsField,
1963 Some("ConversionOptions"),
1964 Some("visitor"),
1965 )];
1966 assert!(
1967 find_bridge_param(&func, &bridges).is_none(),
1968 "bridges configured with bind_via=options_field must not be returned by find_bridge_param"
1969 );
1970 }
1971
1972 #[test]
1973 fn find_bridge_field_detects_field_via_alias() {
1974 let opts_type = TypeDef {
1975 name: "ConversionOptions".to_string(),
1976 rust_path: "mylib::ConversionOptions".to_string(),
1977 original_rust_path: String::new(),
1978 fields: vec![
1979 make_field("debug", TypeRef::Primitive(PrimitiveType::Bool)),
1980 make_field(
1981 "visitor",
1982 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
1983 ),
1984 ],
1985 methods: vec![],
1986 is_opaque: false,
1987 is_clone: true,
1988 is_copy: false,
1989 doc: String::new(),
1990 cfg: None,
1991 is_trait: false,
1992 has_default: true,
1993 has_stripped_cfg_fields: false,
1994 is_return_type: false,
1995 serde_rename_all: None,
1996 has_serde: false,
1997 super_traits: vec![],
1998 };
1999 let func = make_func(
2000 "convert",
2001 vec![
2002 make_param("html", TypeRef::String, true),
2003 make_param(
2004 "options",
2005 TypeRef::Optional(Box::new(TypeRef::Named("ConversionOptions".to_string()))),
2006 false,
2007 ),
2008 ],
2009 );
2010 let bridges = vec![make_bridge(
2011 Some("VisitorHandle"),
2012 Some("visitor"),
2013 BridgeBinding::OptionsField,
2014 Some("ConversionOptions"),
2015 None,
2016 )];
2017 let m = find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).expect("bridge field match");
2018 assert_eq!(m.param_index, 1);
2019 assert_eq!(m.param_name, "options");
2020 assert_eq!(m.options_type, "ConversionOptions");
2021 assert!(m.param_is_optional);
2022 assert_eq!(m.field_name, "visitor");
2023 }
2024
2025 #[test]
2026 fn find_bridge_field_returns_none_for_function_param_bridge() {
2027 let opts_type = TypeDef {
2028 name: "ConversionOptions".to_string(),
2029 rust_path: "mylib::ConversionOptions".to_string(),
2030 original_rust_path: String::new(),
2031 fields: vec![make_field(
2032 "visitor",
2033 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
2034 )],
2035 methods: vec![],
2036 is_opaque: false,
2037 is_clone: true,
2038 is_copy: false,
2039 doc: String::new(),
2040 cfg: None,
2041 is_trait: false,
2042 has_default: true,
2043 has_stripped_cfg_fields: false,
2044 is_return_type: false,
2045 serde_rename_all: None,
2046 has_serde: false,
2047 super_traits: vec![],
2048 };
2049 let func = make_func(
2050 "convert",
2051 vec![make_param(
2052 "options",
2053 TypeRef::Named("ConversionOptions".to_string()),
2054 false,
2055 )],
2056 );
2057 let bridges = vec![make_bridge(
2058 Some("VisitorHandle"),
2059 Some("visitor"),
2060 BridgeBinding::FunctionParam,
2061 None,
2062 None,
2063 )];
2064 assert!(find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).is_none());
2065 }
2066}