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