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