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