1use alef_core::config::{BridgeBinding, TraitBridgeConfig};
9use alef_core::ir::{FieldDef, FunctionDef, MethodDef, ParamDef, PrimitiveType, TypeDef, TypeRef};
10use heck::ToSnakeCase;
11use std::collections::HashMap;
12use std::fmt::Write;
13
14pub struct TraitBridgeSpec<'a> {
16 pub trait_def: &'a TypeDef,
18 pub bridge_config: &'a TraitBridgeConfig,
20 pub core_import: &'a str,
22 pub wrapper_prefix: &'a str,
24 pub type_paths: HashMap<String, String>,
26 pub error_type: String,
28 pub error_constructor: String,
30}
31
32impl<'a> TraitBridgeSpec<'a> {
33 pub fn error_path(&self) -> String {
40 if self.error_type.contains("::") || self.error_type.contains('<') {
41 self.error_type.clone()
42 } else {
43 format!("{}::{}", self.core_import, self.error_type)
44 }
45 }
46
47 pub fn make_error(&self, msg_expr: &str) -> String {
49 self.error_constructor.replace("{msg}", msg_expr)
50 }
51
52 pub fn wrapper_name(&self) -> String {
54 format!("{}{}Bridge", self.wrapper_prefix, self.trait_def.name)
55 }
56
57 pub fn trait_snake(&self) -> String {
59 self.trait_def.name.to_snake_case()
60 }
61
62 pub fn trait_path(&self) -> String {
64 self.trait_def.rust_path.replace('-', "_")
65 }
66
67 pub fn required_methods(&self) -> Vec<&'a MethodDef> {
69 self.trait_def.methods.iter().filter(|m| !m.has_default_impl).collect()
70 }
71
72 pub fn optional_methods(&self) -> Vec<&'a MethodDef> {
74 self.trait_def.methods.iter().filter(|m| m.has_default_impl).collect()
75 }
76}
77
78pub trait TraitBridgeGenerator {
84 fn foreign_object_type(&self) -> &str;
86
87 fn bridge_imports(&self) -> Vec<String>;
89
90 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
95
96 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
100
101 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String;
106
107 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String;
113
114 fn gen_unregistration_fn(&self, _spec: &TraitBridgeSpec) -> String {
121 String::new()
122 }
123
124 fn gen_clear_fn(&self, _spec: &TraitBridgeSpec) -> String {
131 String::new()
132 }
133
134 fn async_trait_is_send(&self) -> bool {
139 true
140 }
141}
142
143pub fn gen_bridge_wrapper_struct(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
157 let wrapper = spec.wrapper_name();
158 let foreign_type = generator.foreign_object_type();
159 let mut out = String::with_capacity(512);
160
161 writeln!(
162 out,
163 "/// Wrapper that bridges a foreign {prefix} object to the `{trait_name}` trait.",
164 prefix = spec.wrapper_prefix,
165 trait_name = spec.trait_def.name,
166 )
167 .ok();
168 writeln!(out, "pub struct {wrapper} {{").ok();
169 writeln!(out, " inner: {foreign_type},").ok();
170 writeln!(out, " cached_name: String,").ok();
171 write!(out, "}}").ok();
172 out
173}
174
175pub fn gen_bridge_plugin_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
183 let super_trait_name = spec.bridge_config.super_trait.as_deref()?;
184
185 let wrapper = spec.wrapper_name();
186 let core_import = spec.core_import;
187
188 let super_trait_path = if super_trait_name.contains("::") {
190 super_trait_name.to_string()
191 } else {
192 format!("{core_import}::{super_trait_name}")
193 };
194
195 let mut out = String::with_capacity(1024);
199 writeln!(out, "impl {super_trait_path} for {wrapper} {{").ok();
200
201 writeln!(out, " fn name(&self) -> &str {{").ok();
203 writeln!(out, " &self.cached_name").ok();
204 writeln!(out, " }}").ok();
205 writeln!(out).ok();
206
207 let error_path = spec.error_path();
208
209 writeln!(out, " fn version(&self) -> String {{").ok();
211 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 for line in version_body.lines() {
229 writeln!(out, " {}", line.trim_start()).ok();
230 }
231 writeln!(out, " }}").ok();
232 writeln!(out).ok();
233
234 writeln!(
236 out,
237 " fn initialize(&self) -> std::result::Result<(), {error_path}> {{"
238 )
239 .ok();
240 let init_method = MethodDef {
241 name: "initialize".to_string(),
242 params: vec![],
243 return_type: alef_core::ir::TypeRef::Unit,
244 is_async: false,
245 is_static: false,
246 error_type: Some(error_path.clone()),
247 doc: String::new(),
248 receiver: Some(alef_core::ir::ReceiverKind::Ref),
249 sanitized: false,
250 trait_source: None,
251 returns_ref: false,
252 returns_cow: false,
253 return_newtype_wrapper: None,
254 has_default_impl: true,
255 };
256 let init_body = generator.gen_sync_method_body(&init_method, spec);
257 for line in init_body.lines() {
258 writeln!(out, " {}", line.trim_start()).ok();
259 }
260 writeln!(out, " }}").ok();
261 writeln!(out).ok();
262
263 writeln!(
265 out,
266 " fn shutdown(&self) -> std::result::Result<(), {error_path}> {{"
267 )
268 .ok();
269 let shutdown_method = MethodDef {
270 name: "shutdown".to_string(),
271 params: vec![],
272 return_type: alef_core::ir::TypeRef::Unit,
273 is_async: false,
274 is_static: false,
275 error_type: Some(error_path.clone()),
276 doc: String::new(),
277 receiver: Some(alef_core::ir::ReceiverKind::Ref),
278 sanitized: false,
279 trait_source: None,
280 returns_ref: false,
281 returns_cow: false,
282 return_newtype_wrapper: None,
283 has_default_impl: true,
284 };
285 let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
286 for line in shutdown_body.lines() {
287 writeln!(out, " {}", line.trim_start()).ok();
288 }
289 writeln!(out, " }}").ok();
290 write!(out, "}}").ok();
291 Some(out)
292}
293
294pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
299 let wrapper = spec.wrapper_name();
300 let trait_path = spec.trait_path();
301 let mut out = String::with_capacity(2048);
302
303 let has_async_methods = spec
306 .trait_def
307 .methods
308 .iter()
309 .any(|m| m.is_async && m.trait_source.is_none());
310 if has_async_methods {
311 if generator.async_trait_is_send() {
312 writeln!(out, "#[async_trait::async_trait]").ok();
313 } else {
314 writeln!(out, "#[async_trait::async_trait(?Send)]").ok();
315 }
316 }
317 writeln!(out, "impl {trait_path} for {wrapper} {{").ok();
318
319 let own_methods: Vec<_> = spec
321 .trait_def
322 .methods
323 .iter()
324 .filter(|m| m.trait_source.is_none())
325 .collect();
326
327 for (i, method) in own_methods.iter().enumerate() {
328 if i > 0 {
329 writeln!(out).ok();
330 }
331
332 let async_kw = if method.is_async { "async " } else { "" };
334 let receiver = match &method.receiver {
335 Some(alef_core::ir::ReceiverKind::Ref) => "&self",
336 Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
337 Some(alef_core::ir::ReceiverKind::Owned) => "self",
338 None => "",
339 };
340
341 let params: Vec<String> = method
343 .params
344 .iter()
345 .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
346 .collect();
347
348 let all_params = if receiver.is_empty() {
349 params.join(", ")
350 } else if params.is_empty() {
351 receiver.to_string()
352 } else {
353 format!("{}, {}", receiver, params.join(", "))
354 };
355
356 let error_override = method.error_type.as_ref().map(|_| spec.error_path());
360 let ret = format_return_type(&method.return_type, error_override.as_deref(), &spec.type_paths);
361
362 writeln!(out, " {async_kw}fn {}({all_params}) -> {ret} {{", method.name).ok();
363
364 let body = if method.is_async {
366 generator.gen_async_method_body(method, spec)
367 } else {
368 generator.gen_sync_method_body(method, spec)
369 };
370
371 for line in body.lines() {
372 writeln!(out, " {line}").ok();
373 }
374 writeln!(out, " }}").ok();
375 }
376
377 write!(out, "}}").ok();
378 out
379}
380
381pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
388 spec.bridge_config.register_fn.as_deref()?;
389 Some(generator.gen_registration_fn(spec))
390}
391
392pub fn gen_bridge_unregistration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
399 spec.bridge_config.unregister_fn.as_deref()?;
400 let body = generator.gen_unregistration_fn(spec);
401 if body.is_empty() { None } else { Some(body) }
402}
403
404pub fn gen_bridge_clear_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
411 spec.bridge_config.clear_fn.as_deref()?;
412 let body = generator.gen_clear_fn(spec);
413 if body.is_empty() { None } else { Some(body) }
414}
415
416pub fn host_function_path(spec: &TraitBridgeSpec, fn_name: &str) -> String {
434 if let Some(getter) = spec.bridge_config.registry_getter.as_deref() {
435 let last = getter.rsplit("::").next().unwrap_or("");
436 if let Some(sub) = last.strip_prefix("get_").and_then(|s| s.strip_suffix("_registry")) {
437 let prefix_end = getter.len() - last.len();
438 let prefix = &getter[..prefix_end];
439 let prefix = prefix.trim_end_matches("registry::");
440 return format!("{prefix}{sub}::{fn_name}");
441 }
442 }
443 format!("{}::plugins::{}", spec.core_import, fn_name)
444}
445
446pub struct BridgeOutput {
449 pub imports: Vec<String>,
451 pub code: String,
453}
454
455pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
461 let imports = generator.bridge_imports();
462 let mut out = String::with_capacity(4096);
463
464 out.push_str(&gen_bridge_wrapper_struct(spec, generator));
466 writeln!(out).ok();
467 writeln!(out).ok();
468
469 out.push_str(&generator.gen_constructor(spec));
471 writeln!(out).ok();
472 writeln!(out).ok();
473
474 if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
476 out.push_str(&plugin_impl);
477 writeln!(out).ok();
478 writeln!(out).ok();
479 }
480
481 out.push_str(&gen_bridge_trait_impl(spec, generator));
483
484 if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
486 writeln!(out).ok();
487 writeln!(out).ok();
488 out.push_str(®_fn_code);
489 }
490
491 if let Some(unreg_fn_code) = gen_bridge_unregistration_fn(spec, generator) {
494 writeln!(out).ok();
495 writeln!(out).ok();
496 out.push_str(&unreg_fn_code);
497 }
498
499 if let Some(clear_fn_code) = gen_bridge_clear_fn(spec, generator) {
502 writeln!(out).ok();
503 writeln!(out).ok();
504 out.push_str(&clear_fn_code);
505 }
506
507 BridgeOutput { imports, code: out }
508}
509
510pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
519 use alef_core::ir::{PrimitiveType, TypeRef};
520 match ty {
521 TypeRef::Primitive(p) => match p {
522 PrimitiveType::Bool => "bool",
523 PrimitiveType::U8 => "u8",
524 PrimitiveType::U16 => "u16",
525 PrimitiveType::U32 => "u32",
526 PrimitiveType::U64 => "u64",
527 PrimitiveType::I8 => "i8",
528 PrimitiveType::I16 => "i16",
529 PrimitiveType::I32 => "i32",
530 PrimitiveType::I64 => "i64",
531 PrimitiveType::F32 => "f32",
532 PrimitiveType::F64 => "f64",
533 PrimitiveType::Usize => "usize",
534 PrimitiveType::Isize => "isize",
535 }
536 .to_string(),
537 TypeRef::String => "String".to_string(),
538 TypeRef::Char => "char".to_string(),
539 TypeRef::Bytes => "Vec<u8>".to_string(),
540 TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
541 TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
542 TypeRef::Map(k, v) => format!(
543 "std::collections::HashMap<{}, {}>",
544 format_type_ref(k, type_paths),
545 format_type_ref(v, type_paths)
546 ),
547 TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
548 TypeRef::Path => "std::path::PathBuf".to_string(),
549 TypeRef::Unit => "()".to_string(),
550 TypeRef::Json => "serde_json::Value".to_string(),
551 TypeRef::Duration => "std::time::Duration".to_string(),
552 }
553}
554
555pub fn format_return_type(
557 ty: &alef_core::ir::TypeRef,
558 error_type: Option<&str>,
559 type_paths: &HashMap<String, String>,
560) -> String {
561 let inner = format_type_ref(ty, type_paths);
562 match error_type {
563 Some(err) => format!("std::result::Result<{inner}, {err}>"),
564 None => inner,
565 }
566}
567
568pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
580 use alef_core::ir::TypeRef;
581 let base = if param.is_ref {
582 let mutability = if param.is_mut { "mut " } else { "" };
583 match ¶m.ty {
584 TypeRef::String => format!("&{mutability}str"),
585 TypeRef::Bytes => format!("&{mutability}[u8]"),
586 TypeRef::Path => format!("&{mutability}std::path::Path"),
587 TypeRef::Vec(inner) => format!("&{mutability}[{}]", format_type_ref(inner, type_paths)),
588 TypeRef::Named(name) => {
589 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
590 format!("&{mutability}{qualified}")
591 }
592 TypeRef::Optional(inner) => {
593 let inner_type_str = match inner.as_ref() {
597 TypeRef::String => format!("&{mutability}str"),
598 TypeRef::Bytes => format!("&{mutability}[u8]"),
599 TypeRef::Path => format!("&{mutability}std::path::Path"),
600 TypeRef::Vec(v) => format!("&{mutability}[{}]", format_type_ref(v, type_paths)),
601 TypeRef::Named(name) => {
602 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
603 format!("&{mutability}{qualified}")
604 }
605 other => format_type_ref(other, type_paths),
607 };
608 return format!("Option<{inner_type_str}>");
610 }
611 other => format_type_ref(other, type_paths),
613 }
614 } else {
615 format_type_ref(¶m.ty, type_paths)
616 };
617
618 if param.optional {
622 format!("Option<{base}>")
623 } else {
624 base
625 }
626}
627
628pub fn prim(p: &PrimitiveType) -> &'static str {
634 use PrimitiveType::*;
635 match p {
636 Bool => "bool",
637 U8 => "u8",
638 U16 => "u16",
639 U32 => "u32",
640 U64 => "u64",
641 I8 => "i8",
642 I16 => "i16",
643 I32 => "i32",
644 I64 => "i64",
645 F32 => "f32",
646 F64 => "f64",
647 Usize => "usize",
648 Isize => "isize",
649 }
650}
651
652pub fn bridge_param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
656 match ty {
657 TypeRef::Bytes if is_ref => "&[u8]".into(),
658 TypeRef::Bytes => "Vec<u8>".into(),
659 TypeRef::String if is_ref => "&str".into(),
660 TypeRef::String => "String".into(),
661 TypeRef::Path if is_ref => "&std::path::Path".into(),
662 TypeRef::Path => "std::path::PathBuf".into(),
663 TypeRef::Named(n) => {
664 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
665 if is_ref { format!("&{qualified}") } else { qualified }
666 }
667 TypeRef::Vec(inner) => format!("Vec<{}>", bridge_param_type(inner, ci, false, tp)),
668 TypeRef::Optional(inner) => format!("Option<{}>", bridge_param_type(inner, ci, false, tp)),
669 TypeRef::Primitive(p) => prim(p).into(),
670 TypeRef::Unit => "()".into(),
671 TypeRef::Char => "char".into(),
672 TypeRef::Map(k, v) => format!(
673 "std::collections::HashMap<{}, {}>",
674 bridge_param_type(k, ci, false, tp),
675 bridge_param_type(v, ci, false, tp)
676 ),
677 TypeRef::Json => "serde_json::Value".into(),
678 TypeRef::Duration => "std::time::Duration".into(),
679 }
680}
681
682pub fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
688 if optional && matches!(ty, TypeRef::String) && is_ref {
689 return "Option<&str>".to_string();
690 }
691 if is_ref {
692 if let TypeRef::Vec(inner) = ty {
693 let inner_str = bridge_param_type(inner, "", false, tp);
694 return format!("&[{inner_str}]");
695 }
696 }
697 bridge_param_type(ty, "", is_ref, tp)
698}
699
700pub fn find_bridge_param<'a>(
707 func: &FunctionDef,
708 bridges: &'a [TraitBridgeConfig],
709) -> Option<(usize, &'a TraitBridgeConfig)> {
710 for (idx, param) in func.params.iter().enumerate() {
711 let named = match ¶m.ty {
712 TypeRef::Named(n) => Some(n.as_str()),
713 TypeRef::Optional(inner) => {
714 if let TypeRef::Named(n) = inner.as_ref() {
715 Some(n.as_str())
716 } else {
717 None
718 }
719 }
720 _ => None,
721 };
722 for bridge in bridges {
723 if bridge.bind_via != BridgeBinding::FunctionParam {
724 continue;
725 }
726 if let Some(type_name) = named {
727 if bridge.type_alias.as_deref() == Some(type_name) {
728 return Some((idx, bridge));
729 }
730 }
731 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
732 return Some((idx, bridge));
733 }
734 }
735 }
736 None
737}
738
739#[derive(Debug, Clone)]
742pub struct BridgeFieldMatch<'a> {
743 pub param_index: usize,
745 pub param_name: String,
747 pub options_type: String,
749 pub param_is_optional: bool,
751 pub field_name: String,
753 pub field: &'a FieldDef,
755 pub bridge: &'a TraitBridgeConfig,
757}
758
759pub fn find_bridge_field<'a>(
770 func: &FunctionDef,
771 types: &'a [TypeDef],
772 bridges: &'a [TraitBridgeConfig],
773) -> Option<BridgeFieldMatch<'a>> {
774 fn unwrap_named(ty: &TypeRef) -> Option<(&str, bool)> {
775 match ty {
776 TypeRef::Named(n) => Some((n.as_str(), false)),
777 TypeRef::Optional(inner) => {
778 if let TypeRef::Named(n) = inner.as_ref() {
779 Some((n.as_str(), true))
780 } else {
781 None
782 }
783 }
784 _ => None,
785 }
786 }
787
788 for (idx, param) in func.params.iter().enumerate() {
789 let Some((type_name, is_optional)) = unwrap_named(¶m.ty) else {
790 continue;
791 };
792 let Some(type_def) = types.iter().find(|t| t.name == type_name) else {
793 continue;
794 };
795 for bridge in bridges {
796 if bridge.bind_via != BridgeBinding::OptionsField {
797 continue;
798 }
799 if bridge.options_type.as_deref() != Some(type_name) {
800 continue;
801 }
802 let field_name = bridge.resolved_options_field();
803 for field in &type_def.fields {
804 let matches_name = field_name.is_some_and(|n| field.name == n);
805 let matches_alias = bridge
806 .type_alias
807 .as_deref()
808 .is_some_and(|alias| field_type_matches_alias(&field.ty, alias));
809 if matches_name || matches_alias {
810 return Some(BridgeFieldMatch {
811 param_index: idx,
812 param_name: param.name.clone(),
813 options_type: type_name.to_string(),
814 param_is_optional: is_optional,
815 field_name: field.name.clone(),
816 field,
817 bridge,
818 });
819 }
820 }
821 }
822 }
823 None
824}
825
826fn field_type_matches_alias(field_ty: &TypeRef, alias: &str) -> bool {
829 match field_ty {
830 TypeRef::Named(n) => n == alias,
831 TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_type_matches_alias(inner, alias),
832 _ => false,
833 }
834}
835
836pub fn to_camel_case(s: &str) -> String {
838 let mut result = String::new();
839 let mut capitalize_next = false;
840 for ch in s.chars() {
841 if ch == '_' {
842 capitalize_next = true;
843 } else if capitalize_next {
844 result.push(ch.to_ascii_uppercase());
845 capitalize_next = false;
846 } else {
847 result.push(ch);
848 }
849 }
850 result
851}
852
853#[cfg(test)]
854mod tests {
855 use super::*;
856 use alef_core::config::TraitBridgeConfig;
857 use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
858
859 fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
864 TraitBridgeConfig {
865 trait_name: "OcrBackend".to_string(),
866 super_trait: super_trait.map(str::to_string),
867 registry_getter: None,
868 register_fn: register_fn.map(str::to_string),
869 unregister_fn: None,
870 clear_fn: None,
871 type_alias: None,
872 param_name: None,
873 register_extra_args: None,
874 exclude_languages: Vec::new(),
875 bind_via: BridgeBinding::FunctionParam,
876 options_type: None,
877 options_field: None,
878 }
879 }
880
881 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
882 TypeDef {
883 name: name.to_string(),
884 rust_path: rust_path.to_string(),
885 original_rust_path: rust_path.to_string(),
886 fields: vec![],
887 methods,
888 is_opaque: true,
889 is_clone: false,
890 is_copy: false,
891 doc: String::new(),
892 cfg: None,
893 is_trait: true,
894 has_default: false,
895 has_stripped_cfg_fields: false,
896 is_return_type: false,
897 serde_rename_all: None,
898 has_serde: false,
899 super_traits: vec![],
900 }
901 }
902
903 fn make_method(
904 name: &str,
905 params: Vec<ParamDef>,
906 return_type: TypeRef,
907 is_async: bool,
908 has_default_impl: bool,
909 trait_source: Option<&str>,
910 error_type: Option<&str>,
911 ) -> MethodDef {
912 MethodDef {
913 name: name.to_string(),
914 params,
915 return_type,
916 is_async,
917 is_static: false,
918 error_type: error_type.map(str::to_string),
919 doc: String::new(),
920 receiver: Some(ReceiverKind::Ref),
921 sanitized: false,
922 trait_source: trait_source.map(str::to_string),
923 returns_ref: false,
924 returns_cow: false,
925 return_newtype_wrapper: None,
926 has_default_impl,
927 }
928 }
929
930 fn make_func(name: &str, params: Vec<ParamDef>) -> FunctionDef {
931 FunctionDef {
932 name: name.to_string(),
933 rust_path: format!("mylib::{name}"),
934 original_rust_path: String::new(),
935 params,
936 return_type: TypeRef::Unit,
937 is_async: false,
938 error_type: None,
939 doc: String::new(),
940 cfg: None,
941 sanitized: false,
942 return_sanitized: false,
943 returns_ref: false,
944 returns_cow: false,
945 return_newtype_wrapper: None,
946 }
947 }
948
949 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
950 FieldDef {
951 name: name.to_string(),
952 ty,
953 optional: false,
954 default: None,
955 doc: String::new(),
956 sanitized: false,
957 is_boxed: false,
958 type_rust_path: None,
959 cfg: None,
960 typed_default: None,
961 core_wrapper: Default::default(),
962 vec_inner_core_wrapper: Default::default(),
963 newtype_wrapper: None,
964 }
965 }
966
967 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
968 ParamDef {
969 name: name.to_string(),
970 ty,
971 optional: false,
972 default: None,
973 sanitized: false,
974 typed_default: None,
975 is_ref,
976 is_mut: false,
977 newtype_wrapper: None,
978 original_type: None,
979 }
980 }
981
982 fn make_spec<'a>(
983 trait_def: &'a TypeDef,
984 bridge_config: &'a TraitBridgeConfig,
985 wrapper_prefix: &'a str,
986 type_paths: HashMap<String, String>,
987 ) -> TraitBridgeSpec<'a> {
988 TraitBridgeSpec {
989 trait_def,
990 bridge_config,
991 core_import: "mylib",
992 wrapper_prefix,
993 type_paths,
994 error_type: "MyError".to_string(),
995 error_constructor: "MyError::from({msg})".to_string(),
996 }
997 }
998
999 struct MockBridgeGenerator;
1004
1005 impl TraitBridgeGenerator for MockBridgeGenerator {
1006 fn foreign_object_type(&self) -> &str {
1007 "Py<PyAny>"
1008 }
1009
1010 fn bridge_imports(&self) -> Vec<String> {
1011 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
1012 }
1013
1014 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1015 format!("// sync body for {}", method.name)
1016 }
1017
1018 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1019 format!("// async body for {}", method.name)
1020 }
1021
1022 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
1023 format!(
1024 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
1025 spec.wrapper_name()
1026 )
1027 }
1028
1029 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
1030 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
1031 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
1032 }
1033 }
1034
1035 #[test]
1040 fn test_wrapper_name() {
1041 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1042 let config = make_trait_bridge_config(None, None);
1043 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1044 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
1045 }
1046
1047 #[test]
1048 fn test_trait_snake() {
1049 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1050 let config = make_trait_bridge_config(None, None);
1051 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1052 assert_eq!(spec.trait_snake(), "ocr_backend");
1053 }
1054
1055 #[test]
1056 fn test_trait_path_replaces_hyphens() {
1057 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
1058 let config = make_trait_bridge_config(None, None);
1059 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1060 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
1061 }
1062
1063 #[test]
1064 fn test_required_methods_filters_no_default_impl() {
1065 let methods = vec![
1066 make_method("process", vec![], TypeRef::String, false, false, None, None),
1067 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1068 make_method("detect", vec![], TypeRef::String, false, false, None, None),
1069 ];
1070 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1071 let config = make_trait_bridge_config(None, None);
1072 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1073 let required = spec.required_methods();
1074 assert_eq!(required.len(), 2);
1075 assert!(required.iter().any(|m| m.name == "process"));
1076 assert!(required.iter().any(|m| m.name == "detect"));
1077 }
1078
1079 #[test]
1080 fn test_optional_methods_filters_has_default_impl() {
1081 let methods = vec![
1082 make_method("process", vec![], TypeRef::String, false, false, None, None),
1083 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1084 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
1085 ];
1086 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1087 let config = make_trait_bridge_config(None, None);
1088 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1089 let optional = spec.optional_methods();
1090 assert_eq!(optional.len(), 2);
1091 assert!(optional.iter().any(|m| m.name == "initialize"));
1092 assert!(optional.iter().any(|m| m.name == "shutdown"));
1093 }
1094
1095 #[test]
1096 fn test_error_path() {
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.error_path(), "mylib::MyError");
1101 }
1102
1103 #[test]
1108 fn test_format_type_ref_primitives() {
1109 let paths = HashMap::new();
1110 let cases: Vec<(TypeRef, &str)> = vec![
1111 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
1112 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
1113 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
1114 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
1115 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
1116 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
1117 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
1118 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
1119 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
1120 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
1121 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
1122 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
1123 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
1124 ];
1125 for (ty, expected) in cases {
1126 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
1127 }
1128 }
1129
1130 #[test]
1131 fn test_format_type_ref_string() {
1132 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
1133 }
1134
1135 #[test]
1136 fn test_format_type_ref_char() {
1137 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
1138 }
1139
1140 #[test]
1141 fn test_format_type_ref_bytes() {
1142 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
1143 }
1144
1145 #[test]
1146 fn test_format_type_ref_path() {
1147 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
1148 }
1149
1150 #[test]
1151 fn test_format_type_ref_unit() {
1152 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
1153 }
1154
1155 #[test]
1156 fn test_format_type_ref_json() {
1157 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
1158 }
1159
1160 #[test]
1161 fn test_format_type_ref_duration() {
1162 assert_eq!(
1163 format_type_ref(&TypeRef::Duration, &HashMap::new()),
1164 "std::time::Duration"
1165 );
1166 }
1167
1168 #[test]
1169 fn test_format_type_ref_optional() {
1170 let ty = TypeRef::Optional(Box::new(TypeRef::String));
1171 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
1172 }
1173
1174 #[test]
1175 fn test_format_type_ref_optional_nested() {
1176 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
1177 PrimitiveType::U32,
1178 )))));
1179 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
1180 }
1181
1182 #[test]
1183 fn test_format_type_ref_vec() {
1184 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
1185 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
1186 }
1187
1188 #[test]
1189 fn test_format_type_ref_vec_nested() {
1190 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
1191 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
1192 }
1193
1194 #[test]
1195 fn test_format_type_ref_map() {
1196 let ty = TypeRef::Map(
1197 Box::new(TypeRef::String),
1198 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
1199 );
1200 assert_eq!(
1201 format_type_ref(&ty, &HashMap::new()),
1202 "std::collections::HashMap<String, i64>"
1203 );
1204 }
1205
1206 #[test]
1207 fn test_format_type_ref_map_nested_value() {
1208 let ty = TypeRef::Map(
1209 Box::new(TypeRef::String),
1210 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
1211 );
1212 assert_eq!(
1213 format_type_ref(&ty, &HashMap::new()),
1214 "std::collections::HashMap<String, Vec<String>>"
1215 );
1216 }
1217
1218 #[test]
1219 fn test_format_type_ref_named_without_type_paths() {
1220 let ty = TypeRef::Named("Config".to_string());
1221 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
1222 }
1223
1224 #[test]
1225 fn test_format_type_ref_named_with_type_paths() {
1226 let ty = TypeRef::Named("Config".to_string());
1227 let mut paths = HashMap::new();
1228 paths.insert("Config".to_string(), "mylib::Config".to_string());
1229 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
1230 }
1231
1232 #[test]
1233 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
1234 let ty = TypeRef::Named("Unknown".to_string());
1235 let mut paths = HashMap::new();
1236 paths.insert("Other".to_string(), "mylib::Other".to_string());
1237 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
1238 }
1239
1240 #[test]
1245 fn test_format_param_type_string_ref() {
1246 let param = make_param("input", TypeRef::String, true);
1247 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
1248 }
1249
1250 #[test]
1251 fn test_format_param_type_string_owned() {
1252 let param = make_param("input", TypeRef::String, false);
1253 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
1254 }
1255
1256 #[test]
1257 fn test_format_param_type_bytes_ref() {
1258 let param = make_param("data", TypeRef::Bytes, true);
1259 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
1260 }
1261
1262 #[test]
1263 fn test_format_param_type_bytes_owned() {
1264 let param = make_param("data", TypeRef::Bytes, false);
1265 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
1266 }
1267
1268 #[test]
1269 fn test_format_param_type_path_ref() {
1270 let param = make_param("path", TypeRef::Path, true);
1271 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
1272 }
1273
1274 #[test]
1275 fn test_format_param_type_path_owned() {
1276 let param = make_param("path", TypeRef::Path, false);
1277 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
1278 }
1279
1280 #[test]
1281 fn test_format_param_type_vec_ref() {
1282 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
1283 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
1284 }
1285
1286 #[test]
1287 fn test_format_param_type_vec_owned() {
1288 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
1289 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
1290 }
1291
1292 #[test]
1293 fn test_format_param_type_named_ref_with_type_paths() {
1294 let mut paths = HashMap::new();
1295 paths.insert("Config".to_string(), "mylib::Config".to_string());
1296 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1297 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
1298 }
1299
1300 #[test]
1301 fn test_format_param_type_named_ref_without_type_paths() {
1302 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1303 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
1304 }
1305
1306 #[test]
1307 fn test_format_param_type_primitive_ref_passes_by_value() {
1308 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
1310 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
1311 }
1312
1313 #[test]
1314 fn test_format_param_type_unit_ref_passes_by_value() {
1315 let param = make_param("nothing", TypeRef::Unit, true);
1316 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
1317 }
1318
1319 #[test]
1324 fn test_format_return_type_without_error() {
1325 let result = format_return_type(&TypeRef::String, None, &HashMap::new());
1326 assert_eq!(result, "String");
1327 }
1328
1329 #[test]
1330 fn test_format_return_type_with_error() {
1331 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new());
1332 assert_eq!(result, "std::result::Result<String, MyError>");
1333 }
1334
1335 #[test]
1336 fn test_format_return_type_unit_with_error() {
1337 let result = format_return_type(&TypeRef::Unit, Some("Box<dyn std::error::Error>"), &HashMap::new());
1338 assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
1339 }
1340
1341 #[test]
1342 fn test_format_return_type_named_with_type_paths_and_error() {
1343 let mut paths = HashMap::new();
1344 paths.insert("Output".to_string(), "mylib::Output".to_string());
1345 let result = format_return_type(&TypeRef::Named("Output".to_string()), Some("mylib::MyError"), &paths);
1346 assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
1347 }
1348
1349 #[test]
1354 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
1355 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1356 let config = make_trait_bridge_config(None, None);
1357 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1358 let generator = MockBridgeGenerator;
1359 let result = gen_bridge_wrapper_struct(&spec, &generator);
1360 assert!(
1361 result.contains("pub struct PyOcrBackendBridge"),
1362 "missing struct declaration in:\n{result}"
1363 );
1364 }
1365
1366 #[test]
1367 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
1368 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1369 let config = make_trait_bridge_config(None, None);
1370 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1371 let generator = MockBridgeGenerator;
1372 let result = gen_bridge_wrapper_struct(&spec, &generator);
1373 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
1374 }
1375
1376 #[test]
1377 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
1378 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1379 let config = make_trait_bridge_config(None, None);
1380 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1381 let generator = MockBridgeGenerator;
1382 let result = gen_bridge_wrapper_struct(&spec, &generator);
1383 assert!(
1384 result.contains("cached_name: String"),
1385 "missing cached_name field in:\n{result}"
1386 );
1387 }
1388
1389 #[test]
1394 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1395 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1396 let config = make_trait_bridge_config(None, None);
1397 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1398 let generator = MockBridgeGenerator;
1399 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1400 }
1401
1402 #[test]
1403 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1404 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1405 let config = make_trait_bridge_config(Some("Plugin"), None);
1406 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1407 let generator = MockBridgeGenerator;
1408 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1409 }
1410
1411 #[test]
1412 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1413 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1414 let config = make_trait_bridge_config(Some("Plugin"), None);
1415 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1416 let generator = MockBridgeGenerator;
1417 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1418 assert!(
1419 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1420 "missing qualified super-trait path in:\n{result}"
1421 );
1422 }
1423
1424 #[test]
1425 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1426 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1427 let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
1428 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1429 let generator = MockBridgeGenerator;
1430 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1431 assert!(
1432 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1433 "wrong super-trait path in:\n{result}"
1434 );
1435 }
1436
1437 #[test]
1438 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1439 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1440 let config = make_trait_bridge_config(Some("Plugin"), None);
1441 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1442 let generator = MockBridgeGenerator;
1443 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1444 assert!(
1445 result.contains("fn name(") && result.contains("cached_name"),
1446 "missing name() using cached_name in:\n{result}"
1447 );
1448 }
1449
1450 #[test]
1451 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1452 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1453 let config = make_trait_bridge_config(Some("Plugin"), None);
1454 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1455 let generator = MockBridgeGenerator;
1456 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1457 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1458 }
1459
1460 #[test]
1461 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1462 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1463 let config = make_trait_bridge_config(Some("Plugin"), None);
1464 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1465 let generator = MockBridgeGenerator;
1466 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1467 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1468 }
1469
1470 #[test]
1471 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1472 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1473 let config = make_trait_bridge_config(Some("Plugin"), None);
1474 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1475 let generator = MockBridgeGenerator;
1476 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1477 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1478 }
1479
1480 #[test]
1485 fn test_gen_bridge_trait_impl_includes_impl_header() {
1486 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1487 let config = make_trait_bridge_config(None, None);
1488 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1489 let generator = MockBridgeGenerator;
1490 let result = gen_bridge_trait_impl(&spec, &generator);
1491 assert!(
1492 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1493 "missing impl header in:\n{result}"
1494 );
1495 }
1496
1497 #[test]
1498 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1499 let methods = vec![make_method(
1500 "process",
1501 vec![],
1502 TypeRef::String,
1503 false,
1504 false,
1505 None,
1506 None,
1507 )];
1508 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1509 let config = make_trait_bridge_config(None, None);
1510 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1511 let generator = MockBridgeGenerator;
1512 let result = gen_bridge_trait_impl(&spec, &generator);
1513 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1514 }
1515
1516 #[test]
1517 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1518 let methods = vec![make_method(
1519 "process",
1520 vec![],
1521 TypeRef::String,
1522 false,
1523 false,
1524 None,
1525 None,
1526 )];
1527 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1528 let config = make_trait_bridge_config(None, None);
1529 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1530 let generator = MockBridgeGenerator;
1531 let result = gen_bridge_trait_impl(&spec, &generator);
1532 assert!(
1533 result.contains("// sync body for process"),
1534 "missing sync method body in:\n{result}"
1535 );
1536 }
1537
1538 #[test]
1539 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1540 let methods = vec![make_method(
1541 "process_async",
1542 vec![],
1543 TypeRef::String,
1544 true,
1545 false,
1546 None,
1547 None,
1548 )];
1549 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1550 let config = make_trait_bridge_config(None, None);
1551 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1552 let generator = MockBridgeGenerator;
1553 let result = gen_bridge_trait_impl(&spec, &generator);
1554 assert!(
1555 result.contains("// async body for process_async"),
1556 "missing async method body in:\n{result}"
1557 );
1558 assert!(
1559 result.contains("async fn process_async("),
1560 "missing async keyword in method signature in:\n{result}"
1561 );
1562 }
1563
1564 #[test]
1565 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1566 let methods = vec![
1568 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1569 make_method(
1570 "inherited_method",
1571 vec![],
1572 TypeRef::String,
1573 false,
1574 false,
1575 Some("other_crate::OtherTrait"),
1576 None,
1577 ),
1578 ];
1579 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1580 let config = make_trait_bridge_config(None, None);
1581 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1582 let generator = MockBridgeGenerator;
1583 let result = gen_bridge_trait_impl(&spec, &generator);
1584 assert!(
1585 result.contains("fn own_method("),
1586 "own method should be present in:\n{result}"
1587 );
1588 assert!(
1589 !result.contains("fn inherited_method("),
1590 "inherited method should be filtered out in:\n{result}"
1591 );
1592 }
1593
1594 #[test]
1595 fn test_gen_bridge_trait_impl_method_with_params() {
1596 let params = vec![
1597 make_param("input", TypeRef::String, true),
1598 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1599 ];
1600 let methods = vec![make_method(
1601 "process",
1602 params,
1603 TypeRef::String,
1604 false,
1605 false,
1606 None,
1607 None,
1608 )];
1609 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1610 let config = make_trait_bridge_config(None, None);
1611 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1612 let generator = MockBridgeGenerator;
1613 let result = gen_bridge_trait_impl(&spec, &generator);
1614 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1615 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1616 }
1617
1618 #[test]
1619 fn test_gen_bridge_trait_impl_return_type_with_error() {
1620 let methods = vec![make_method(
1621 "process",
1622 vec![],
1623 TypeRef::String,
1624 false,
1625 false,
1626 None,
1627 Some("MyError"),
1628 )];
1629 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1630 let config = make_trait_bridge_config(None, None);
1631 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1632 let generator = MockBridgeGenerator;
1633 let result = gen_bridge_trait_impl(&spec, &generator);
1634 assert!(
1635 result.contains("-> std::result::Result<String, mylib::MyError>"),
1636 "missing std::result::Result return type in:\n{result}"
1637 );
1638 }
1639
1640 #[test]
1645 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1646 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1647 let config = make_trait_bridge_config(None, None);
1648 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1649 let generator = MockBridgeGenerator;
1650 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1651 }
1652
1653 #[test]
1654 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1655 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1656 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1657 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1658 let generator = MockBridgeGenerator;
1659 let result = gen_bridge_registration_fn(&spec, &generator);
1660 assert!(result.is_some());
1661 let code = result.unwrap();
1662 assert!(
1663 code.contains("register_ocr_backend"),
1664 "missing register fn name in:\n{code}"
1665 );
1666 }
1667
1668 #[test]
1673 fn test_gen_bridge_all_includes_imports() {
1674 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1675 let config = make_trait_bridge_config(None, None);
1676 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1677 let generator = MockBridgeGenerator;
1678 let output = gen_bridge_all(&spec, &generator);
1679 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1680 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1681 }
1682
1683 #[test]
1684 fn test_gen_bridge_all_includes_wrapper_struct() {
1685 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1686 let config = make_trait_bridge_config(None, None);
1687 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1688 let generator = MockBridgeGenerator;
1689 let output = gen_bridge_all(&spec, &generator);
1690 assert!(
1691 output.code.contains("pub struct PyOcrBackendBridge"),
1692 "missing struct in:\n{}",
1693 output.code
1694 );
1695 }
1696
1697 #[test]
1698 fn test_gen_bridge_all_includes_constructor() {
1699 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1700 let config = make_trait_bridge_config(None, None);
1701 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1702 let generator = MockBridgeGenerator;
1703 let output = gen_bridge_all(&spec, &generator);
1704 assert!(
1705 output.code.contains("pub fn new("),
1706 "missing constructor in:\n{}",
1707 output.code
1708 );
1709 }
1710
1711 #[test]
1712 fn test_gen_bridge_all_includes_trait_impl() {
1713 let methods = vec![make_method(
1714 "process",
1715 vec![],
1716 TypeRef::String,
1717 false,
1718 false,
1719 None,
1720 None,
1721 )];
1722 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1723 let config = make_trait_bridge_config(None, None);
1724 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1725 let generator = MockBridgeGenerator;
1726 let output = gen_bridge_all(&spec, &generator);
1727 assert!(
1728 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1729 "missing trait impl in:\n{}",
1730 output.code
1731 );
1732 }
1733
1734 #[test]
1735 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1736 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1737 let config = make_trait_bridge_config(Some("Plugin"), None);
1738 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1739 let generator = MockBridgeGenerator;
1740 let output = gen_bridge_all(&spec, &generator);
1741 assert!(
1742 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1743 "missing plugin impl in:\n{}",
1744 output.code
1745 );
1746 }
1747
1748 #[test]
1749 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1750 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1751 let config = make_trait_bridge_config(None, None);
1752 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1753 let generator = MockBridgeGenerator;
1754 let output = gen_bridge_all(&spec, &generator);
1755 assert!(
1756 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1757 "unexpected plugin impl present without super_trait"
1758 );
1759 }
1760
1761 #[test]
1762 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1763 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1764 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1765 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1766 let generator = MockBridgeGenerator;
1767 let output = gen_bridge_all(&spec, &generator);
1768 assert!(
1769 output.code.contains("register_ocr_backend"),
1770 "missing registration fn in:\n{}",
1771 output.code
1772 );
1773 }
1774
1775 #[test]
1776 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1777 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1778 let config = make_trait_bridge_config(None, None);
1779 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1780 let generator = MockBridgeGenerator;
1781 let output = gen_bridge_all(&spec, &generator);
1782 assert!(
1783 !output.code.contains("register_ocr_backend"),
1784 "unexpected registration fn present:\n{}",
1785 output.code
1786 );
1787 }
1788
1789 #[test]
1790 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1791 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1792 let config = make_trait_bridge_config(None, None);
1793 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1794 let generator = MockBridgeGenerator;
1795 let output = gen_bridge_all(&spec, &generator);
1796 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1797 let impl_pos = output
1798 .code
1799 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1800 .unwrap();
1801 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1802 }
1803
1804 fn make_bridge(
1809 type_alias: Option<&str>,
1810 param_name: Option<&str>,
1811 bind_via: BridgeBinding,
1812 options_type: Option<&str>,
1813 options_field: Option<&str>,
1814 ) -> TraitBridgeConfig {
1815 TraitBridgeConfig {
1816 trait_name: "HtmlVisitor".to_string(),
1817 super_trait: None,
1818 registry_getter: None,
1819 register_fn: None,
1820 unregister_fn: None,
1821 clear_fn: None,
1822 type_alias: type_alias.map(str::to_string),
1823 param_name: param_name.map(str::to_string),
1824 register_extra_args: None,
1825 exclude_languages: vec![],
1826 bind_via,
1827 options_type: options_type.map(str::to_string),
1828 options_field: options_field.map(str::to_string),
1829 }
1830 }
1831
1832 #[test]
1833 fn find_bridge_param_returns_first_param_match_in_function_param_mode() {
1834 let func = make_func(
1835 "convert",
1836 vec![
1837 make_param("html", TypeRef::String, true),
1838 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1839 ],
1840 );
1841 let bridges = vec![make_bridge(
1842 Some("VisitorHandle"),
1843 Some("visitor"),
1844 BridgeBinding::FunctionParam,
1845 None,
1846 None,
1847 )];
1848 let result = find_bridge_param(&func, &bridges).expect("bridge match");
1849 assert_eq!(result.0, 1);
1850 }
1851
1852 #[test]
1853 fn find_bridge_param_skips_options_field_bridges() {
1854 let func = make_func(
1855 "convert",
1856 vec![
1857 make_param("html", TypeRef::String, true),
1858 make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1859 ],
1860 );
1861 let bridges = vec![make_bridge(
1862 Some("VisitorHandle"),
1863 Some("visitor"),
1864 BridgeBinding::OptionsField,
1865 Some("ConversionOptions"),
1866 Some("visitor"),
1867 )];
1868 assert!(
1869 find_bridge_param(&func, &bridges).is_none(),
1870 "bridges configured with bind_via=options_field must not be returned by find_bridge_param"
1871 );
1872 }
1873
1874 #[test]
1875 fn find_bridge_field_detects_field_via_alias() {
1876 let opts_type = TypeDef {
1877 name: "ConversionOptions".to_string(),
1878 rust_path: "mylib::ConversionOptions".to_string(),
1879 original_rust_path: String::new(),
1880 fields: vec![
1881 make_field("debug", TypeRef::Primitive(PrimitiveType::Bool)),
1882 make_field(
1883 "visitor",
1884 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
1885 ),
1886 ],
1887 methods: vec![],
1888 is_opaque: false,
1889 is_clone: true,
1890 is_copy: false,
1891 doc: String::new(),
1892 cfg: None,
1893 is_trait: false,
1894 has_default: true,
1895 has_stripped_cfg_fields: false,
1896 is_return_type: false,
1897 serde_rename_all: None,
1898 has_serde: false,
1899 super_traits: vec![],
1900 };
1901 let func = make_func(
1902 "convert",
1903 vec![
1904 make_param("html", TypeRef::String, true),
1905 make_param(
1906 "options",
1907 TypeRef::Optional(Box::new(TypeRef::Named("ConversionOptions".to_string()))),
1908 false,
1909 ),
1910 ],
1911 );
1912 let bridges = vec![make_bridge(
1913 Some("VisitorHandle"),
1914 Some("visitor"),
1915 BridgeBinding::OptionsField,
1916 Some("ConversionOptions"),
1917 None,
1918 )];
1919 let m = find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).expect("bridge field match");
1920 assert_eq!(m.param_index, 1);
1921 assert_eq!(m.param_name, "options");
1922 assert_eq!(m.options_type, "ConversionOptions");
1923 assert!(m.param_is_optional);
1924 assert_eq!(m.field_name, "visitor");
1925 }
1926
1927 #[test]
1928 fn find_bridge_field_returns_none_for_function_param_bridge() {
1929 let opts_type = TypeDef {
1930 name: "ConversionOptions".to_string(),
1931 rust_path: "mylib::ConversionOptions".to_string(),
1932 original_rust_path: String::new(),
1933 fields: vec![make_field(
1934 "visitor",
1935 TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
1936 )],
1937 methods: vec![],
1938 is_opaque: false,
1939 is_clone: true,
1940 is_copy: false,
1941 doc: String::new(),
1942 cfg: None,
1943 is_trait: false,
1944 has_default: true,
1945 has_stripped_cfg_fields: false,
1946 is_return_type: false,
1947 serde_rename_all: None,
1948 has_serde: false,
1949 super_traits: vec![],
1950 };
1951 let func = make_func(
1952 "convert",
1953 vec![make_param(
1954 "options",
1955 TypeRef::Named("ConversionOptions".to_string()),
1956 false,
1957 )],
1958 );
1959 let bridges = vec![make_bridge(
1960 Some("VisitorHandle"),
1961 Some("visitor"),
1962 BridgeBinding::FunctionParam,
1963 None,
1964 None,
1965 )];
1966 assert!(find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).is_none());
1967 }
1968}