1use alef_core::config::TraitBridgeConfig;
9use alef_core::ir::{MethodDef, ParamDef, TypeDef};
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}
29
30impl<'a> TraitBridgeSpec<'a> {
31 pub fn error_path(&self) -> String {
33 format!("{}::{}", self.core_import, self.error_type)
34 }
35
36 pub fn wrapper_name(&self) -> String {
38 format!("{}{}Bridge", self.wrapper_prefix, self.trait_def.name)
39 }
40
41 pub fn trait_snake(&self) -> String {
43 self.trait_def.name.to_snake_case()
44 }
45
46 pub fn trait_path(&self) -> String {
48 self.trait_def.rust_path.replace('-', "_")
49 }
50
51 pub fn required_methods(&self) -> Vec<&'a MethodDef> {
53 self.trait_def.methods.iter().filter(|m| !m.has_default_impl).collect()
54 }
55
56 pub fn optional_methods(&self) -> Vec<&'a MethodDef> {
58 self.trait_def.methods.iter().filter(|m| m.has_default_impl).collect()
59 }
60}
61
62pub trait TraitBridgeGenerator {
68 fn foreign_object_type(&self) -> &str;
70
71 fn bridge_imports(&self) -> Vec<String>;
73
74 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
79
80 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
84
85 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String;
90
91 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String;
97}
98
99pub fn gen_bridge_wrapper_struct(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
113 let wrapper = spec.wrapper_name();
114 let foreign_type = generator.foreign_object_type();
115 let mut out = String::with_capacity(512);
116
117 writeln!(
118 out,
119 "/// Wrapper that bridges a foreign {prefix} object to the `{trait_name}` trait.",
120 prefix = spec.wrapper_prefix,
121 trait_name = spec.trait_def.name,
122 )
123 .ok();
124 writeln!(out, "pub struct {wrapper} {{").ok();
125 writeln!(out, " inner: {foreign_type},").ok();
126 writeln!(out, " cached_name: String,").ok();
127 write!(out, "}}").ok();
128 out
129}
130
131pub fn gen_bridge_plugin_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
139 let super_trait_name = spec.bridge_config.super_trait.as_deref()?;
140
141 let wrapper = spec.wrapper_name();
142 let core_import = spec.core_import;
143
144 let super_trait_path = if super_trait_name.contains("::") {
146 super_trait_name.to_string()
147 } else {
148 format!("{core_import}::{super_trait_name}")
149 };
150
151 let mut out = String::with_capacity(1024);
155 writeln!(out, "impl {super_trait_path} for {wrapper} {{").ok();
156
157 writeln!(out, " fn name(&self) -> &str {{").ok();
159 writeln!(out, " &self.cached_name").ok();
160 writeln!(out, " }}").ok();
161 writeln!(out).ok();
162
163 writeln!(out, " fn version(&self) -> &str {{").ok();
165 let version_method = MethodDef {
167 name: "version".to_string(),
168 params: vec![],
169 return_type: alef_core::ir::TypeRef::String,
170 is_async: false,
171 is_static: false,
172 error_type: None,
173 doc: String::new(),
174 receiver: Some(alef_core::ir::ReceiverKind::Ref),
175 sanitized: false,
176 trait_source: None,
177 returns_ref: true,
178 returns_cow: false,
179 return_newtype_wrapper: None,
180 has_default_impl: false,
181 };
182 let version_body = generator.gen_sync_method_body(&version_method, spec);
183 for line in version_body.lines() {
184 writeln!(out, " {}", line.trim_start()).ok();
185 }
186 writeln!(out, " }}").ok();
187 writeln!(out).ok();
188
189 writeln!(
191 out,
192 " fn initialize(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {{"
193 )
194 .ok();
195 let init_method = MethodDef {
196 name: "initialize".to_string(),
197 params: vec![],
198 return_type: alef_core::ir::TypeRef::Unit,
199 is_async: false,
200 is_static: false,
201 error_type: Some("Box<dyn std::error::Error + Send + Sync>".to_string()),
202 doc: String::new(),
203 receiver: Some(alef_core::ir::ReceiverKind::Ref),
204 sanitized: false,
205 trait_source: None,
206 returns_ref: false,
207 returns_cow: false,
208 return_newtype_wrapper: None,
209 has_default_impl: true,
210 };
211 let init_body = generator.gen_sync_method_body(&init_method, spec);
212 for line in init_body.lines() {
213 writeln!(out, " {}", line.trim_start()).ok();
214 }
215 writeln!(out, " }}").ok();
216 writeln!(out).ok();
217
218 writeln!(
220 out,
221 " fn shutdown(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {{"
222 )
223 .ok();
224 let shutdown_method = MethodDef {
225 name: "shutdown".to_string(),
226 params: vec![],
227 return_type: alef_core::ir::TypeRef::Unit,
228 is_async: false,
229 is_static: false,
230 error_type: Some("Box<dyn std::error::Error + Send + Sync>".to_string()),
231 doc: String::new(),
232 receiver: Some(alef_core::ir::ReceiverKind::Ref),
233 sanitized: false,
234 trait_source: None,
235 returns_ref: false,
236 returns_cow: false,
237 return_newtype_wrapper: None,
238 has_default_impl: true,
239 };
240 let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
241 for line in shutdown_body.lines() {
242 writeln!(out, " {}", line.trim_start()).ok();
243 }
244 writeln!(out, " }}").ok();
245 write!(out, "}}").ok();
246 Some(out)
247}
248
249pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
254 let wrapper = spec.wrapper_name();
255 let trait_path = spec.trait_path();
256 let mut out = String::with_capacity(2048);
257
258 writeln!(out, "impl {trait_path} for {wrapper} {{").ok();
259
260 let own_methods: Vec<_> = spec
262 .trait_def
263 .methods
264 .iter()
265 .filter(|m| m.trait_source.is_none())
266 .collect();
267
268 for (i, method) in own_methods.iter().enumerate() {
269 if i > 0 {
270 writeln!(out).ok();
271 }
272
273 let async_kw = if method.is_async { "async " } else { "" };
275 let receiver = match &method.receiver {
276 Some(alef_core::ir::ReceiverKind::Ref) => "&self",
277 Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
278 Some(alef_core::ir::ReceiverKind::Owned) => "self",
279 None => "",
280 };
281
282 let params: Vec<String> = method
284 .params
285 .iter()
286 .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
287 .collect();
288
289 let all_params = if receiver.is_empty() {
290 params.join(", ")
291 } else if params.is_empty() {
292 receiver.to_string()
293 } else {
294 format!("{}, {}", receiver, params.join(", "))
295 };
296
297 let ret = format_return_type(&method.return_type, method.error_type.as_deref(), &spec.type_paths);
299
300 writeln!(out, " {async_kw}fn {}({all_params}) -> {ret} {{", method.name).ok();
301
302 let body = if method.is_async {
304 generator.gen_async_method_body(method, spec)
305 } else {
306 generator.gen_sync_method_body(method, spec)
307 };
308
309 for line in body.lines() {
310 writeln!(out, " {line}").ok();
311 }
312 writeln!(out, " }}").ok();
313 }
314
315 write!(out, "}}").ok();
316 out
317}
318
319pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
326 spec.bridge_config.register_fn.as_deref()?;
327 Some(generator.gen_registration_fn(spec))
328}
329
330pub struct BridgeOutput {
333 pub imports: Vec<String>,
335 pub code: String,
337}
338
339pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
345 let imports = generator.bridge_imports();
346 let mut out = String::with_capacity(4096);
347
348 out.push_str(&gen_bridge_wrapper_struct(spec, generator));
350 writeln!(out).ok();
351 writeln!(out).ok();
352
353 out.push_str(&generator.gen_constructor(spec));
355 writeln!(out).ok();
356 writeln!(out).ok();
357
358 if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
360 out.push_str(&plugin_impl);
361 writeln!(out).ok();
362 writeln!(out).ok();
363 }
364
365 out.push_str(&gen_bridge_trait_impl(spec, generator));
367
368 if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
370 writeln!(out).ok();
371 writeln!(out).ok();
372 out.push_str(®_fn_code);
373 }
374
375 BridgeOutput { imports, code: out }
376}
377
378pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
387 use alef_core::ir::{PrimitiveType, TypeRef};
388 match ty {
389 TypeRef::Primitive(p) => match p {
390 PrimitiveType::Bool => "bool",
391 PrimitiveType::U8 => "u8",
392 PrimitiveType::U16 => "u16",
393 PrimitiveType::U32 => "u32",
394 PrimitiveType::U64 => "u64",
395 PrimitiveType::I8 => "i8",
396 PrimitiveType::I16 => "i16",
397 PrimitiveType::I32 => "i32",
398 PrimitiveType::I64 => "i64",
399 PrimitiveType::F32 => "f32",
400 PrimitiveType::F64 => "f64",
401 PrimitiveType::Usize => "usize",
402 PrimitiveType::Isize => "isize",
403 }
404 .to_string(),
405 TypeRef::String => "String".to_string(),
406 TypeRef::Char => "char".to_string(),
407 TypeRef::Bytes => "Vec<u8>".to_string(),
408 TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
409 TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
410 TypeRef::Map(k, v) => format!(
411 "std::collections::HashMap<{}, {}>",
412 format_type_ref(k, type_paths),
413 format_type_ref(v, type_paths)
414 ),
415 TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
416 TypeRef::Path => "std::path::PathBuf".to_string(),
417 TypeRef::Unit => "()".to_string(),
418 TypeRef::Json => "serde_json::Value".to_string(),
419 TypeRef::Duration => "std::time::Duration".to_string(),
420 }
421}
422
423pub fn format_return_type(
425 ty: &alef_core::ir::TypeRef,
426 error_type: Option<&str>,
427 type_paths: &HashMap<String, String>,
428) -> String {
429 let inner = format_type_ref(ty, type_paths);
430 match error_type {
431 Some(err) => format!("Result<{inner}, {err}>"),
432 None => inner,
433 }
434}
435
436pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
446 use alef_core::ir::TypeRef;
447 if param.is_ref {
448 match ¶m.ty {
449 TypeRef::String => "&str".to_string(),
450 TypeRef::Bytes => "&[u8]".to_string(),
451 TypeRef::Path => "&std::path::Path".to_string(),
452 TypeRef::Vec(inner) => format!("&[{}]", format_type_ref(inner, type_paths)),
453 TypeRef::Named(name) => {
454 let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
455 format!("&{qualified}")
456 }
457 other => format_type_ref(other, type_paths),
459 }
460 } else {
461 format_type_ref(¶m.ty, type_paths)
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468 use alef_core::config::TraitBridgeConfig;
469 use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
470
471 fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
476 TraitBridgeConfig {
477 trait_name: "OcrBackend".to_string(),
478 super_trait: super_trait.map(str::to_string),
479 registry_getter: None,
480 register_fn: register_fn.map(str::to_string),
481 type_alias: None,
482 param_name: None,
483 }
484 }
485
486 fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
487 TypeDef {
488 name: name.to_string(),
489 rust_path: rust_path.to_string(),
490 original_rust_path: rust_path.to_string(),
491 fields: vec![],
492 methods,
493 is_opaque: true,
494 is_clone: false,
495 doc: String::new(),
496 cfg: None,
497 is_trait: true,
498 has_default: false,
499 has_stripped_cfg_fields: false,
500 is_return_type: false,
501 serde_rename_all: None,
502 has_serde: false,
503 super_traits: vec![],
504 }
505 }
506
507 fn make_method(
508 name: &str,
509 params: Vec<ParamDef>,
510 return_type: TypeRef,
511 is_async: bool,
512 has_default_impl: bool,
513 trait_source: Option<&str>,
514 error_type: Option<&str>,
515 ) -> MethodDef {
516 MethodDef {
517 name: name.to_string(),
518 params,
519 return_type,
520 is_async,
521 is_static: false,
522 error_type: error_type.map(str::to_string),
523 doc: String::new(),
524 receiver: Some(ReceiverKind::Ref),
525 sanitized: false,
526 trait_source: trait_source.map(str::to_string),
527 returns_ref: false,
528 returns_cow: false,
529 return_newtype_wrapper: None,
530 has_default_impl,
531 }
532 }
533
534 fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
535 ParamDef {
536 name: name.to_string(),
537 ty,
538 optional: false,
539 default: None,
540 sanitized: false,
541 typed_default: None,
542 is_ref,
543 is_mut: false,
544 newtype_wrapper: None,
545 }
546 }
547
548 fn make_spec<'a>(
549 trait_def: &'a TypeDef,
550 bridge_config: &'a TraitBridgeConfig,
551 wrapper_prefix: &'a str,
552 type_paths: HashMap<String, String>,
553 ) -> TraitBridgeSpec<'a> {
554 TraitBridgeSpec {
555 trait_def,
556 bridge_config,
557 core_import: "mylib",
558 wrapper_prefix,
559 type_paths,
560 error_type: "MyError".to_string(),
561 }
562 }
563
564 struct MockBridgeGenerator;
569
570 impl TraitBridgeGenerator for MockBridgeGenerator {
571 fn foreign_object_type(&self) -> &str {
572 "Py<PyAny>"
573 }
574
575 fn bridge_imports(&self) -> Vec<String> {
576 vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
577 }
578
579 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
580 format!("// sync body for {}", method.name)
581 }
582
583 fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
584 format!("// async body for {}", method.name)
585 }
586
587 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
588 format!(
589 "impl {} {{\n pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
590 spec.wrapper_name()
591 )
592 }
593
594 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
595 let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
596 format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
597 }
598 }
599
600 #[test]
605 fn test_wrapper_name() {
606 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
607 let config = make_trait_bridge_config(None, None);
608 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
609 assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
610 }
611
612 #[test]
613 fn test_trait_snake() {
614 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
615 let config = make_trait_bridge_config(None, None);
616 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
617 assert_eq!(spec.trait_snake(), "ocr_backend");
618 }
619
620 #[test]
621 fn test_trait_path_replaces_hyphens() {
622 let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
623 let config = make_trait_bridge_config(None, None);
624 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
625 assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
626 }
627
628 #[test]
629 fn test_required_methods_filters_no_default_impl() {
630 let methods = vec![
631 make_method("process", vec![], TypeRef::String, false, false, None, None),
632 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
633 make_method("detect", vec![], TypeRef::String, false, false, None, None),
634 ];
635 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
636 let config = make_trait_bridge_config(None, None);
637 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
638 let required = spec.required_methods();
639 assert_eq!(required.len(), 2);
640 assert!(required.iter().any(|m| m.name == "process"));
641 assert!(required.iter().any(|m| m.name == "detect"));
642 }
643
644 #[test]
645 fn test_optional_methods_filters_has_default_impl() {
646 let methods = vec![
647 make_method("process", vec![], TypeRef::String, false, false, None, None),
648 make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
649 make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
650 ];
651 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
652 let config = make_trait_bridge_config(None, None);
653 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
654 let optional = spec.optional_methods();
655 assert_eq!(optional.len(), 2);
656 assert!(optional.iter().any(|m| m.name == "initialize"));
657 assert!(optional.iter().any(|m| m.name == "shutdown"));
658 }
659
660 #[test]
661 fn test_error_path() {
662 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
663 let config = make_trait_bridge_config(None, None);
664 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
665 assert_eq!(spec.error_path(), "mylib::MyError");
666 }
667
668 #[test]
673 fn test_format_type_ref_primitives() {
674 let paths = HashMap::new();
675 let cases: Vec<(TypeRef, &str)> = vec![
676 (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
677 (TypeRef::Primitive(PrimitiveType::U8), "u8"),
678 (TypeRef::Primitive(PrimitiveType::U16), "u16"),
679 (TypeRef::Primitive(PrimitiveType::U32), "u32"),
680 (TypeRef::Primitive(PrimitiveType::U64), "u64"),
681 (TypeRef::Primitive(PrimitiveType::I8), "i8"),
682 (TypeRef::Primitive(PrimitiveType::I16), "i16"),
683 (TypeRef::Primitive(PrimitiveType::I32), "i32"),
684 (TypeRef::Primitive(PrimitiveType::I64), "i64"),
685 (TypeRef::Primitive(PrimitiveType::F32), "f32"),
686 (TypeRef::Primitive(PrimitiveType::F64), "f64"),
687 (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
688 (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
689 ];
690 for (ty, expected) in cases {
691 assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
692 }
693 }
694
695 #[test]
696 fn test_format_type_ref_string() {
697 assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
698 }
699
700 #[test]
701 fn test_format_type_ref_char() {
702 assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
703 }
704
705 #[test]
706 fn test_format_type_ref_bytes() {
707 assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
708 }
709
710 #[test]
711 fn test_format_type_ref_path() {
712 assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
713 }
714
715 #[test]
716 fn test_format_type_ref_unit() {
717 assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
718 }
719
720 #[test]
721 fn test_format_type_ref_json() {
722 assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
723 }
724
725 #[test]
726 fn test_format_type_ref_duration() {
727 assert_eq!(
728 format_type_ref(&TypeRef::Duration, &HashMap::new()),
729 "std::time::Duration"
730 );
731 }
732
733 #[test]
734 fn test_format_type_ref_optional() {
735 let ty = TypeRef::Optional(Box::new(TypeRef::String));
736 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
737 }
738
739 #[test]
740 fn test_format_type_ref_optional_nested() {
741 let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
742 PrimitiveType::U32,
743 )))));
744 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
745 }
746
747 #[test]
748 fn test_format_type_ref_vec() {
749 let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
750 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
751 }
752
753 #[test]
754 fn test_format_type_ref_vec_nested() {
755 let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
756 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
757 }
758
759 #[test]
760 fn test_format_type_ref_map() {
761 let ty = TypeRef::Map(
762 Box::new(TypeRef::String),
763 Box::new(TypeRef::Primitive(PrimitiveType::I64)),
764 );
765 assert_eq!(
766 format_type_ref(&ty, &HashMap::new()),
767 "std::collections::HashMap<String, i64>"
768 );
769 }
770
771 #[test]
772 fn test_format_type_ref_map_nested_value() {
773 let ty = TypeRef::Map(
774 Box::new(TypeRef::String),
775 Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
776 );
777 assert_eq!(
778 format_type_ref(&ty, &HashMap::new()),
779 "std::collections::HashMap<String, Vec<String>>"
780 );
781 }
782
783 #[test]
784 fn test_format_type_ref_named_without_type_paths() {
785 let ty = TypeRef::Named("Config".to_string());
786 assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
787 }
788
789 #[test]
790 fn test_format_type_ref_named_with_type_paths() {
791 let ty = TypeRef::Named("Config".to_string());
792 let mut paths = HashMap::new();
793 paths.insert("Config".to_string(), "mylib::Config".to_string());
794 assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
795 }
796
797 #[test]
798 fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
799 let ty = TypeRef::Named("Unknown".to_string());
800 let mut paths = HashMap::new();
801 paths.insert("Other".to_string(), "mylib::Other".to_string());
802 assert_eq!(format_type_ref(&ty, &paths), "Unknown");
803 }
804
805 #[test]
810 fn test_format_param_type_string_ref() {
811 let param = make_param("input", TypeRef::String, true);
812 assert_eq!(format_param_type(¶m, &HashMap::new()), "&str");
813 }
814
815 #[test]
816 fn test_format_param_type_string_owned() {
817 let param = make_param("input", TypeRef::String, false);
818 assert_eq!(format_param_type(¶m, &HashMap::new()), "String");
819 }
820
821 #[test]
822 fn test_format_param_type_bytes_ref() {
823 let param = make_param("data", TypeRef::Bytes, true);
824 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[u8]");
825 }
826
827 #[test]
828 fn test_format_param_type_bytes_owned() {
829 let param = make_param("data", TypeRef::Bytes, false);
830 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<u8>");
831 }
832
833 #[test]
834 fn test_format_param_type_path_ref() {
835 let param = make_param("path", TypeRef::Path, true);
836 assert_eq!(format_param_type(¶m, &HashMap::new()), "&std::path::Path");
837 }
838
839 #[test]
840 fn test_format_param_type_path_owned() {
841 let param = make_param("path", TypeRef::Path, false);
842 assert_eq!(format_param_type(¶m, &HashMap::new()), "std::path::PathBuf");
843 }
844
845 #[test]
846 fn test_format_param_type_vec_ref() {
847 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
848 assert_eq!(format_param_type(¶m, &HashMap::new()), "&[String]");
849 }
850
851 #[test]
852 fn test_format_param_type_vec_owned() {
853 let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
854 assert_eq!(format_param_type(¶m, &HashMap::new()), "Vec<String>");
855 }
856
857 #[test]
858 fn test_format_param_type_named_ref_with_type_paths() {
859 let mut paths = HashMap::new();
860 paths.insert("Config".to_string(), "mylib::Config".to_string());
861 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
862 assert_eq!(format_param_type(¶m, &paths), "&mylib::Config");
863 }
864
865 #[test]
866 fn test_format_param_type_named_ref_without_type_paths() {
867 let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
868 assert_eq!(format_param_type(¶m, &HashMap::new()), "&Config");
869 }
870
871 #[test]
872 fn test_format_param_type_primitive_ref_passes_by_value() {
873 let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
875 assert_eq!(format_param_type(¶m, &HashMap::new()), "u32");
876 }
877
878 #[test]
879 fn test_format_param_type_unit_ref_passes_by_value() {
880 let param = make_param("nothing", TypeRef::Unit, true);
881 assert_eq!(format_param_type(¶m, &HashMap::new()), "()");
882 }
883
884 #[test]
889 fn test_format_return_type_without_error() {
890 let result = format_return_type(&TypeRef::String, None, &HashMap::new());
891 assert_eq!(result, "String");
892 }
893
894 #[test]
895 fn test_format_return_type_with_error() {
896 let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new());
897 assert_eq!(result, "Result<String, MyError>");
898 }
899
900 #[test]
901 fn test_format_return_type_unit_with_error() {
902 let result = format_return_type(&TypeRef::Unit, Some("Box<dyn std::error::Error>"), &HashMap::new());
903 assert_eq!(result, "Result<(), Box<dyn std::error::Error>>");
904 }
905
906 #[test]
907 fn test_format_return_type_named_with_type_paths_and_error() {
908 let mut paths = HashMap::new();
909 paths.insert("Output".to_string(), "mylib::Output".to_string());
910 let result = format_return_type(&TypeRef::Named("Output".to_string()), Some("mylib::MyError"), &paths);
911 assert_eq!(result, "Result<mylib::Output, mylib::MyError>");
912 }
913
914 #[test]
919 fn test_gen_bridge_wrapper_struct_contains_struct_name() {
920 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
921 let config = make_trait_bridge_config(None, None);
922 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
923 let generator = MockBridgeGenerator;
924 let result = gen_bridge_wrapper_struct(&spec, &generator);
925 assert!(
926 result.contains("pub struct PyOcrBackendBridge"),
927 "missing struct declaration in:\n{result}"
928 );
929 }
930
931 #[test]
932 fn test_gen_bridge_wrapper_struct_contains_inner_field() {
933 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
934 let config = make_trait_bridge_config(None, None);
935 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
936 let generator = MockBridgeGenerator;
937 let result = gen_bridge_wrapper_struct(&spec, &generator);
938 assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
939 }
940
941 #[test]
942 fn test_gen_bridge_wrapper_struct_contains_cached_name() {
943 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
944 let config = make_trait_bridge_config(None, None);
945 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
946 let generator = MockBridgeGenerator;
947 let result = gen_bridge_wrapper_struct(&spec, &generator);
948 assert!(
949 result.contains("cached_name: String"),
950 "missing cached_name field in:\n{result}"
951 );
952 }
953
954 #[test]
959 fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
960 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
961 let config = make_trait_bridge_config(None, None);
962 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
963 let generator = MockBridgeGenerator;
964 assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
965 }
966
967 #[test]
968 fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
969 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
970 let config = make_trait_bridge_config(Some("Plugin"), None);
971 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
972 let generator = MockBridgeGenerator;
973 assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
974 }
975
976 #[test]
977 fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
978 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
979 let config = make_trait_bridge_config(Some("Plugin"), None);
980 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
981 let generator = MockBridgeGenerator;
982 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
983 assert!(
984 result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
985 "missing qualified super-trait path in:\n{result}"
986 );
987 }
988
989 #[test]
990 fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
991 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
992 let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
993 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
994 let generator = MockBridgeGenerator;
995 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
996 assert!(
997 result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
998 "wrong super-trait path in:\n{result}"
999 );
1000 }
1001
1002 #[test]
1003 fn test_gen_bridge_plugin_impl_contains_name_fn() {
1004 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1005 let config = make_trait_bridge_config(Some("Plugin"), None);
1006 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1007 let generator = MockBridgeGenerator;
1008 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1009 assert!(
1010 result.contains("fn name(") && result.contains("cached_name"),
1011 "missing name() using cached_name in:\n{result}"
1012 );
1013 }
1014
1015 #[test]
1016 fn test_gen_bridge_plugin_impl_contains_version_fn() {
1017 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1018 let config = make_trait_bridge_config(Some("Plugin"), None);
1019 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1020 let generator = MockBridgeGenerator;
1021 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1022 assert!(result.contains("fn version("), "missing version() in:\n{result}");
1023 }
1024
1025 #[test]
1026 fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1027 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1028 let config = make_trait_bridge_config(Some("Plugin"), None);
1029 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1030 let generator = MockBridgeGenerator;
1031 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1032 assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1033 }
1034
1035 #[test]
1036 fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1037 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1038 let config = make_trait_bridge_config(Some("Plugin"), None);
1039 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1040 let generator = MockBridgeGenerator;
1041 let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1042 assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1043 }
1044
1045 #[test]
1050 fn test_gen_bridge_trait_impl_includes_impl_header() {
1051 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1052 let config = make_trait_bridge_config(None, None);
1053 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1054 let generator = MockBridgeGenerator;
1055 let result = gen_bridge_trait_impl(&spec, &generator);
1056 assert!(
1057 result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1058 "missing impl header in:\n{result}"
1059 );
1060 }
1061
1062 #[test]
1063 fn test_gen_bridge_trait_impl_includes_method_signatures() {
1064 let methods = vec![make_method(
1065 "process",
1066 vec![],
1067 TypeRef::String,
1068 false,
1069 false,
1070 None,
1071 None,
1072 )];
1073 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1074 let config = make_trait_bridge_config(None, None);
1075 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1076 let generator = MockBridgeGenerator;
1077 let result = gen_bridge_trait_impl(&spec, &generator);
1078 assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1079 }
1080
1081 #[test]
1082 fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1083 let methods = vec![make_method(
1084 "process",
1085 vec![],
1086 TypeRef::String,
1087 false,
1088 false,
1089 None,
1090 None,
1091 )];
1092 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1093 let config = make_trait_bridge_config(None, None);
1094 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1095 let generator = MockBridgeGenerator;
1096 let result = gen_bridge_trait_impl(&spec, &generator);
1097 assert!(
1098 result.contains("// sync body for process"),
1099 "missing sync method body in:\n{result}"
1100 );
1101 }
1102
1103 #[test]
1104 fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1105 let methods = vec![make_method(
1106 "process_async",
1107 vec![],
1108 TypeRef::String,
1109 true,
1110 false,
1111 None,
1112 None,
1113 )];
1114 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1115 let config = make_trait_bridge_config(None, None);
1116 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1117 let generator = MockBridgeGenerator;
1118 let result = gen_bridge_trait_impl(&spec, &generator);
1119 assert!(
1120 result.contains("// async body for process_async"),
1121 "missing async method body in:\n{result}"
1122 );
1123 assert!(
1124 result.contains("async fn process_async("),
1125 "missing async keyword in method signature in:\n{result}"
1126 );
1127 }
1128
1129 #[test]
1130 fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1131 let methods = vec![
1133 make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1134 make_method(
1135 "inherited_method",
1136 vec![],
1137 TypeRef::String,
1138 false,
1139 false,
1140 Some("other_crate::OtherTrait"),
1141 None,
1142 ),
1143 ];
1144 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1145 let config = make_trait_bridge_config(None, None);
1146 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1147 let generator = MockBridgeGenerator;
1148 let result = gen_bridge_trait_impl(&spec, &generator);
1149 assert!(
1150 result.contains("fn own_method("),
1151 "own method should be present in:\n{result}"
1152 );
1153 assert!(
1154 !result.contains("fn inherited_method("),
1155 "inherited method should be filtered out in:\n{result}"
1156 );
1157 }
1158
1159 #[test]
1160 fn test_gen_bridge_trait_impl_method_with_params() {
1161 let params = vec![
1162 make_param("input", TypeRef::String, true),
1163 make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1164 ];
1165 let methods = vec![make_method(
1166 "process",
1167 params,
1168 TypeRef::String,
1169 false,
1170 false,
1171 None,
1172 None,
1173 )];
1174 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1175 let config = make_trait_bridge_config(None, None);
1176 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1177 let generator = MockBridgeGenerator;
1178 let result = gen_bridge_trait_impl(&spec, &generator);
1179 assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1180 assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1181 }
1182
1183 #[test]
1184 fn test_gen_bridge_trait_impl_return_type_with_error() {
1185 let methods = vec![make_method(
1186 "process",
1187 vec![],
1188 TypeRef::String,
1189 false,
1190 false,
1191 None,
1192 Some("MyError"),
1193 )];
1194 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1195 let config = make_trait_bridge_config(None, None);
1196 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1197 let generator = MockBridgeGenerator;
1198 let result = gen_bridge_trait_impl(&spec, &generator);
1199 assert!(
1200 result.contains("-> Result<String, MyError>"),
1201 "missing Result return type in:\n{result}"
1202 );
1203 }
1204
1205 #[test]
1210 fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1211 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1212 let config = make_trait_bridge_config(None, None);
1213 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1214 let generator = MockBridgeGenerator;
1215 assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1216 }
1217
1218 #[test]
1219 fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1220 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1221 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1222 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1223 let generator = MockBridgeGenerator;
1224 let result = gen_bridge_registration_fn(&spec, &generator);
1225 assert!(result.is_some());
1226 let code = result.unwrap();
1227 assert!(
1228 code.contains("register_ocr_backend"),
1229 "missing register fn name in:\n{code}"
1230 );
1231 }
1232
1233 #[test]
1238 fn test_gen_bridge_all_includes_imports() {
1239 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1240 let config = make_trait_bridge_config(None, None);
1241 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1242 let generator = MockBridgeGenerator;
1243 let output = gen_bridge_all(&spec, &generator);
1244 assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1245 assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1246 }
1247
1248 #[test]
1249 fn test_gen_bridge_all_includes_wrapper_struct() {
1250 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1251 let config = make_trait_bridge_config(None, None);
1252 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1253 let generator = MockBridgeGenerator;
1254 let output = gen_bridge_all(&spec, &generator);
1255 assert!(
1256 output.code.contains("pub struct PyOcrBackendBridge"),
1257 "missing struct in:\n{}",
1258 output.code
1259 );
1260 }
1261
1262 #[test]
1263 fn test_gen_bridge_all_includes_constructor() {
1264 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1265 let config = make_trait_bridge_config(None, None);
1266 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1267 let generator = MockBridgeGenerator;
1268 let output = gen_bridge_all(&spec, &generator);
1269 assert!(
1270 output.code.contains("pub fn new("),
1271 "missing constructor in:\n{}",
1272 output.code
1273 );
1274 }
1275
1276 #[test]
1277 fn test_gen_bridge_all_includes_trait_impl() {
1278 let methods = vec![make_method(
1279 "process",
1280 vec![],
1281 TypeRef::String,
1282 false,
1283 false,
1284 None,
1285 None,
1286 )];
1287 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1288 let config = make_trait_bridge_config(None, None);
1289 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1290 let generator = MockBridgeGenerator;
1291 let output = gen_bridge_all(&spec, &generator);
1292 assert!(
1293 output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1294 "missing trait impl in:\n{}",
1295 output.code
1296 );
1297 }
1298
1299 #[test]
1300 fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1301 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1302 let config = make_trait_bridge_config(Some("Plugin"), None);
1303 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1304 let generator = MockBridgeGenerator;
1305 let output = gen_bridge_all(&spec, &generator);
1306 assert!(
1307 output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1308 "missing plugin impl in:\n{}",
1309 output.code
1310 );
1311 }
1312
1313 #[test]
1314 fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1315 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1316 let config = make_trait_bridge_config(None, None);
1317 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1318 let generator = MockBridgeGenerator;
1319 let output = gen_bridge_all(&spec, &generator);
1320 assert!(
1321 !output.code.contains("fn name(") || !output.code.contains("cached_name"),
1322 "unexpected plugin impl present without super_trait"
1323 );
1324 }
1325
1326 #[test]
1327 fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1328 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1329 let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1330 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1331 let generator = MockBridgeGenerator;
1332 let output = gen_bridge_all(&spec, &generator);
1333 assert!(
1334 output.code.contains("register_ocr_backend"),
1335 "missing registration fn in:\n{}",
1336 output.code
1337 );
1338 }
1339
1340 #[test]
1341 fn test_gen_bridge_all_no_registration_fn_when_absent() {
1342 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1343 let config = make_trait_bridge_config(None, None);
1344 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1345 let generator = MockBridgeGenerator;
1346 let output = gen_bridge_all(&spec, &generator);
1347 assert!(
1348 !output.code.contains("register_ocr_backend"),
1349 "unexpected registration fn present:\n{}",
1350 output.code
1351 );
1352 }
1353
1354 #[test]
1355 fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1356 let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1357 let config = make_trait_bridge_config(None, None);
1358 let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1359 let generator = MockBridgeGenerator;
1360 let output = gen_bridge_all(&spec, &generator);
1361 let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1362 let impl_pos = output
1363 .code
1364 .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1365 .unwrap();
1366 assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1367 }
1368}