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