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 format!(
178 "impl std::fmt::Debug for {wrapper} {{\n fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{\n write!(f, \"{wrapper}\")\n }}\n}}"
179 )
180}
181
182pub fn gen_bridge_plugin_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
190 let super_trait_name = spec.bridge_config.super_trait.as_deref()?;
191
192 let wrapper = spec.wrapper_name();
193 let core_import = spec.core_import;
194
195 let super_trait_path = if super_trait_name.contains("::") {
197 super_trait_name.to_string()
198 } else {
199 format!("{core_import}::{super_trait_name}")
200 };
201
202 let error_path = spec.error_path();
206
207 let version_method = MethodDef {
209 name: "version".to_string(),
210 params: vec![],
211 return_type: alef_core::ir::TypeRef::String,
212 is_async: false,
213 is_static: false,
214 error_type: None,
215 doc: String::new(),
216 receiver: Some(alef_core::ir::ReceiverKind::Ref),
217 sanitized: false,
218 trait_source: None,
219 returns_ref: false,
220 returns_cow: false,
221 return_newtype_wrapper: None,
222 has_default_impl: false,
223 };
224 let version_body = generator.gen_sync_method_body(&version_method, spec);
225
226 let init_method = MethodDef {
228 name: "initialize".to_string(),
229 params: vec![],
230 return_type: alef_core::ir::TypeRef::Unit,
231 is_async: false,
232 is_static: false,
233 error_type: Some(error_path.clone()),
234 doc: String::new(),
235 receiver: Some(alef_core::ir::ReceiverKind::Ref),
236 sanitized: false,
237 trait_source: None,
238 returns_ref: false,
239 returns_cow: false,
240 return_newtype_wrapper: None,
241 has_default_impl: true,
242 };
243 let init_body = generator.gen_sync_method_body(&init_method, spec);
244
245 let shutdown_method = MethodDef {
247 name: "shutdown".to_string(),
248 params: vec![],
249 return_type: alef_core::ir::TypeRef::Unit,
250 is_async: false,
251 is_static: false,
252 error_type: Some(error_path.clone()),
253 doc: String::new(),
254 receiver: Some(alef_core::ir::ReceiverKind::Ref),
255 sanitized: false,
256 trait_source: None,
257 returns_ref: false,
258 returns_cow: false,
259 return_newtype_wrapper: None,
260 has_default_impl: true,
261 };
262 let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
263
264 let version_lines: Vec<&str> = version_body.lines().collect();
266 let init_lines: Vec<&str> = init_body.lines().collect();
267 let shutdown_lines: Vec<&str> = shutdown_body.lines().collect();
268
269 Some(crate::template_env::render(
270 "generators/trait_bridge/plugin_impl.jinja",
271 minijinja::context! {
272 super_trait_path => super_trait_path,
273 wrapper_name => wrapper,
274 error_path => error_path,
275 version_lines => version_lines,
276 init_lines => init_lines,
277 shutdown_lines => shutdown_lines,
278 },
279 ))
280}
281
282pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
288 let wrapper = spec.wrapper_name();
289 let trait_path = spec.trait_path();
290
291 let has_async_methods = spec
294 .trait_def
295 .methods
296 .iter()
297 .any(|m| m.is_async && m.trait_source.is_none() && !m.has_default_impl);
298 let async_trait_is_send = generator.async_trait_is_send();
299
300 let own_methods: Vec<_> = spec
304 .trait_def
305 .methods
306 .iter()
307 .filter(|m| m.trait_source.is_none() && !m.has_default_impl)
308 .collect();
309
310 let mut methods_code = String::with_capacity(1024);
312 for (i, method) in own_methods.iter().enumerate() {
313 if i > 0 {
314 methods_code.push_str("\n\n");
315 }
316
317 let async_kw = if method.is_async { "async " } else { "" };
319 let receiver = match &method.receiver {
320 Some(alef_core::ir::ReceiverKind::Ref) => "&self",
321 Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
322 Some(alef_core::ir::ReceiverKind::Owned) => "self",
323 None => "",
324 };
325
326 let params: Vec<String> = method
328 .params
329 .iter()
330 .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
331 .collect();
332
333 let all_params = if receiver.is_empty() {
334 params.join(", ")
335 } else if params.is_empty() {
336 receiver.to_string()
337 } else {
338 format!("{}, {}", receiver, params.join(", "))
339 };
340
341 let error_override = method.error_type.as_ref().map(|_| spec.error_path());
346 let ret = format_return_type(
347 &method.return_type,
348 error_override.as_deref(),
349 &spec.type_paths,
350 method.returns_ref,
351 );
352
353 let raw_body = if method.is_async {
355 generator.gen_async_method_body(method, spec)
356 } else {
357 generator.gen_sync_method_body(method, spec)
358 };
359
360 let body = if method.returns_ref
366 && matches!(&method.return_type, alef_core::ir::TypeRef::Vec(inner) if matches!(inner.as_ref(), alef_core::ir::TypeRef::String))
367 {
368 format!(
369 "let __types: Vec<String> = {{ {raw_body} }};\n\
370 let __strs: Vec<&'static str> = __types.into_iter()\n\
371 .map(|s| -> &'static str {{ Box::leak(s.into_boxed_str()) }})\n\
372 .collect();\n\
373 Box::leak(__strs.into_boxed_slice())"
374 )
375 } else {
376 raw_body
377 };
378
379 let indented_body = body
381 .lines()
382 .map(|line| format!(" {line}"))
383 .collect::<Vec<_>>()
384 .join("\n");
385
386 methods_code.push_str(&crate::template_env::render(
387 "generators/trait_bridge/trait_method.jinja",
388 minijinja::context! {
389 async_kw => async_kw,
390 method_name => &method.name,
391 all_params => all_params,
392 ret => ret,
393 indented_body => &indented_body,
394 },
395 ));
396 }
397
398 crate::template_env::render(
399 "generators/trait_bridge/trait_impl.jinja",
400 minijinja::context! {
401 has_async_methods => has_async_methods,
402 async_trait_is_send => async_trait_is_send,
403 trait_path => trait_path,
404 wrapper_name => wrapper,
405 methods_code => methods_code,
406 },
407 )
408}
409
410pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
417 spec.bridge_config.register_fn.as_deref()?;
418 Some(generator.gen_registration_fn(spec))
419}
420
421pub fn gen_bridge_unregistration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
428 spec.bridge_config.unregister_fn.as_deref()?;
429 let body = generator.gen_unregistration_fn(spec);
430 if body.is_empty() { None } else { Some(body) }
431}
432
433pub fn gen_bridge_clear_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
440 spec.bridge_config.clear_fn.as_deref()?;
441 let body = generator.gen_clear_fn(spec);
442 if body.is_empty() { None } else { Some(body) }
443}
444
445pub fn host_function_path(spec: &TraitBridgeSpec, fn_name: &str) -> String {
463 if let Some(getter) = spec.bridge_config.registry_getter.as_deref() {
464 let last = getter.rsplit("::").next().unwrap_or("");
465 if let Some(sub) = last.strip_prefix("get_").and_then(|s| s.strip_suffix("_registry")) {
466 let prefix_end = getter.len() - last.len();
467 let prefix = &getter[..prefix_end];
468 let prefix = prefix.trim_end_matches("registry::");
469 return format!("{prefix}{sub}::{fn_name}");
470 }
471 }
472 format!("{}::plugins::{}", spec.core_import, fn_name)
473}
474
475pub struct BridgeOutput {
478 pub imports: Vec<String>,
480 pub code: String,
482}
483
484pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
490 let imports = generator.bridge_imports();
491 let mut out = String::with_capacity(4096);
492
493 out.push_str(&gen_bridge_wrapper_struct(spec, generator));
495 out.push_str("\n\n");
496
497 out.push_str(&gen_bridge_debug_impl(spec));
499 out.push_str("\n\n");
500
501 out.push_str(&generator.gen_constructor(spec));
503 out.push_str("\n\n");
504
505 if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
507 out.push_str(&plugin_impl);
508 out.push_str("\n\n");
509 }
510
511 out.push_str(&gen_bridge_trait_impl(spec, generator));
513
514 if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
516 out.push_str("\n\n");
517 out.push_str(®_fn_code);
518 }
519
520 if let Some(unreg_fn_code) = gen_bridge_unregistration_fn(spec, generator) {
523 out.push_str("\n\n");
524 out.push_str(&unreg_fn_code);
525 }
526
527 if let Some(clear_fn_code) = gen_bridge_clear_fn(spec, generator) {
530 out.push_str("\n\n");
531 out.push_str(&clear_fn_code);
532 }
533
534 BridgeOutput { imports, code: out }
535}
536
537pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
546 use alef_core::ir::{PrimitiveType, TypeRef};
547 match ty {
548 TypeRef::Primitive(p) => match p {
549 PrimitiveType::Bool => "bool",
550 PrimitiveType::U8 => "u8",
551 PrimitiveType::U16 => "u16",
552 PrimitiveType::U32 => "u32",
553 PrimitiveType::U64 => "u64",
554 PrimitiveType::I8 => "i8",
555 PrimitiveType::I16 => "i16",
556 PrimitiveType::I32 => "i32",
557 PrimitiveType::I64 => "i64",
558 PrimitiveType::F32 => "f32",
559 PrimitiveType::F64 => "f64",
560 PrimitiveType::Usize => "usize",
561 PrimitiveType::Isize => "isize",
562 }
563 .to_string(),
564 TypeRef::String => "String".to_string(),
565 TypeRef::Char => "char".to_string(),
566 TypeRef::Bytes => "Vec<u8>".to_string(),
567 TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
568 TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
569 TypeRef::Map(k, v) => format!(
570 "std::collections::HashMap<{}, {}>",
571 format_type_ref(k, type_paths),
572 format_type_ref(v, type_paths)
573 ),
574 TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
575 TypeRef::Path => "std::path::PathBuf".to_string(),
576 TypeRef::Unit => "()".to_string(),
577 TypeRef::Json => "serde_json::Value".to_string(),
578 TypeRef::Duration => "std::time::Duration".to_string(),
579 }
580}
581
582pub fn format_return_type(
592 ty: &alef_core::ir::TypeRef,
593 error_type: Option<&str>,
594 type_paths: &HashMap<String, String>,
595 returns_ref: bool,
596) -> String {
597 let inner = if returns_ref {
598 if let alef_core::ir::TypeRef::Vec(elem) = ty {
600 let elem_str = match elem.as_ref() {
601 alef_core::ir::TypeRef::String => "&str".to_string(),
602 alef_core::ir::TypeRef::Bytes => "&[u8]".to_string(),
603 alef_core::ir::TypeRef::Named(name) => {
604 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
605 format!("&{qualified}")
606 }
607 other => format_type_ref(other, type_paths),
608 };
609 format!("&[{elem_str}]")
610 } else {
611 format_type_ref(ty, type_paths)
612 }
613 } else {
614 format_type_ref(ty, type_paths)
615 };
616 match error_type {
617 Some(err) => format!("std::result::Result<{inner}, {err}>"),
618 None => inner,
619 }
620}
621
622pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
634 use alef_core::ir::TypeRef;
635 let base = if param.is_ref {
636 let mutability = if param.is_mut { "mut " } else { "" };
637 match ¶m.ty {
638 TypeRef::String => format!("&{mutability}str"),
639 TypeRef::Bytes => format!("&{mutability}[u8]"),
640 TypeRef::Path => format!("&{mutability}std::path::Path"),
641 TypeRef::Vec(inner) => format!("&{mutability}[{}]", format_type_ref(inner, type_paths)),
642 TypeRef::Named(name) => {
643 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
644 format!("&{mutability}{qualified}")
645 }
646 TypeRef::Optional(inner) => {
647 let inner_type_str = match inner.as_ref() {
651 TypeRef::String => format!("&{mutability}str"),
652 TypeRef::Bytes => format!("&{mutability}[u8]"),
653 TypeRef::Path => format!("&{mutability}std::path::Path"),
654 TypeRef::Vec(v) => format!("&{mutability}[{}]", format_type_ref(v, 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 other => format_type_ref(other, type_paths),
661 };
662 return format!("Option<{inner_type_str}>");
664 }
665 other => format_type_ref(other, type_paths),
667 }
668 } else {
669 format_type_ref(¶m.ty, type_paths)
670 };
671
672 if param.optional {
676 format!("Option<{base}>")
677 } else {
678 base
679 }
680}
681
682pub fn prim(p: &PrimitiveType) -> &'static str {
688 use PrimitiveType::*;
689 match p {
690 Bool => "bool",
691 U8 => "u8",
692 U16 => "u16",
693 U32 => "u32",
694 U64 => "u64",
695 I8 => "i8",
696 I16 => "i16",
697 I32 => "i32",
698 I64 => "i64",
699 F32 => "f32",
700 F64 => "f64",
701 Usize => "usize",
702 Isize => "isize",
703 }
704}
705
706pub fn bridge_param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
710 match ty {
711 TypeRef::Bytes if is_ref => "&[u8]".into(),
712 TypeRef::Bytes => "Vec<u8>".into(),
713 TypeRef::String if is_ref => "&str".into(),
714 TypeRef::String => "String".into(),
715 TypeRef::Path if is_ref => "&std::path::Path".into(),
716 TypeRef::Path => "std::path::PathBuf".into(),
717 TypeRef::Named(n) => {
718 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
719 if is_ref { format!("&{qualified}") } else { qualified }
720 }
721 TypeRef::Vec(inner) => format!("Vec<{}>", bridge_param_type(inner, ci, false, tp)),
722 TypeRef::Optional(inner) => format!("Option<{}>", bridge_param_type(inner, ci, false, tp)),
723 TypeRef::Primitive(p) => prim(p).into(),
724 TypeRef::Unit => "()".into(),
725 TypeRef::Char => "char".into(),
726 TypeRef::Map(k, v) => format!(
727 "std::collections::HashMap<{}, {}>",
728 bridge_param_type(k, ci, false, tp),
729 bridge_param_type(v, ci, false, tp)
730 ),
731 TypeRef::Json => "serde_json::Value".into(),
732 TypeRef::Duration => "std::time::Duration".into(),
733 }
734}
735
736pub fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
742 if optional && matches!(ty, TypeRef::String) && is_ref {
743 return "Option<&str>".to_string();
744 }
745 if is_ref {
746 if let TypeRef::Vec(inner) = ty {
747 let inner_str = bridge_param_type(inner, "", false, tp);
748 return format!("&[{inner_str}]");
749 }
750 }
751 bridge_param_type(ty, "", is_ref, tp)
752}
753
754pub fn find_bridge_param<'a>(
761 func: &FunctionDef,
762 bridges: &'a [TraitBridgeConfig],
763) -> Option<(usize, &'a TraitBridgeConfig)> {
764 for (idx, param) in func.params.iter().enumerate() {
765 let named = match ¶m.ty {
766 TypeRef::Named(n) => Some(n.as_str()),
767 TypeRef::Optional(inner) => {
768 if let TypeRef::Named(n) = inner.as_ref() {
769 Some(n.as_str())
770 } else {
771 None
772 }
773 }
774 _ => None,
775 };
776 for bridge in bridges {
777 if bridge.bind_via != BridgeBinding::FunctionParam {
778 continue;
779 }
780 if let Some(type_name) = named {
781 if bridge.type_alias.as_deref() == Some(type_name) {
782 return Some((idx, bridge));
783 }
784 }
785 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
786 return Some((idx, bridge));
787 }
788 }
789 }
790 None
791}
792
793#[derive(Debug, Clone)]
796pub struct BridgeFieldMatch<'a> {
797 pub param_index: usize,
799 pub param_name: String,
801 pub options_type: String,
803 pub param_is_optional: bool,
805 pub field_name: String,
807 pub field: &'a FieldDef,
809 pub bridge: &'a TraitBridgeConfig,
811}
812
813pub fn find_bridge_field<'a>(
824 func: &FunctionDef,
825 types: &'a [TypeDef],
826 bridges: &'a [TraitBridgeConfig],
827) -> Option<BridgeFieldMatch<'a>> {
828 fn unwrap_named(ty: &TypeRef) -> Option<(&str, bool)> {
829 match ty {
830 TypeRef::Named(n) => Some((n.as_str(), false)),
831 TypeRef::Optional(inner) => {
832 if let TypeRef::Named(n) = inner.as_ref() {
833 Some((n.as_str(), true))
834 } else {
835 None
836 }
837 }
838 _ => None,
839 }
840 }
841
842 for (idx, param) in func.params.iter().enumerate() {
843 let Some((type_name, is_optional)) = unwrap_named(¶m.ty) else {
844 continue;
845 };
846 let Some(type_def) = types.iter().find(|t| t.name == type_name) else {
847 continue;
848 };
849 for bridge in bridges {
850 if bridge.bind_via != BridgeBinding::OptionsField {
851 continue;
852 }
853 if bridge.options_type.as_deref() != Some(type_name) {
854 continue;
855 }
856 let field_name = bridge.resolved_options_field();
857 for field in &type_def.fields {
858 let matches_name = field_name.is_some_and(|n| field.name == n);
859 let matches_alias = bridge
860 .type_alias
861 .as_deref()
862 .is_some_and(|alias| field_type_matches_alias(&field.ty, alias));
863 if matches_name || matches_alias {
864 return Some(BridgeFieldMatch {
865 param_index: idx,
866 param_name: param.name.clone(),
867 options_type: type_name.to_string(),
868 param_is_optional: is_optional,
869 field_name: field.name.clone(),
870 field,
871 bridge,
872 });
873 }
874 }
875 }
876 }
877 None
878}
879
880fn field_type_matches_alias(field_ty: &TypeRef, alias: &str) -> bool {
883 match field_ty {
884 TypeRef::Named(n) => n == alias,
885 TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_type_matches_alias(inner, alias),
886 _ => false,
887 }
888}
889
890pub fn to_camel_case(s: &str) -> String {
892 let mut result = String::new();
893 let mut capitalize_next = false;
894 for ch in s.chars() {
895 if ch == '_' {
896 capitalize_next = true;
897 } else if capitalize_next {
898 result.push(ch.to_ascii_uppercase());
899 capitalize_next = false;
900 } else {
901 result.push(ch);
902 }
903 }
904 result
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910 use alef_core::config::TraitBridgeConfig;
911 use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
912
913 fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
918 TraitBridgeConfig {
919 trait_name: "OcrBackend".to_string(),
920 super_trait: super_trait.map(str::to_string),
921 registry_getter: None,
922 register_fn: register_fn.map(str::to_string),
923 unregister_fn: None,
924 clear_fn: None,
925 type_alias: None,
926 param_name: None,
927 register_extra_args: None,
928 exclude_languages: Vec::new(),
929 bind_via: BridgeBinding::FunctionParam,
930 options_type: None,
931 options_field: None,
932 }
933 }
934
935 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
936 TypeDef {
937 name: name.to_string(),
938 rust_path: rust_path.to_string(),
939 original_rust_path: rust_path.to_string(),
940 fields: vec![],
941 methods,
942 is_opaque: true,
943 is_clone: false,
944 is_copy: false,
945 doc: String::new(),
946 cfg: None,
947 is_trait: true,
948 has_default: false,
949 has_stripped_cfg_fields: false,
950 is_return_type: false,
951 serde_rename_all: None,
952 has_serde: false,
953 super_traits: vec![],
954 }
955 }
956
957 fn make_method(
958 name: &str,
959 params: Vec<ParamDef>,
960 return_type: TypeRef,
961 is_async: bool,
962 has_default_impl: bool,
963 trait_source: Option<&str>,
964 error_type: Option<&str>,
965 ) -> MethodDef {
966 MethodDef {
967 name: name.to_string(),
968 params,
969 return_type,
970 is_async,
971 is_static: false,
972 error_type: error_type.map(str::to_string),
973 doc: String::new(),
974 receiver: Some(ReceiverKind::Ref),
975 sanitized: false,
976 trait_source: trait_source.map(str::to_string),
977 returns_ref: false,
978 returns_cow: false,
979 return_newtype_wrapper: None,
980 has_default_impl,
981 }
982 }
983
984 fn make_func(name: &str, params: Vec<ParamDef>) -> FunctionDef {
985 FunctionDef {
986 name: name.to_string(),
987 rust_path: format!("mylib::{name}"),
988 original_rust_path: String::new(),
989 params,
990 return_type: TypeRef::Unit,
991 is_async: false,
992 error_type: None,
993 doc: String::new(),
994 cfg: None,
995 sanitized: false,
996 return_sanitized: false,
997 returns_ref: false,
998 returns_cow: false,
999 return_newtype_wrapper: None,
1000 }
1001 }
1002
1003 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1004 FieldDef {
1005 name: name.to_string(),
1006 ty,
1007 optional: false,
1008 default: None,
1009 doc: String::new(),
1010 sanitized: false,
1011 is_boxed: false,
1012 type_rust_path: None,
1013 cfg: None,
1014 typed_default: None,
1015 core_wrapper: Default::default(),
1016 vec_inner_core_wrapper: Default::default(),
1017 newtype_wrapper: None,
1018 serde_rename: None,
1019 serde_flatten: false,
1020 }
1021 }
1022
1023 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
1024 ParamDef {
1025 name: name.to_string(),
1026 ty,
1027 optional: false,
1028 default: None,
1029 sanitized: false,
1030 typed_default: None,
1031 is_ref,
1032 is_mut: false,
1033 newtype_wrapper: None,
1034 original_type: None,
1035 }
1036 }
1037
1038 fn make_spec<'a>(
1039 trait_def: &'a TypeDef,
1040 bridge_config: &'a TraitBridgeConfig,
1041 wrapper_prefix: &'a str,
1042 type_paths: HashMap<String, String>,
1043 ) -> TraitBridgeSpec<'a> {
1044 TraitBridgeSpec {
1045 trait_def,
1046 bridge_config,
1047 core_import: "mylib",
1048 wrapper_prefix,
1049 type_paths,
1050 error_type: "MyError".to_string(),
1051 error_constructor: "MyError::from({msg})".to_string(),
1052 }
1053 }
1054
1055 struct MockBridgeGenerator;
1060
1061 impl TraitBridgeGenerator for MockBridgeGenerator {
1062 fn foreign_object_type(&self) -> &str {
1063 "Py<PyAny>"
1064 }
1065
1066 fn bridge_imports(&self) -> Vec<String> {
1067 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
1068 }
1069
1070 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1071 format!("// sync body for {}", method.name)
1072 }
1073
1074 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1075 format!("// async body for {}", method.name)
1076 }
1077
1078 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
1079 format!(
1080 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
1081 spec.wrapper_name()
1082 )
1083 }
1084
1085 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
1086 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
1087 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
1088 }
1089 }
1090
1091 #[test]
1096 fn test_wrapper_name() {
1097 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1098 let config = make_trait_bridge_config(None, None);
1099 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1100 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
1101 }
1102
1103 #[test]
1104 fn test_trait_snake() {
1105 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1106 let config = make_trait_bridge_config(None, None);
1107 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1108 assert_eq!(spec.trait_snake(), "ocr_backend");
1109 }
1110
1111 #[test]
1112 fn test_trait_path_replaces_hyphens() {
1113 let trait_def = make_type_def("OcrBackend", "my-lib::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.trait_path(), "my_lib::OcrBackend");
1117 }
1118
1119 #[test]
1120 fn test_required_methods_filters_no_default_impl() {
1121 let methods = vec![
1122 make_method("process", vec![], TypeRef::String, false, false, None, None),
1123 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1124 make_method("detect", vec![], TypeRef::String, false, false, None, None),
1125 ];
1126 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1127 let config = make_trait_bridge_config(None, None);
1128 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1129 let required = spec.required_methods();
1130 assert_eq!(required.len(), 2);
1131 assert!(required.iter().any(|m| m.name == "process"));
1132 assert!(required.iter().any(|m| m.name == "detect"));
1133 }
1134
1135 #[test]
1136 fn test_optional_methods_filters_has_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("shutdown", vec![], TypeRef::Unit, false, true, 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 optional = spec.optional_methods();
1146 assert_eq!(optional.len(), 2);
1147 assert!(optional.iter().any(|m| m.name == "initialize"));
1148 assert!(optional.iter().any(|m| m.name == "shutdown"));
1149 }
1150
1151 #[test]
1152 fn test_error_path() {
1153 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1154 let config = make_trait_bridge_config(None, None);
1155 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1156 assert_eq!(spec.error_path(), "mylib::MyError");
1157 }
1158
1159 #[test]
1164 fn test_format_type_ref_primitives() {
1165 let paths = HashMap::new();
1166 let cases: Vec<(TypeRef, &str)> = vec![
1167 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
1168 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
1169 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
1170 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
1171 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
1172 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
1173 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
1174 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
1175 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
1176 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
1177 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
1178 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
1179 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
1180 ];
1181 for (ty, expected) in cases {
1182 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
1183 }
1184 }
1185
1186 #[test]
1187 fn test_format_type_ref_string() {
1188 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
1189 }
1190
1191 #[test]
1192 fn test_format_type_ref_char() {
1193 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
1194 }
1195
1196 #[test]
1197 fn test_format_type_ref_bytes() {
1198 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
1199 }
1200
1201 #[test]
1202 fn test_format_type_ref_path() {
1203 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
1204 }
1205
1206 #[test]
1207 fn test_format_type_ref_unit() {
1208 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
1209 }
1210
1211 #[test]
1212 fn test_format_type_ref_json() {
1213 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
1214 }
1215
1216 #[test]
1217 fn test_format_type_ref_duration() {
1218 assert_eq!(
1219 format_type_ref(&TypeRef::Duration, &HashMap::new()),
1220 "std::time::Duration"
1221 );
1222 }
1223
1224 #[test]
1225 fn test_format_type_ref_optional() {
1226 let ty = TypeRef::Optional(Box::new(TypeRef::String));
1227 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
1228 }
1229
1230 #[test]
1231 fn test_format_type_ref_optional_nested() {
1232 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
1233 PrimitiveType::U32,
1234 )))));
1235 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
1236 }
1237
1238 #[test]
1239 fn test_format_type_ref_vec() {
1240 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
1241 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
1242 }
1243
1244 #[test]
1245 fn test_format_type_ref_vec_nested() {
1246 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
1247 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
1248 }
1249
1250 #[test]
1251 fn test_format_type_ref_map() {
1252 let ty = TypeRef::Map(
1253 Box::new(TypeRef::String),
1254 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
1255 );
1256 assert_eq!(
1257 format_type_ref(&ty, &HashMap::new()),
1258 "std::collections::HashMap<String, i64>"
1259 );
1260 }
1261
1262 #[test]
1263 fn test_format_type_ref_map_nested_value() {
1264 let ty = TypeRef::Map(
1265 Box::new(TypeRef::String),
1266 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
1267 );
1268 assert_eq!(
1269 format_type_ref(&ty, &HashMap::new()),
1270 "std::collections::HashMap<String, Vec<String>>"
1271 );
1272 }
1273
1274 #[test]
1275 fn test_format_type_ref_named_without_type_paths() {
1276 let ty = TypeRef::Named("Config".to_string());
1277 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
1278 }
1279
1280 #[test]
1281 fn test_format_type_ref_named_with_type_paths() {
1282 let ty = TypeRef::Named("Config".to_string());
1283 let mut paths = HashMap::new();
1284 paths.insert("Config".to_string(), "mylib::Config".to_string());
1285 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
1286 }
1287
1288 #[test]
1289 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
1290 let ty = TypeRef::Named("Unknown".to_string());
1291 let mut paths = HashMap::new();
1292 paths.insert("Other".to_string(), "mylib::Other".to_string());
1293 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
1294 }
1295
1296 #[test]
1301 fn test_format_param_type_string_ref() {
1302 let param = make_param("input", TypeRef::String, true);
1303 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
1304 }
1305
1306 #[test]
1307 fn test_format_param_type_string_owned() {
1308 let param = make_param("input", TypeRef::String, false);
1309 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
1310 }
1311
1312 #[test]
1313 fn test_format_param_type_bytes_ref() {
1314 let param = make_param("data", TypeRef::Bytes, true);
1315 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
1316 }
1317
1318 #[test]
1319 fn test_format_param_type_bytes_owned() {
1320 let param = make_param("data", TypeRef::Bytes, false);
1321 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
1322 }
1323
1324 #[test]
1325 fn test_format_param_type_path_ref() {
1326 let param = make_param("path", TypeRef::Path, true);
1327 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
1328 }
1329
1330 #[test]
1331 fn test_format_param_type_path_owned() {
1332 let param = make_param("path", TypeRef::Path, false);
1333 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
1334 }
1335
1336 #[test]
1337 fn test_format_param_type_vec_ref() {
1338 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
1339 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
1340 }
1341
1342 #[test]
1343 fn test_format_param_type_vec_owned() {
1344 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
1345 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
1346 }
1347
1348 #[test]
1349 fn test_format_param_type_named_ref_with_type_paths() {
1350 let mut paths = HashMap::new();
1351 paths.insert("Config".to_string(), "mylib::Config".to_string());
1352 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1353 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
1354 }
1355
1356 #[test]
1357 fn test_format_param_type_named_ref_without_type_paths() {
1358 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1359 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
1360 }
1361
1362 #[test]
1363 fn test_format_param_type_primitive_ref_passes_by_value() {
1364 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
1366 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
1367 }
1368
1369 #[test]
1370 fn test_format_param_type_unit_ref_passes_by_value() {
1371 let param = make_param("nothing", TypeRef::Unit, true);
1372 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
1373 }
1374
1375 #[test]
1380 fn test_format_return_type_without_error() {
1381 let result = format_return_type(&TypeRef::String, None, &HashMap::new(), false);
1382 assert_eq!(result, "String");
1383 }
1384
1385 #[test]
1386 fn test_format_return_type_with_error() {
1387 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new(), false);
1388 assert_eq!(result, "std::result::Result<String, MyError>");
1389 }
1390
1391 #[test]
1392 fn test_format_return_type_unit_with_error() {
1393 let result = format_return_type(
1394 &TypeRef::Unit,
1395 Some("Box<dyn std::error::Error>"),
1396 &HashMap::new(),
1397 false,
1398 );
1399 assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
1400 }
1401
1402 #[test]
1403 fn test_format_return_type_named_with_type_paths_and_error() {
1404 let mut paths = HashMap::new();
1405 paths.insert("Output".to_string(), "mylib::Output".to_string());
1406 let result = format_return_type(
1407 &TypeRef::Named("Output".to_string()),
1408 Some("mylib::MyError"),
1409 &paths,
1410 false,
1411 );
1412 assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
1413 }
1414
1415 #[test]
1416 fn test_format_return_type_vec_string_with_returns_ref() {
1417 let result = format_return_type(&TypeRef::Vec(Box::new(TypeRef::String)), None, &HashMap::new(), true);
1421 assert_eq!(result, "&[&str]", "Vec<String> + returns_ref must yield &[&str]");
1422 }
1423
1424 #[test]
1425 fn test_format_return_type_vec_no_returns_ref_unchanged() {
1426 let result = format_return_type(&TypeRef::Vec(Box::new(TypeRef::String)), None, &HashMap::new(), false);
1428 assert_eq!(
1429 result, "Vec<String>",
1430 "Vec<String> without returns_ref must stay Vec<String>"
1431 );
1432 }
1433
1434 #[test]
1439 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
1440 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1441 let config = make_trait_bridge_config(None, None);
1442 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1443 let generator = MockBridgeGenerator;
1444 let result = gen_bridge_wrapper_struct(&spec, &generator);
1445 assert!(
1446 result.contains("pub struct PyOcrBackendBridge"),
1447 "missing struct declaration in:\n{result}"
1448 );
1449 }
1450
1451 #[test]
1452 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
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!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
1459 }
1460
1461 #[test]
1462 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
1463 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1464 let config = make_trait_bridge_config(None, None);
1465 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1466 let generator = MockBridgeGenerator;
1467 let result = gen_bridge_wrapper_struct(&spec, &generator);
1468 assert!(
1469 result.contains("cached_name: String"),
1470 "missing cached_name field in:\n{result}"
1471 );
1472 }
1473
1474 #[test]
1479 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1480 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1481 let config = make_trait_bridge_config(None, None);
1482 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1483 let generator = MockBridgeGenerator;
1484 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1485 }
1486
1487 #[test]
1488 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1489 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1490 let config = make_trait_bridge_config(Some("Plugin"), None);
1491 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1492 let generator = MockBridgeGenerator;
1493 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1494 }
1495
1496 #[test]
1497 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1498 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1499 let config = make_trait_bridge_config(Some("Plugin"), None);
1500 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1501 let generator = MockBridgeGenerator;
1502 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1503 assert!(
1504 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1505 "missing qualified super-trait path in:\n{result}"
1506 );
1507 }
1508
1509 #[test]
1510 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1511 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1512 let config = make_trait_bridge_config(Some("other_crate::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 other_crate::Plugin for PyOcrBackendBridge"),
1518 "wrong super-trait path in:\n{result}"
1519 );
1520 }
1521
1522 #[test]
1523 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1524 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1525 let config = make_trait_bridge_config(Some("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("fn name(") && result.contains("cached_name"),
1531 "missing name() using cached_name in:\n{result}"
1532 );
1533 }
1534
1535 #[test]
1536 fn test_gen_bridge_plugin_impl_contains_version_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!(result.contains("fn version("), "missing version() in:\n{result}");
1543 }
1544
1545 #[test]
1546 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1547 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1548 let config = make_trait_bridge_config(Some("Plugin"), None);
1549 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1550 let generator = MockBridgeGenerator;
1551 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1552 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1553 }
1554
1555 #[test]
1556 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1557 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1558 let config = make_trait_bridge_config(Some("Plugin"), None);
1559 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1560 let generator = MockBridgeGenerator;
1561 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1562 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1563 }
1564
1565 #[test]
1570 fn test_gen_bridge_trait_impl_includes_impl_header() {
1571 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1572 let config = make_trait_bridge_config(None, None);
1573 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1574 let generator = MockBridgeGenerator;
1575 let result = gen_bridge_trait_impl(&spec, &generator);
1576 assert!(
1577 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1578 "missing impl header in:\n{result}"
1579 );
1580 }
1581
1582 #[test]
1583 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1584 let methods = vec![make_method(
1585 "process",
1586 vec![],
1587 TypeRef::String,
1588 false,
1589 false,
1590 None,
1591 None,
1592 )];
1593 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1594 let config = make_trait_bridge_config(None, None);
1595 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1596 let generator = MockBridgeGenerator;
1597 let result = gen_bridge_trait_impl(&spec, &generator);
1598 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1599 }
1600
1601 #[test]
1602 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1603 let methods = vec![make_method(
1604 "process",
1605 vec![],
1606 TypeRef::String,
1607 false,
1608 false,
1609 None,
1610 None,
1611 )];
1612 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1613 let config = make_trait_bridge_config(None, None);
1614 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1615 let generator = MockBridgeGenerator;
1616 let result = gen_bridge_trait_impl(&spec, &generator);
1617 assert!(
1618 result.contains("// sync body for process"),
1619 "missing sync method body in:\n{result}"
1620 );
1621 }
1622
1623 #[test]
1624 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1625 let methods = vec![make_method(
1626 "process_async",
1627 vec![],
1628 TypeRef::String,
1629 true,
1630 false,
1631 None,
1632 None,
1633 )];
1634 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1635 let config = make_trait_bridge_config(None, None);
1636 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1637 let generator = MockBridgeGenerator;
1638 let result = gen_bridge_trait_impl(&spec, &generator);
1639 assert!(
1640 result.contains("// async body for process_async"),
1641 "missing async method body in:\n{result}"
1642 );
1643 assert!(
1644 result.contains("async fn process_async("),
1645 "missing async keyword in method signature in:\n{result}"
1646 );
1647 }
1648
1649 #[test]
1650 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1651 let methods = vec![
1653 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1654 make_method(
1655 "inherited_method",
1656 vec![],
1657 TypeRef::String,
1658 false,
1659 false,
1660 Some("other_crate::OtherTrait"),
1661 None,
1662 ),
1663 ];
1664 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1665 let config = make_trait_bridge_config(None, None);
1666 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1667 let generator = MockBridgeGenerator;
1668 let result = gen_bridge_trait_impl(&spec, &generator);
1669 assert!(
1670 result.contains("fn own_method("),
1671 "own method should be present in:\n{result}"
1672 );
1673 assert!(
1674 !result.contains("fn inherited_method("),
1675 "inherited method should be filtered out in:\n{result}"
1676 );
1677 }
1678
1679 #[test]
1680 fn test_gen_bridge_trait_impl_method_with_params() {
1681 let params = vec![
1682 make_param("input", TypeRef::String, true),
1683 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1684 ];
1685 let methods = vec![make_method(
1686 "process",
1687 params,
1688 TypeRef::String,
1689 false,
1690 false,
1691 None,
1692 None,
1693 )];
1694 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1695 let config = make_trait_bridge_config(None, None);
1696 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1697 let generator = MockBridgeGenerator;
1698 let result = gen_bridge_trait_impl(&spec, &generator);
1699 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1700 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1701 }
1702
1703 #[test]
1704 fn test_gen_bridge_trait_impl_return_type_with_error() {
1705 let methods = vec![make_method(
1706 "process",
1707 vec![],
1708 TypeRef::String,
1709 false,
1710 false,
1711 None,
1712 Some("MyError"),
1713 )];
1714 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1715 let config = make_trait_bridge_config(None, None);
1716 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1717 let generator = MockBridgeGenerator;
1718 let result = gen_bridge_trait_impl(&spec, &generator);
1719 assert!(
1720 result.contains("-> std::result::Result<String, mylib::MyError>"),
1721 "missing std::result::Result return type in:\n{result}"
1722 );
1723 }
1724
1725 #[test]
1730 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1731 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1732 let config = make_trait_bridge_config(None, None);
1733 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1734 let generator = MockBridgeGenerator;
1735 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1736 }
1737
1738 #[test]
1739 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1740 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1741 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1742 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1743 let generator = MockBridgeGenerator;
1744 let result = gen_bridge_registration_fn(&spec, &generator);
1745 assert!(result.is_some());
1746 let code = result.unwrap();
1747 assert!(
1748 code.contains("register_ocr_backend"),
1749 "missing register fn name in:\n{code}"
1750 );
1751 }
1752
1753 #[test]
1758 fn test_gen_bridge_all_includes_imports() {
1759 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1760 let config = make_trait_bridge_config(None, None);
1761 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1762 let generator = MockBridgeGenerator;
1763 let output = gen_bridge_all(&spec, &generator);
1764 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1765 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1766 }
1767
1768 #[test]
1769 fn test_gen_bridge_all_includes_wrapper_struct() {
1770 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1771 let config = make_trait_bridge_config(None, None);
1772 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1773 let generator = MockBridgeGenerator;
1774 let output = gen_bridge_all(&spec, &generator);
1775 assert!(
1776 output.code.contains("pub struct PyOcrBackendBridge"),
1777 "missing struct in:\n{}",
1778 output.code
1779 );
1780 }
1781
1782 #[test]
1783 fn test_gen_bridge_all_includes_constructor() {
1784 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1785 let config = make_trait_bridge_config(None, None);
1786 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1787 let generator = MockBridgeGenerator;
1788 let output = gen_bridge_all(&spec, &generator);
1789 assert!(
1790 output.code.contains("pub fn new("),
1791 "missing constructor in:\n{}",
1792 output.code
1793 );
1794 }
1795
1796 #[test]
1797 fn test_gen_bridge_all_includes_trait_impl() {
1798 let methods = vec![make_method(
1799 "process",
1800 vec![],
1801 TypeRef::String,
1802 false,
1803 false,
1804 None,
1805 None,
1806 )];
1807 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1808 let config = make_trait_bridge_config(None, None);
1809 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1810 let generator = MockBridgeGenerator;
1811 let output = gen_bridge_all(&spec, &generator);
1812 assert!(
1813 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1814 "missing trait impl in:\n{}",
1815 output.code
1816 );
1817 }
1818
1819 #[test]
1820 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1821 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1822 let config = make_trait_bridge_config(Some("Plugin"), None);
1823 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1824 let generator = MockBridgeGenerator;
1825 let output = gen_bridge_all(&spec, &generator);
1826 assert!(
1827 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1828 "missing plugin impl in:\n{}",
1829 output.code
1830 );
1831 }
1832
1833 #[test]
1834 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1835 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1836 let config = make_trait_bridge_config(None, None);
1837 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1838 let generator = MockBridgeGenerator;
1839 let output = gen_bridge_all(&spec, &generator);
1840 assert!(
1841 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1842 "unexpected plugin impl present without super_trait"
1843 );
1844 }
1845
1846 #[test]
1847 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1848 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1849 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
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("register_ocr_backend"),
1855 "missing registration fn in:\n{}",
1856 output.code
1857 );
1858 }
1859
1860 #[test]
1861 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1862 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1863 let config = make_trait_bridge_config(None, None);
1864 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1865 let generator = MockBridgeGenerator;
1866 let output = gen_bridge_all(&spec, &generator);
1867 assert!(
1868 !output.code.contains("register_ocr_backend"),
1869 "unexpected registration fn present:\n{}",
1870 output.code
1871 );
1872 }
1873
1874 #[test]
1875 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1876 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1877 let config = make_trait_bridge_config(None, None);
1878 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1879 let generator = MockBridgeGenerator;
1880 let output = gen_bridge_all(&spec, &generator);
1881 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1882 let impl_pos = output
1883 .code
1884 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1885 .unwrap();
1886 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1887 }
1888
1889 fn make_bridge(
1894 type_alias: Option<&str>,
1895 param_name: Option<&str>,
1896 bind_via: BridgeBinding,
1897 options_type: Option<&str>,
1898 options_field: Option<&str>,
1899 ) -> TraitBridgeConfig {
1900 TraitBridgeConfig {
1901 trait_name: "HtmlVisitor".to_string(),
1902 super_trait: None,
1903 registry_getter: None,
1904 register_fn: None,
1905 unregister_fn: None,
1906 clear_fn: None,
1907 type_alias: type_alias.map(str::to_string),
1908 param_name: param_name.map(str::to_string),
1909 register_extra_args: None,
1910 exclude_languages: vec![],
1911 bind_via,
1912 options_type: options_type.map(str::to_string),
1913 options_field: options_field.map(str::to_string),
1914 }
1915 }
1916
1917 #[test]
1918 fn find_bridge_param_returns_first_param_match_in_function_param_mode() {
1919 let func = make_func(
1920 "convert",
1921 vec![
1922 make_param("html", TypeRef::String, true),
1923 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1924 ],
1925 );
1926 let bridges = vec![make_bridge(
1927 Some("VisitorHandle"),
1928 Some("visitor"),
1929 BridgeBinding::FunctionParam,
1930 None,
1931 None,
1932 )];
1933 let result = find_bridge_param(&func, &bridges).expect("bridge match");
1934 assert_eq!(result.0, 1);
1935 }
1936
1937 #[test]
1938 fn find_bridge_param_skips_options_field_bridges() {
1939 let func = make_func(
1940 "convert",
1941 vec![
1942 make_param("html", TypeRef::String, true),
1943 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1944 ],
1945 );
1946 let bridges = vec![make_bridge(
1947 Some("VisitorHandle"),
1948 Some("visitor"),
1949 BridgeBinding::OptionsField,
1950 Some("ConversionOptions"),
1951 Some("visitor"),
1952 )];
1953 assert!(
1954 find_bridge_param(&func, &bridges).is_none(),
1955 "bridges configured with bind_via=options_field must not be returned by find_bridge_param"
1956 );
1957 }
1958
1959 #[test]
1960 fn find_bridge_field_detects_field_via_alias() {
1961 let opts_type = TypeDef {
1962 name: "ConversionOptions".to_string(),
1963 rust_path: "mylib::ConversionOptions".to_string(),
1964 original_rust_path: String::new(),
1965 fields: vec![
1966 make_field("debug", TypeRef::Primitive(PrimitiveType::Bool)),
1967 make_field(
1968 "visitor",
1969 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
1970 ),
1971 ],
1972 methods: vec![],
1973 is_opaque: false,
1974 is_clone: true,
1975 is_copy: false,
1976 doc: String::new(),
1977 cfg: None,
1978 is_trait: false,
1979 has_default: true,
1980 has_stripped_cfg_fields: false,
1981 is_return_type: false,
1982 serde_rename_all: None,
1983 has_serde: false,
1984 super_traits: vec![],
1985 };
1986 let func = make_func(
1987 "convert",
1988 vec![
1989 make_param("html", TypeRef::String, true),
1990 make_param(
1991 "options",
1992 TypeRef::Optional(Box::new(TypeRef::Named("ConversionOptions".to_string()))),
1993 false,
1994 ),
1995 ],
1996 );
1997 let bridges = vec![make_bridge(
1998 Some("VisitorHandle"),
1999 Some("visitor"),
2000 BridgeBinding::OptionsField,
2001 Some("ConversionOptions"),
2002 None,
2003 )];
2004 let m = find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).expect("bridge field match");
2005 assert_eq!(m.param_index, 1);
2006 assert_eq!(m.param_name, "options");
2007 assert_eq!(m.options_type, "ConversionOptions");
2008 assert!(m.param_is_optional);
2009 assert_eq!(m.field_name, "visitor");
2010 }
2011
2012 #[test]
2013 fn find_bridge_field_returns_none_for_function_param_bridge() {
2014 let opts_type = TypeDef {
2015 name: "ConversionOptions".to_string(),
2016 rust_path: "mylib::ConversionOptions".to_string(),
2017 original_rust_path: String::new(),
2018 fields: vec![make_field(
2019 "visitor",
2020 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
2021 )],
2022 methods: vec![],
2023 is_opaque: false,
2024 is_clone: true,
2025 is_copy: false,
2026 doc: String::new(),
2027 cfg: None,
2028 is_trait: false,
2029 has_default: true,
2030 has_stripped_cfg_fields: false,
2031 is_return_type: false,
2032 serde_rename_all: None,
2033 has_serde: false,
2034 super_traits: vec![],
2035 };
2036 let func = make_func(
2037 "convert",
2038 vec![make_param(
2039 "options",
2040 TypeRef::Named("ConversionOptions".to_string()),
2041 false,
2042 )],
2043 );
2044 let bridges = vec![make_bridge(
2045 Some("VisitorHandle"),
2046 Some("visitor"),
2047 BridgeBinding::FunctionParam,
2048 None,
2049 None,
2050 )];
2051 assert!(find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).is_none());
2052 }
2053}