Skip to main content

alef_codegen/generators/
trait_bridge.rs

1//! Shared trait bridge code generation.
2//!
3//! Generates wrapper structs that allow foreign language objects (Python, JS, etc.)
4//! to implement Rust traits via FFI. Each backend implements [`TraitBridgeGenerator`]
5//! to provide language-specific dispatch logic; the shared functions in this module
6//! handle the structural boilerplate.
7
8use 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
14/// Everything needed to generate a trait bridge for one trait.
15pub struct TraitBridgeSpec<'a> {
16    /// The trait definition from the IR.
17    pub trait_def: &'a TypeDef,
18    /// Bridge configuration from `alef.toml`.
19    pub bridge_config: &'a TraitBridgeConfig,
20    /// Core crate import path (e.g., `"kreuzberg"`).
21    pub core_import: &'a str,
22    /// Language-specific prefix for the wrapper type (e.g., `"Python"`, `"Js"`, `"Wasm"`).
23    pub wrapper_prefix: &'a str,
24    /// Map of type name → fully-qualified Rust path for qualifying `Named` types.
25    pub type_paths: HashMap<String, String>,
26    /// The crate's error type name (e.g., `"KreuzbergError"`). Defaults to `"Error"`.
27    pub error_type: String,
28    /// Error constructor pattern. `{msg}` is replaced with the message expression.
29    pub error_constructor: String,
30}
31
32impl<'a> TraitBridgeSpec<'a> {
33    /// Fully qualified error type path (e.g., `"kreuzberg::KreuzbergError"`).
34    ///
35    /// If `error_type` already looks fully-qualified (contains `::`) or is a generic
36    /// type expression (contains `<`), it is returned as-is without prefixing
37    /// `core_import`. This lets backends specify rich error types like
38    /// `"Box<dyn std::error::Error + Send + Sync>"` directly.
39    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    /// Generate an error construction expression from a message expression.
48    pub fn make_error(&self, msg_expr: &str) -> String {
49        self.error_constructor.replace("{msg}", msg_expr)
50    }
51
52    /// Wrapper struct name: `{prefix}{TraitName}Bridge` (e.g., `PythonOcrBackendBridge`).
53    pub fn wrapper_name(&self) -> String {
54        format!("{}{}Bridge", self.wrapper_prefix, self.trait_def.name)
55    }
56
57    /// Snake-case version of the trait name (e.g., `"ocr_backend"`).
58    pub fn trait_snake(&self) -> String {
59        self.trait_def.name.to_snake_case()
60    }
61
62    /// Full Rust path to the trait (e.g., `kreuzberg::OcrBackend`).
63    pub fn trait_path(&self) -> String {
64        self.trait_def.rust_path.replace('-', "_")
65    }
66
67    /// Methods that are required (no default impl) — must be provided by the foreign object.
68    pub fn required_methods(&self) -> Vec<&'a MethodDef> {
69        self.trait_def.methods.iter().filter(|m| !m.has_default_impl).collect()
70    }
71
72    /// Methods that have a default impl — optional on the foreign object.
73    pub fn optional_methods(&self) -> Vec<&'a MethodDef> {
74        self.trait_def.methods.iter().filter(|m| m.has_default_impl).collect()
75    }
76}
77
78/// Backend-specific trait bridge generation.
79///
80/// Each binding backend (PyO3, NAPI-RS, wasm-bindgen, etc.) implements this trait
81/// to provide the language-specific parts of bridge codegen. The shared functions
82/// in this module call these methods to fill in the backend-dependent pieces.
83pub trait TraitBridgeGenerator {
84    /// The type of the wrapped foreign object (e.g., `"Py<PyAny>"`, `"ThreadsafeFunction"`).
85    fn foreign_object_type(&self) -> &str;
86
87    /// Additional `use` imports needed for the bridge code.
88    fn bridge_imports(&self) -> Vec<String>;
89
90    /// Generate the body of a synchronous method bridge.
91    ///
92    /// The returned string is inserted inside the trait impl method. It should
93    /// call through to the foreign object and convert the result.
94    fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
95
96    /// Generate the body of an async method bridge.
97    ///
98    /// The returned string is the body of a `Box::pin(async move { ... })` block.
99    fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String;
100
101    /// Generate the constructor body that validates and wraps the foreign object.
102    ///
103    /// Should check that the foreign object provides all required methods and
104    /// return `Self { ... }` on success.
105    fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String;
106
107    /// Generate the complete registration function including attributes, signature, and body.
108    ///
109    /// Each backend needs different function signatures (PyO3 takes `py: Python`,
110    /// NAPI takes `#[napi]` with JS params, FFI takes `extern "C"` with raw pointers),
111    /// so the generator owns the full function.
112    fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String;
113
114    /// Generate an unregistration function for the bridge.
115    ///
116    /// Default implementation returns an empty string — backends opt in by
117    /// emitting a function whose name is `spec.bridge_config.unregister_fn`
118    /// (when set) and whose body calls into the host crate's
119    /// `unregister_*(name)` plugin entry point.
120    fn gen_unregistration_fn(&self, _spec: &TraitBridgeSpec) -> String {
121        String::new()
122    }
123
124    /// Generate a clear-all-plugins function for the bridge.
125    ///
126    /// Default implementation returns an empty string — backends opt in by
127    /// emitting a function whose name is `spec.bridge_config.clear_fn`
128    /// (when set) and whose body calls into the host crate's `clear_*()`
129    /// plugin entry point. Typically used in test teardown.
130    fn gen_clear_fn(&self, _spec: &TraitBridgeSpec) -> String {
131        String::new()
132    }
133
134    /// Whether the `#[async_trait]` macro should require `Send` on its futures.
135    ///
136    /// Returns `true` (default) for most targets. WASM is single-threaded so its
137    /// trait bounds don't include `Send`; implementors should return `false` there.
138    fn async_trait_is_send(&self) -> bool {
139        true
140    }
141}
142
143// ---------------------------------------------------------------------------
144// Shared generation functions
145// ---------------------------------------------------------------------------
146
147/// Generate the wrapper struct holding the foreign object and cached fields.
148///
149/// Produces a struct like:
150/// ```ignore
151/// pub struct PythonOcrBackendBridge {
152///     inner: Py<PyAny>,
153///     cached_name: String,
154/// }
155/// ```
156pub fn gen_bridge_wrapper_struct(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
157    let wrapper = spec.wrapper_name();
158    let foreign_type = generator.foreign_object_type();
159    let mut out = String::with_capacity(512);
160
161    writeln!(
162        out,
163        "/// Wrapper that bridges a foreign {prefix} object to the `{trait_name}` trait.",
164        prefix = spec.wrapper_prefix,
165        trait_name = spec.trait_def.name,
166    )
167    .ok();
168    writeln!(out, "pub struct {wrapper} {{").ok();
169    writeln!(out, "    inner: {foreign_type},").ok();
170    writeln!(out, "    cached_name: String,").ok();
171    write!(out, "}}").ok();
172    out
173}
174
175/// Generate `impl SuperTrait for Wrapper` when the bridge config specifies a super-trait.
176///
177/// Forwards `name()`, `version()`, `initialize()`, and `shutdown()` to the
178/// foreign object, using `cached_name` for `name()`.
179///
180/// The super-trait path is derived from the config's `super_trait` field. If it
181/// contains `::`, it's used as-is; otherwise it's qualified as `{core_import}::{super_trait}`.
182pub fn gen_bridge_plugin_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
183    let super_trait_name = spec.bridge_config.super_trait.as_deref()?;
184
185    let wrapper = spec.wrapper_name();
186    let core_import = spec.core_import;
187
188    // Derive the fully-qualified super-trait path
189    let super_trait_path = if super_trait_name.contains("::") {
190        super_trait_name.to_string()
191    } else {
192        format!("{core_import}::{super_trait_name}")
193    };
194
195    // Build synthetic MethodDefs for the Plugin methods and delegate to the generator
196    // for the actual call bodies. The Plugin trait interface is well-known: name(),
197    // version(), initialize(), shutdown().
198    let mut out = String::with_capacity(1024);
199    writeln!(out, "impl {super_trait_path} for {wrapper} {{").ok();
200
201    // name() -> &str — uses cached field
202    writeln!(out, "    fn name(&self) -> &str {{").ok();
203    writeln!(out, "        &self.cached_name").ok();
204    writeln!(out, "    }}").ok();
205    writeln!(out).ok();
206
207    let error_path = spec.error_path();
208
209    // version() -> String — delegate to foreign object
210    writeln!(out, "    fn version(&self) -> String {{").ok();
211    let version_method = MethodDef {
212        name: "version".to_string(),
213        params: vec![],
214        return_type: alef_core::ir::TypeRef::String,
215        is_async: false,
216        is_static: false,
217        error_type: None,
218        doc: String::new(),
219        receiver: Some(alef_core::ir::ReceiverKind::Ref),
220        sanitized: false,
221        trait_source: None,
222        returns_ref: false,
223        returns_cow: false,
224        return_newtype_wrapper: None,
225        has_default_impl: false,
226    };
227    let version_body = generator.gen_sync_method_body(&version_method, spec);
228    for line in version_body.lines() {
229        writeln!(out, "        {}", line.trim_start()).ok();
230    }
231    writeln!(out, "    }}").ok();
232    writeln!(out).ok();
233
234    // initialize() -> Result<(), ErrorType>
235    writeln!(
236        out,
237        "    fn initialize(&self) -> std::result::Result<(), {error_path}> {{"
238    )
239    .ok();
240    let init_method = MethodDef {
241        name: "initialize".to_string(),
242        params: vec![],
243        return_type: alef_core::ir::TypeRef::Unit,
244        is_async: false,
245        is_static: false,
246        error_type: Some(error_path.clone()),
247        doc: String::new(),
248        receiver: Some(alef_core::ir::ReceiverKind::Ref),
249        sanitized: false,
250        trait_source: None,
251        returns_ref: false,
252        returns_cow: false,
253        return_newtype_wrapper: None,
254        has_default_impl: true,
255    };
256    let init_body = generator.gen_sync_method_body(&init_method, spec);
257    for line in init_body.lines() {
258        writeln!(out, "        {}", line.trim_start()).ok();
259    }
260    writeln!(out, "    }}").ok();
261    writeln!(out).ok();
262
263    // shutdown() -> Result<(), ErrorType>
264    writeln!(
265        out,
266        "    fn shutdown(&self) -> std::result::Result<(), {error_path}> {{"
267    )
268    .ok();
269    let shutdown_method = MethodDef {
270        name: "shutdown".to_string(),
271        params: vec![],
272        return_type: alef_core::ir::TypeRef::Unit,
273        is_async: false,
274        is_static: false,
275        error_type: Some(error_path.clone()),
276        doc: String::new(),
277        receiver: Some(alef_core::ir::ReceiverKind::Ref),
278        sanitized: false,
279        trait_source: None,
280        returns_ref: false,
281        returns_cow: false,
282        return_newtype_wrapper: None,
283        has_default_impl: true,
284    };
285    let shutdown_body = generator.gen_sync_method_body(&shutdown_method, spec);
286    for line in shutdown_body.lines() {
287        writeln!(out, "        {}", line.trim_start()).ok();
288    }
289    writeln!(out, "    }}").ok();
290    write!(out, "}}").ok();
291    Some(out)
292}
293
294/// Generate `impl Trait for Wrapper` dispatching each method through the generator.
295///
296/// Every method on the trait (including those with `has_default_impl`) gets a
297/// generated body that forwards to the foreign object.
298pub fn gen_bridge_trait_impl(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> String {
299    let wrapper = spec.wrapper_name();
300    let trait_path = spec.trait_path();
301    let mut out = String::with_capacity(2048);
302
303    // Add #[async_trait] when the trait has async methods (needed for async_trait macro compatibility).
304    // On non-Send targets (e.g. WASM), use `(?Send)` to drop the `Send` bound on futures.
305    let has_async_methods = spec
306        .trait_def
307        .methods
308        .iter()
309        .any(|m| m.is_async && m.trait_source.is_none());
310    if has_async_methods {
311        if generator.async_trait_is_send() {
312            writeln!(out, "#[async_trait::async_trait]").ok();
313        } else {
314            writeln!(out, "#[async_trait::async_trait(?Send)]").ok();
315        }
316    }
317    writeln!(out, "impl {trait_path} for {wrapper} {{").ok();
318
319    // Filter out methods inherited from super-traits (they're handled by gen_bridge_plugin_impl)
320    let own_methods: Vec<_> = spec
321        .trait_def
322        .methods
323        .iter()
324        .filter(|m| m.trait_source.is_none())
325        .collect();
326
327    for (i, method) in own_methods.iter().enumerate() {
328        if i > 0 {
329            writeln!(out).ok();
330        }
331
332        // Build the method signature
333        let async_kw = if method.is_async { "async " } else { "" };
334        let receiver = match &method.receiver {
335            Some(alef_core::ir::ReceiverKind::Ref) => "&self",
336            Some(alef_core::ir::ReceiverKind::RefMut) => "&mut self",
337            Some(alef_core::ir::ReceiverKind::Owned) => "self",
338            None => "",
339        };
340
341        // Build params (excluding self), using format_param_type to respect is_ref/is_mut
342        let params: Vec<String> = method
343            .params
344            .iter()
345            .map(|p| format!("{}: {}", p.name, format_param_type(p, &spec.type_paths)))
346            .collect();
347
348        let all_params = if receiver.is_empty() {
349            params.join(", ")
350        } else if params.is_empty() {
351            receiver.to_string()
352        } else {
353            format!("{}, {}", receiver, params.join(", "))
354        };
355
356        // Return type — override the IR's error type with the configured crate error type
357        // so the impl matches the actual trait definition (the IR may extract a different
358        // error type like anyhow::Error from re-exports or type alias resolution).
359        let error_override = method.error_type.as_ref().map(|_| spec.error_path());
360        let ret = format_return_type(&method.return_type, error_override.as_deref(), &spec.type_paths);
361
362        writeln!(out, "    {async_kw}fn {}({all_params}) -> {ret} {{", method.name).ok();
363
364        // Generate body: async methods use Box::pin, sync methods call directly
365        let body = if method.is_async {
366            generator.gen_async_method_body(method, spec)
367        } else {
368            generator.gen_sync_method_body(method, spec)
369        };
370
371        for line in body.lines() {
372            writeln!(out, "        {line}").ok();
373        }
374        writeln!(out, "    }}").ok();
375    }
376
377    write!(out, "}}").ok();
378    out
379}
380
381/// Generate the `register_xxx()` function that wraps a foreign object and
382/// inserts it into the plugin registry.
383///
384/// Returns `None` when `bridge_config.register_fn` is absent (per-call bridge pattern).
385/// The generator owns the full function (attributes, signature, body) because each
386/// backend needs different signatures.
387pub fn gen_bridge_registration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
388    spec.bridge_config.register_fn.as_deref()?;
389    Some(generator.gen_registration_fn(spec))
390}
391
392/// Generate the `unregister_xxx(name)` function that removes a previously
393/// registered plugin from the registry.
394///
395/// Returns `None` when `bridge_config.unregister_fn` is absent or when the
396/// backend hasn't opted in (returns the empty string from
397/// [`TraitBridgeGenerator::gen_unregistration_fn`]).
398pub fn gen_bridge_unregistration_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
399    spec.bridge_config.unregister_fn.as_deref()?;
400    let body = generator.gen_unregistration_fn(spec);
401    if body.is_empty() { None } else { Some(body) }
402}
403
404/// Generate the `clear_xxx()` function that removes all registered plugins
405/// of this type.
406///
407/// Returns `None` when `bridge_config.clear_fn` is absent or when the
408/// backend hasn't opted in (returns the empty string from
409/// [`TraitBridgeGenerator::gen_clear_fn`]).
410pub fn gen_bridge_clear_fn(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> Option<String> {
411    spec.bridge_config.clear_fn.as_deref()?;
412    let body = generator.gen_clear_fn(spec);
413    if body.is_empty() { None } else { Some(body) }
414}
415
416/// Resolve the FQN of a host-crate plugin function (e.g.
417/// `kreuzberg::plugins::ocr::unregister_ocr_backend`) given the bridge's
418/// `registry_getter` path. The convention used by host crates is:
419///
420/// - `registry_getter = "kreuzberg::plugins::registry::get_ocr_backend_registry"`
421/// - top-level fn      = `kreuzberg::plugins::ocr::unregister_ocr_backend`
422///
423/// We rewrite `::registry::get_*_registry` to `::<sub>::<fn_name>` where
424/// `<sub>` is the trait submodule name (extracted from `_*_registry`).
425/// When the heuristic fails (no `registry_getter`, unexpected shape), we
426/// fall back to `{core_import}::plugins::{fn_name}` so the user can rely on
427/// a re-export.
428///
429/// Shared by every backend that opts in to `unregister_*`/`clear_*` codegen
430/// (pyo3, napi, magnus, php, rustler, gleam, extendr, dart, swift, kotlin,
431/// wasm). Replaces the duplicated `<lang>_host_function_path` helpers that
432/// each backend used to define.
433pub fn host_function_path(spec: &TraitBridgeSpec, fn_name: &str) -> String {
434    if let Some(getter) = spec.bridge_config.registry_getter.as_deref() {
435        let last = getter.rsplit("::").next().unwrap_or("");
436        if let Some(sub) = last.strip_prefix("get_").and_then(|s| s.strip_suffix("_registry")) {
437            let prefix_end = getter.len() - last.len();
438            let prefix = &getter[..prefix_end];
439            let prefix = prefix.trim_end_matches("registry::");
440            return format!("{prefix}{sub}::{fn_name}");
441        }
442    }
443    format!("{}::plugins::{}", spec.core_import, fn_name)
444}
445
446/// Result of trait bridge generation: imports (to be added via `builder.add_import`)
447/// and the code body (to be added via `builder.add_item`).
448pub struct BridgeOutput {
449    /// Import paths (e.g., `"std::sync::Arc"`) — callers should add via `builder.add_import()`.
450    pub imports: Vec<String>,
451    /// The generated code (struct, impls, registration fn).
452    pub code: String,
453}
454
455/// Generate the complete trait bridge code block: struct, impls, and
456/// optionally a registration function.
457///
458/// Returns [`BridgeOutput`] with imports separated from code so callers can
459/// route imports through `builder.add_import()` (which deduplicates).
460pub fn gen_bridge_all(spec: &TraitBridgeSpec, generator: &dyn TraitBridgeGenerator) -> BridgeOutput {
461    let imports = generator.bridge_imports();
462    let mut out = String::with_capacity(4096);
463
464    // Wrapper struct
465    out.push_str(&gen_bridge_wrapper_struct(spec, generator));
466    writeln!(out).ok();
467    writeln!(out).ok();
468
469    // Constructor (impl block with new())
470    out.push_str(&generator.gen_constructor(spec));
471    writeln!(out).ok();
472    writeln!(out).ok();
473
474    // Plugin super-trait impl (if applicable)
475    if let Some(plugin_impl) = gen_bridge_plugin_impl(spec, generator) {
476        out.push_str(&plugin_impl);
477        writeln!(out).ok();
478        writeln!(out).ok();
479    }
480
481    // Trait impl
482    out.push_str(&gen_bridge_trait_impl(spec, generator));
483
484    // Registration function — only when register_fn is configured
485    if let Some(reg_fn_code) = gen_bridge_registration_fn(spec, generator) {
486        writeln!(out).ok();
487        writeln!(out).ok();
488        out.push_str(&reg_fn_code);
489    }
490
491    // Unregistration function — only when unregister_fn is configured AND
492    // the backend has opted in (non-empty body).
493    if let Some(unreg_fn_code) = gen_bridge_unregistration_fn(spec, generator) {
494        writeln!(out).ok();
495        writeln!(out).ok();
496        out.push_str(&unreg_fn_code);
497    }
498
499    // Clear-all function — only when clear_fn is configured AND the backend
500    // has opted in (non-empty body).
501    if let Some(clear_fn_code) = gen_bridge_clear_fn(spec, generator) {
502        writeln!(out).ok();
503        writeln!(out).ok();
504        out.push_str(&clear_fn_code);
505    }
506
507    BridgeOutput { imports, code: out }
508}
509
510// ---------------------------------------------------------------------------
511// Helpers
512// ---------------------------------------------------------------------------
513
514/// Format a `TypeRef` as a Rust type string for use in trait method signatures.
515///
516/// `type_paths` qualifies `Named` types with their full Rust path (e.g., `"Config"` →
517/// `"kreuzberg::Config"`). If a name isn't in `type_paths`, it's used as-is.
518pub fn format_type_ref(ty: &alef_core::ir::TypeRef, type_paths: &HashMap<String, String>) -> String {
519    use alef_core::ir::{PrimitiveType, TypeRef};
520    match ty {
521        TypeRef::Primitive(p) => match p {
522            PrimitiveType::Bool => "bool",
523            PrimitiveType::U8 => "u8",
524            PrimitiveType::U16 => "u16",
525            PrimitiveType::U32 => "u32",
526            PrimitiveType::U64 => "u64",
527            PrimitiveType::I8 => "i8",
528            PrimitiveType::I16 => "i16",
529            PrimitiveType::I32 => "i32",
530            PrimitiveType::I64 => "i64",
531            PrimitiveType::F32 => "f32",
532            PrimitiveType::F64 => "f64",
533            PrimitiveType::Usize => "usize",
534            PrimitiveType::Isize => "isize",
535        }
536        .to_string(),
537        TypeRef::String => "String".to_string(),
538        TypeRef::Char => "char".to_string(),
539        TypeRef::Bytes => "Vec<u8>".to_string(),
540        TypeRef::Optional(inner) => format!("Option<{}>", format_type_ref(inner, type_paths)),
541        TypeRef::Vec(inner) => format!("Vec<{}>", format_type_ref(inner, type_paths)),
542        TypeRef::Map(k, v) => format!(
543            "std::collections::HashMap<{}, {}>",
544            format_type_ref(k, type_paths),
545            format_type_ref(v, type_paths)
546        ),
547        TypeRef::Named(name) => type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone()),
548        TypeRef::Path => "std::path::PathBuf".to_string(),
549        TypeRef::Unit => "()".to_string(),
550        TypeRef::Json => "serde_json::Value".to_string(),
551        TypeRef::Duration => "std::time::Duration".to_string(),
552    }
553}
554
555/// Format a return type, wrapping in `Result` when an error type is present.
556pub fn format_return_type(
557    ty: &alef_core::ir::TypeRef,
558    error_type: Option<&str>,
559    type_paths: &HashMap<String, String>,
560) -> String {
561    let inner = format_type_ref(ty, type_paths);
562    match error_type {
563        Some(err) => format!("std::result::Result<{inner}, {err}>"),
564        None => inner,
565    }
566}
567
568/// Format a parameter type, respecting `is_ref`, `is_mut`, and `optional` from the IR.
569///
570/// Unlike [`format_type_ref`], this function produces reference types when the
571/// original Rust parameter was a `&T` or `&mut T`, and wraps in `Option<>` when
572/// `param.optional` is true:
573/// - `String + is_ref` → `&str`
574/// - `String + is_ref + optional` → `Option<&str>`
575/// - `Bytes + is_ref` → `&[u8]`
576/// - `Path + is_ref` → `&std::path::Path`
577/// - `Vec<T> + is_ref` → `&[T]`
578/// - `Named(n) + is_ref` → `&{qualified_name}`
579pub fn format_param_type(param: &ParamDef, type_paths: &HashMap<String, String>) -> String {
580    use alef_core::ir::TypeRef;
581    let base = if param.is_ref {
582        let mutability = if param.is_mut { "mut " } else { "" };
583        match &param.ty {
584            TypeRef::String => format!("&{mutability}str"),
585            TypeRef::Bytes => format!("&{mutability}[u8]"),
586            TypeRef::Path => format!("&{mutability}std::path::Path"),
587            TypeRef::Vec(inner) => format!("&{mutability}[{}]", format_type_ref(inner, type_paths)),
588            TypeRef::Named(name) => {
589                let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
590                format!("&{mutability}{qualified}")
591            }
592            TypeRef::Optional(inner) => {
593                // Preserve the Option wrapper but apply the ref transformation to the inner type.
594                // e.g. Option<String> + is_ref → Option<&str>
595                //      Option<Vec<T>> + is_ref → Option<&[T]>
596                let inner_type_str = match inner.as_ref() {
597                    TypeRef::String => format!("&{mutability}str"),
598                    TypeRef::Bytes => format!("&{mutability}[u8]"),
599                    TypeRef::Path => format!("&{mutability}std::path::Path"),
600                    TypeRef::Vec(v) => format!("&{mutability}[{}]", format_type_ref(v, type_paths)),
601                    TypeRef::Named(name) => {
602                        let qualified = type_paths.get(name.as_str()).cloned().unwrap_or_else(|| name.clone());
603                        format!("&{mutability}{qualified}")
604                    }
605                    // Primitives and other Copy types: pass by value inside Option
606                    other => format_type_ref(other, type_paths),
607                };
608                // Already wrapped in Option — return directly to avoid double-wrapping below.
609                return format!("Option<{inner_type_str}>");
610            }
611            // All other types are Copy/small — pass by value even when is_ref is set
612            other => format_type_ref(other, type_paths),
613        }
614    } else {
615        format_type_ref(&param.ty, type_paths)
616    };
617
618    // Wrap in Option<> when the parameter is optional (e.g. `title: Option<&str>`).
619    // The TypeRef::Optional arm above returns early, so this only fires for the
620    // `optional: true` IR flag pattern where ty is the unwrapped inner type.
621    if param.optional {
622        format!("Option<{base}>")
623    } else {
624        base
625    }
626}
627
628// ---------------------------------------------------------------------------
629// Shared helpers — used by all backend trait_bridge modules.
630// ---------------------------------------------------------------------------
631
632/// Map a Rust primitive to its type string.
633pub fn prim(p: &PrimitiveType) -> &'static str {
634    use PrimitiveType::*;
635    match p {
636        Bool => "bool",
637        U8 => "u8",
638        U16 => "u16",
639        U32 => "u32",
640        U64 => "u64",
641        I8 => "i8",
642        I16 => "i16",
643        I32 => "i32",
644        I64 => "i64",
645        F32 => "f32",
646        F64 => "f64",
647        Usize => "usize",
648        Isize => "isize",
649    }
650}
651
652/// Map a `TypeRef` to its Rust source type string for use in trait bridge method
653/// signatures. `ci` is the core import path (e.g. `"kreuzberg"`), `tp` maps
654/// type names to fully-qualified paths.
655pub fn bridge_param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
656    match ty {
657        TypeRef::Bytes if is_ref => "&[u8]".into(),
658        TypeRef::Bytes => "Vec<u8>".into(),
659        TypeRef::String if is_ref => "&str".into(),
660        TypeRef::String => "String".into(),
661        TypeRef::Path if is_ref => "&std::path::Path".into(),
662        TypeRef::Path => "std::path::PathBuf".into(),
663        TypeRef::Named(n) => {
664            let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
665            if is_ref { format!("&{qualified}") } else { qualified }
666        }
667        TypeRef::Vec(inner) => format!("Vec<{}>", bridge_param_type(inner, ci, false, tp)),
668        TypeRef::Optional(inner) => format!("Option<{}>", bridge_param_type(inner, ci, false, tp)),
669        TypeRef::Primitive(p) => prim(p).into(),
670        TypeRef::Unit => "()".into(),
671        TypeRef::Char => "char".into(),
672        TypeRef::Map(k, v) => format!(
673            "std::collections::HashMap<{}, {}>",
674            bridge_param_type(k, ci, false, tp),
675            bridge_param_type(v, ci, false, tp)
676        ),
677        TypeRef::Json => "serde_json::Value".into(),
678        TypeRef::Duration => "std::time::Duration".into(),
679    }
680}
681
682/// Map a visitor method parameter type to the correct Rust type string, handling
683/// IR quirks:
684/// - `ty=String, optional=true, is_ref=true` → `Option<&str>` (IR collapses `Option<&str>`)
685/// - `ty=Vec<T>, is_ref=true` → `&[T]` (IR collapses `&[T]`)
686/// - Everything else delegates to [`bridge_param_type`].
687pub fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
688    if optional && matches!(ty, TypeRef::String) && is_ref {
689        return "Option<&str>".to_string();
690    }
691    if is_ref {
692        if let TypeRef::Vec(inner) = ty {
693            let inner_str = bridge_param_type(inner, "", false, tp);
694            return format!("&[{inner_str}]");
695        }
696    }
697    bridge_param_type(ty, "", is_ref, tp)
698}
699
700/// Find the first function parameter that matches a trait bridge configuration
701/// (by type alias or parameter name).
702///
703/// Bridges configured with `bind_via = "options_field"` are skipped — they live on a
704/// struct field rather than directly as a parameter, and are returned by
705/// [`find_bridge_field`] instead.
706pub fn find_bridge_param<'a>(
707    func: &FunctionDef,
708    bridges: &'a [TraitBridgeConfig],
709) -> Option<(usize, &'a TraitBridgeConfig)> {
710    for (idx, param) in func.params.iter().enumerate() {
711        let named = match &param.ty {
712            TypeRef::Named(n) => Some(n.as_str()),
713            TypeRef::Optional(inner) => {
714                if let TypeRef::Named(n) = inner.as_ref() {
715                    Some(n.as_str())
716                } else {
717                    None
718                }
719            }
720            _ => None,
721        };
722        for bridge in bridges {
723            if bridge.bind_via != BridgeBinding::FunctionParam {
724                continue;
725            }
726            if let Some(type_name) = named {
727                if bridge.type_alias.as_deref() == Some(type_name) {
728                    return Some((idx, bridge));
729                }
730            }
731            if bridge.param_name.as_deref() == Some(param.name.as_str()) {
732                return Some((idx, bridge));
733            }
734        }
735    }
736    None
737}
738
739/// Match info for a trait bridge whose handle lives as a struct field
740/// (`bind_via = "options_field"`).
741#[derive(Debug, Clone)]
742pub struct BridgeFieldMatch<'a> {
743    /// Index of the function parameter that carries the owning struct.
744    pub param_index: usize,
745    /// Name of the parameter (e.g., `"options"`).
746    pub param_name: String,
747    /// IR type name of the parameter, with any `Option<>` wrapper unwrapped.
748    pub options_type: String,
749    /// True if the param is `Option<TypeName>` rather than `TypeName`.
750    pub param_is_optional: bool,
751    /// Name of the field on `options_type` that holds the bridge handle.
752    pub field_name: String,
753    /// The matching field definition (carries the field's `TypeRef`).
754    pub field: &'a FieldDef,
755    /// The bridge configuration that produced the match.
756    pub bridge: &'a TraitBridgeConfig,
757}
758
759/// Find the first function parameter whose IR type carries a bridge field
760/// (`bind_via = "options_field"`).
761///
762/// For each function parameter whose IR type is `Named(N)` or `Optional<Named(N)>`,
763/// look up `N` in `types`. If `N` matches any bridge's `options_type`, search its
764/// fields for one whose name matches the bridge's resolved options field (or whose
765/// type's `Named` alias matches the bridge's `type_alias`). Returns the first match.
766///
767/// Bridges configured with `bind_via = "function_param"` are skipped — those go
768/// through [`find_bridge_param`] instead.
769pub fn find_bridge_field<'a>(
770    func: &FunctionDef,
771    types: &'a [TypeDef],
772    bridges: &'a [TraitBridgeConfig],
773) -> Option<BridgeFieldMatch<'a>> {
774    fn unwrap_named(ty: &TypeRef) -> Option<(&str, bool)> {
775        match ty {
776            TypeRef::Named(n) => Some((n.as_str(), false)),
777            TypeRef::Optional(inner) => {
778                if let TypeRef::Named(n) = inner.as_ref() {
779                    Some((n.as_str(), true))
780                } else {
781                    None
782                }
783            }
784            _ => None,
785        }
786    }
787
788    for (idx, param) in func.params.iter().enumerate() {
789        let Some((type_name, is_optional)) = unwrap_named(&param.ty) else {
790            continue;
791        };
792        let Some(type_def) = types.iter().find(|t| t.name == type_name) else {
793            continue;
794        };
795        for bridge in bridges {
796            if bridge.bind_via != BridgeBinding::OptionsField {
797                continue;
798            }
799            if bridge.options_type.as_deref() != Some(type_name) {
800                continue;
801            }
802            let field_name = bridge.resolved_options_field();
803            for field in &type_def.fields {
804                let matches_name = field_name.is_some_and(|n| field.name == n);
805                let matches_alias = bridge
806                    .type_alias
807                    .as_deref()
808                    .is_some_and(|alias| field_type_matches_alias(&field.ty, alias));
809                if matches_name || matches_alias {
810                    return Some(BridgeFieldMatch {
811                        param_index: idx,
812                        param_name: param.name.clone(),
813                        options_type: type_name.to_string(),
814                        param_is_optional: is_optional,
815                        field_name: field.name.clone(),
816                        field,
817                        bridge,
818                    });
819                }
820            }
821        }
822    }
823    None
824}
825
826/// True if `field_ty` references a `Named` type whose name equals `alias`,
827/// allowing for `Option<>` and `Vec<>` wrappers.
828fn field_type_matches_alias(field_ty: &TypeRef, alias: &str) -> bool {
829    match field_ty {
830        TypeRef::Named(n) => n == alias,
831        TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_type_matches_alias(inner, alias),
832        _ => false,
833    }
834}
835
836/// Convert a snake_case string to camelCase.
837pub fn to_camel_case(s: &str) -> String {
838    let mut result = String::new();
839    let mut capitalize_next = false;
840    for ch in s.chars() {
841        if ch == '_' {
842            capitalize_next = true;
843        } else if capitalize_next {
844            result.push(ch.to_ascii_uppercase());
845            capitalize_next = false;
846        } else {
847            result.push(ch);
848        }
849    }
850    result
851}
852
853#[cfg(test)]
854mod tests {
855    use super::*;
856    use alef_core::config::TraitBridgeConfig;
857    use alef_core::ir::{MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeDef, TypeRef};
858
859    // ---------------------------------------------------------------------------
860    // Test helpers
861    // ---------------------------------------------------------------------------
862
863    fn make_trait_bridge_config(super_trait: Option<&str>, register_fn: Option<&str>) -> TraitBridgeConfig {
864        TraitBridgeConfig {
865            trait_name: "OcrBackend".to_string(),
866            super_trait: super_trait.map(str::to_string),
867            registry_getter: None,
868            register_fn: register_fn.map(str::to_string),
869            unregister_fn: None,
870            clear_fn: None,
871            type_alias: None,
872            param_name: None,
873            register_extra_args: None,
874            exclude_languages: Vec::new(),
875            bind_via: BridgeBinding::FunctionParam,
876            options_type: None,
877            options_field: None,
878        }
879    }
880
881    fn make_type_def(name: &str, rust_path: &str, methods: Vec<MethodDef>) -> TypeDef {
882        TypeDef {
883            name: name.to_string(),
884            rust_path: rust_path.to_string(),
885            original_rust_path: rust_path.to_string(),
886            fields: vec![],
887            methods,
888            is_opaque: true,
889            is_clone: false,
890            is_copy: false,
891            doc: String::new(),
892            cfg: None,
893            is_trait: true,
894            has_default: false,
895            has_stripped_cfg_fields: false,
896            is_return_type: false,
897            serde_rename_all: None,
898            has_serde: false,
899            super_traits: vec![],
900        }
901    }
902
903    fn make_method(
904        name: &str,
905        params: Vec<ParamDef>,
906        return_type: TypeRef,
907        is_async: bool,
908        has_default_impl: bool,
909        trait_source: Option<&str>,
910        error_type: Option<&str>,
911    ) -> MethodDef {
912        MethodDef {
913            name: name.to_string(),
914            params,
915            return_type,
916            is_async,
917            is_static: false,
918            error_type: error_type.map(str::to_string),
919            doc: String::new(),
920            receiver: Some(ReceiverKind::Ref),
921            sanitized: false,
922            trait_source: trait_source.map(str::to_string),
923            returns_ref: false,
924            returns_cow: false,
925            return_newtype_wrapper: None,
926            has_default_impl,
927        }
928    }
929
930    fn make_func(name: &str, params: Vec<ParamDef>) -> FunctionDef {
931        FunctionDef {
932            name: name.to_string(),
933            rust_path: format!("mylib::{name}"),
934            original_rust_path: String::new(),
935            params,
936            return_type: TypeRef::Unit,
937            is_async: false,
938            error_type: None,
939            doc: String::new(),
940            cfg: None,
941            sanitized: false,
942            return_sanitized: false,
943            returns_ref: false,
944            returns_cow: false,
945            return_newtype_wrapper: None,
946        }
947    }
948
949    fn make_field(name: &str, ty: TypeRef) -> FieldDef {
950        FieldDef {
951            name: name.to_string(),
952            ty,
953            optional: false,
954            default: None,
955            doc: String::new(),
956            sanitized: false,
957            is_boxed: false,
958            type_rust_path: None,
959            cfg: None,
960            typed_default: None,
961            core_wrapper: Default::default(),
962            vec_inner_core_wrapper: Default::default(),
963            newtype_wrapper: None,
964        }
965    }
966
967    fn make_param(name: &str, ty: TypeRef, is_ref: bool) -> ParamDef {
968        ParamDef {
969            name: name.to_string(),
970            ty,
971            optional: false,
972            default: None,
973            sanitized: false,
974            typed_default: None,
975            is_ref,
976            is_mut: false,
977            newtype_wrapper: None,
978            original_type: None,
979        }
980    }
981
982    fn make_spec<'a>(
983        trait_def: &'a TypeDef,
984        bridge_config: &'a TraitBridgeConfig,
985        wrapper_prefix: &'a str,
986        type_paths: HashMap<String, String>,
987    ) -> TraitBridgeSpec<'a> {
988        TraitBridgeSpec {
989            trait_def,
990            bridge_config,
991            core_import: "mylib",
992            wrapper_prefix,
993            type_paths,
994            error_type: "MyError".to_string(),
995            error_constructor: "MyError::from({msg})".to_string(),
996        }
997    }
998
999    // ---------------------------------------------------------------------------
1000    // Mock backend
1001    // ---------------------------------------------------------------------------
1002
1003    struct MockBridgeGenerator;
1004
1005    impl TraitBridgeGenerator for MockBridgeGenerator {
1006        fn foreign_object_type(&self) -> &str {
1007            "Py<PyAny>"
1008        }
1009
1010        fn bridge_imports(&self) -> Vec<String> {
1011            vec!["pyo3::prelude::*".to_string(), "pyo3::types::PyString".to_string()]
1012        }
1013
1014        fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1015            format!("// sync body for {}", method.name)
1016        }
1017
1018        fn gen_async_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
1019            format!("// async body for {}", method.name)
1020        }
1021
1022        fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
1023            format!(
1024                "impl {} {{\n    pub fn new(obj: Py<PyAny>) -> Self {{ Self {{ inner: obj, cached_name: String::new() }} }}\n}}",
1025                spec.wrapper_name()
1026            )
1027        }
1028
1029        fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
1030            let fn_name = spec.bridge_config.register_fn.as_deref().unwrap_or("register");
1031            format!("pub fn {fn_name}(obj: Py<PyAny>) {{ /* register */ }}")
1032        }
1033    }
1034
1035    // ---------------------------------------------------------------------------
1036    // TraitBridgeSpec helpers
1037    // ---------------------------------------------------------------------------
1038
1039    #[test]
1040    fn test_wrapper_name() {
1041        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1042        let config = make_trait_bridge_config(None, None);
1043        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1044        assert_eq!(spec.wrapper_name(), "PyOcrBackendBridge");
1045    }
1046
1047    #[test]
1048    fn test_trait_snake() {
1049        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1050        let config = make_trait_bridge_config(None, None);
1051        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1052        assert_eq!(spec.trait_snake(), "ocr_backend");
1053    }
1054
1055    #[test]
1056    fn test_trait_path_replaces_hyphens() {
1057        let trait_def = make_type_def("OcrBackend", "my-lib::OcrBackend", vec![]);
1058        let config = make_trait_bridge_config(None, None);
1059        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1060        assert_eq!(spec.trait_path(), "my_lib::OcrBackend");
1061    }
1062
1063    #[test]
1064    fn test_required_methods_filters_no_default_impl() {
1065        let methods = vec![
1066            make_method("process", vec![], TypeRef::String, false, false, None, None),
1067            make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1068            make_method("detect", vec![], TypeRef::String, false, false, None, None),
1069        ];
1070        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1071        let config = make_trait_bridge_config(None, None);
1072        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1073        let required = spec.required_methods();
1074        assert_eq!(required.len(), 2);
1075        assert!(required.iter().any(|m| m.name == "process"));
1076        assert!(required.iter().any(|m| m.name == "detect"));
1077    }
1078
1079    #[test]
1080    fn test_optional_methods_filters_has_default_impl() {
1081        let methods = vec![
1082            make_method("process", vec![], TypeRef::String, false, false, None, None),
1083            make_method("initialize", vec![], TypeRef::Unit, false, true, None, None),
1084            make_method("shutdown", vec![], TypeRef::Unit, false, true, None, None),
1085        ];
1086        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1087        let config = make_trait_bridge_config(None, None);
1088        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1089        let optional = spec.optional_methods();
1090        assert_eq!(optional.len(), 2);
1091        assert!(optional.iter().any(|m| m.name == "initialize"));
1092        assert!(optional.iter().any(|m| m.name == "shutdown"));
1093    }
1094
1095    #[test]
1096    fn test_error_path() {
1097        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1098        let config = make_trait_bridge_config(None, None);
1099        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1100        assert_eq!(spec.error_path(), "mylib::MyError");
1101    }
1102
1103    // ---------------------------------------------------------------------------
1104    // format_type_ref
1105    // ---------------------------------------------------------------------------
1106
1107    #[test]
1108    fn test_format_type_ref_primitives() {
1109        let paths = HashMap::new();
1110        let cases: Vec<(TypeRef, &str)> = vec![
1111            (TypeRef::Primitive(PrimitiveType::Bool), "bool"),
1112            (TypeRef::Primitive(PrimitiveType::U8), "u8"),
1113            (TypeRef::Primitive(PrimitiveType::U16), "u16"),
1114            (TypeRef::Primitive(PrimitiveType::U32), "u32"),
1115            (TypeRef::Primitive(PrimitiveType::U64), "u64"),
1116            (TypeRef::Primitive(PrimitiveType::I8), "i8"),
1117            (TypeRef::Primitive(PrimitiveType::I16), "i16"),
1118            (TypeRef::Primitive(PrimitiveType::I32), "i32"),
1119            (TypeRef::Primitive(PrimitiveType::I64), "i64"),
1120            (TypeRef::Primitive(PrimitiveType::F32), "f32"),
1121            (TypeRef::Primitive(PrimitiveType::F64), "f64"),
1122            (TypeRef::Primitive(PrimitiveType::Usize), "usize"),
1123            (TypeRef::Primitive(PrimitiveType::Isize), "isize"),
1124        ];
1125        for (ty, expected) in cases {
1126            assert_eq!(format_type_ref(&ty, &paths), expected, "mismatch for {expected}");
1127        }
1128    }
1129
1130    #[test]
1131    fn test_format_type_ref_string() {
1132        assert_eq!(format_type_ref(&TypeRef::String, &HashMap::new()), "String");
1133    }
1134
1135    #[test]
1136    fn test_format_type_ref_char() {
1137        assert_eq!(format_type_ref(&TypeRef::Char, &HashMap::new()), "char");
1138    }
1139
1140    #[test]
1141    fn test_format_type_ref_bytes() {
1142        assert_eq!(format_type_ref(&TypeRef::Bytes, &HashMap::new()), "Vec<u8>");
1143    }
1144
1145    #[test]
1146    fn test_format_type_ref_path() {
1147        assert_eq!(format_type_ref(&TypeRef::Path, &HashMap::new()), "std::path::PathBuf");
1148    }
1149
1150    #[test]
1151    fn test_format_type_ref_unit() {
1152        assert_eq!(format_type_ref(&TypeRef::Unit, &HashMap::new()), "()");
1153    }
1154
1155    #[test]
1156    fn test_format_type_ref_json() {
1157        assert_eq!(format_type_ref(&TypeRef::Json, &HashMap::new()), "serde_json::Value");
1158    }
1159
1160    #[test]
1161    fn test_format_type_ref_duration() {
1162        assert_eq!(
1163            format_type_ref(&TypeRef::Duration, &HashMap::new()),
1164            "std::time::Duration"
1165        );
1166    }
1167
1168    #[test]
1169    fn test_format_type_ref_optional() {
1170        let ty = TypeRef::Optional(Box::new(TypeRef::String));
1171        assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<String>");
1172    }
1173
1174    #[test]
1175    fn test_format_type_ref_optional_nested() {
1176        let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::Primitive(
1177            PrimitiveType::U32,
1178        )))));
1179        assert_eq!(format_type_ref(&ty, &HashMap::new()), "Option<Option<u32>>");
1180    }
1181
1182    #[test]
1183    fn test_format_type_ref_vec() {
1184        let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U8)));
1185        assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<u8>");
1186    }
1187
1188    #[test]
1189    fn test_format_type_ref_vec_nested() {
1190        let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
1191        assert_eq!(format_type_ref(&ty, &HashMap::new()), "Vec<Vec<String>>");
1192    }
1193
1194    #[test]
1195    fn test_format_type_ref_map() {
1196        let ty = TypeRef::Map(
1197            Box::new(TypeRef::String),
1198            Box::new(TypeRef::Primitive(PrimitiveType::I64)),
1199        );
1200        assert_eq!(
1201            format_type_ref(&ty, &HashMap::new()),
1202            "std::collections::HashMap<String, i64>"
1203        );
1204    }
1205
1206    #[test]
1207    fn test_format_type_ref_map_nested_value() {
1208        let ty = TypeRef::Map(
1209            Box::new(TypeRef::String),
1210            Box::new(TypeRef::Vec(Box::new(TypeRef::String))),
1211        );
1212        assert_eq!(
1213            format_type_ref(&ty, &HashMap::new()),
1214            "std::collections::HashMap<String, Vec<String>>"
1215        );
1216    }
1217
1218    #[test]
1219    fn test_format_type_ref_named_without_type_paths() {
1220        let ty = TypeRef::Named("Config".to_string());
1221        assert_eq!(format_type_ref(&ty, &HashMap::new()), "Config");
1222    }
1223
1224    #[test]
1225    fn test_format_type_ref_named_with_type_paths() {
1226        let ty = TypeRef::Named("Config".to_string());
1227        let mut paths = HashMap::new();
1228        paths.insert("Config".to_string(), "mylib::Config".to_string());
1229        assert_eq!(format_type_ref(&ty, &paths), "mylib::Config");
1230    }
1231
1232    #[test]
1233    fn test_format_type_ref_named_not_in_type_paths_falls_back_to_name() {
1234        let ty = TypeRef::Named("Unknown".to_string());
1235        let mut paths = HashMap::new();
1236        paths.insert("Other".to_string(), "mylib::Other".to_string());
1237        assert_eq!(format_type_ref(&ty, &paths), "Unknown");
1238    }
1239
1240    // ---------------------------------------------------------------------------
1241    // format_param_type
1242    // ---------------------------------------------------------------------------
1243
1244    #[test]
1245    fn test_format_param_type_string_ref() {
1246        let param = make_param("input", TypeRef::String, true);
1247        assert_eq!(format_param_type(&param, &HashMap::new()), "&str");
1248    }
1249
1250    #[test]
1251    fn test_format_param_type_string_owned() {
1252        let param = make_param("input", TypeRef::String, false);
1253        assert_eq!(format_param_type(&param, &HashMap::new()), "String");
1254    }
1255
1256    #[test]
1257    fn test_format_param_type_bytes_ref() {
1258        let param = make_param("data", TypeRef::Bytes, true);
1259        assert_eq!(format_param_type(&param, &HashMap::new()), "&[u8]");
1260    }
1261
1262    #[test]
1263    fn test_format_param_type_bytes_owned() {
1264        let param = make_param("data", TypeRef::Bytes, false);
1265        assert_eq!(format_param_type(&param, &HashMap::new()), "Vec<u8>");
1266    }
1267
1268    #[test]
1269    fn test_format_param_type_path_ref() {
1270        let param = make_param("path", TypeRef::Path, true);
1271        assert_eq!(format_param_type(&param, &HashMap::new()), "&std::path::Path");
1272    }
1273
1274    #[test]
1275    fn test_format_param_type_path_owned() {
1276        let param = make_param("path", TypeRef::Path, false);
1277        assert_eq!(format_param_type(&param, &HashMap::new()), "std::path::PathBuf");
1278    }
1279
1280    #[test]
1281    fn test_format_param_type_vec_ref() {
1282        let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), true);
1283        assert_eq!(format_param_type(&param, &HashMap::new()), "&[String]");
1284    }
1285
1286    #[test]
1287    fn test_format_param_type_vec_owned() {
1288        let param = make_param("items", TypeRef::Vec(Box::new(TypeRef::String)), false);
1289        assert_eq!(format_param_type(&param, &HashMap::new()), "Vec<String>");
1290    }
1291
1292    #[test]
1293    fn test_format_param_type_named_ref_with_type_paths() {
1294        let mut paths = HashMap::new();
1295        paths.insert("Config".to_string(), "mylib::Config".to_string());
1296        let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1297        assert_eq!(format_param_type(&param, &paths), "&mylib::Config");
1298    }
1299
1300    #[test]
1301    fn test_format_param_type_named_ref_without_type_paths() {
1302        let param = make_param("cfg", TypeRef::Named("Config".to_string()), true);
1303        assert_eq!(format_param_type(&param, &HashMap::new()), "&Config");
1304    }
1305
1306    #[test]
1307    fn test_format_param_type_primitive_ref_passes_by_value() {
1308        // Copy types like u32 are passed by value even when is_ref is set
1309        let param = make_param("count", TypeRef::Primitive(PrimitiveType::U32), true);
1310        assert_eq!(format_param_type(&param, &HashMap::new()), "u32");
1311    }
1312
1313    #[test]
1314    fn test_format_param_type_unit_ref_passes_by_value() {
1315        let param = make_param("nothing", TypeRef::Unit, true);
1316        assert_eq!(format_param_type(&param, &HashMap::new()), "()");
1317    }
1318
1319    // ---------------------------------------------------------------------------
1320    // format_return_type
1321    // ---------------------------------------------------------------------------
1322
1323    #[test]
1324    fn test_format_return_type_without_error() {
1325        let result = format_return_type(&TypeRef::String, None, &HashMap::new());
1326        assert_eq!(result, "String");
1327    }
1328
1329    #[test]
1330    fn test_format_return_type_with_error() {
1331        let result = format_return_type(&TypeRef::String, Some("MyError"), &HashMap::new());
1332        assert_eq!(result, "std::result::Result<String, MyError>");
1333    }
1334
1335    #[test]
1336    fn test_format_return_type_unit_with_error() {
1337        let result = format_return_type(&TypeRef::Unit, Some("Box<dyn std::error::Error>"), &HashMap::new());
1338        assert_eq!(result, "std::result::Result<(), Box<dyn std::error::Error>>");
1339    }
1340
1341    #[test]
1342    fn test_format_return_type_named_with_type_paths_and_error() {
1343        let mut paths = HashMap::new();
1344        paths.insert("Output".to_string(), "mylib::Output".to_string());
1345        let result = format_return_type(&TypeRef::Named("Output".to_string()), Some("mylib::MyError"), &paths);
1346        assert_eq!(result, "std::result::Result<mylib::Output, mylib::MyError>");
1347    }
1348
1349    // ---------------------------------------------------------------------------
1350    // gen_bridge_wrapper_struct
1351    // ---------------------------------------------------------------------------
1352
1353    #[test]
1354    fn test_gen_bridge_wrapper_struct_contains_struct_name() {
1355        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1356        let config = make_trait_bridge_config(None, None);
1357        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1358        let generator = MockBridgeGenerator;
1359        let result = gen_bridge_wrapper_struct(&spec, &generator);
1360        assert!(
1361            result.contains("pub struct PyOcrBackendBridge"),
1362            "missing struct declaration in:\n{result}"
1363        );
1364    }
1365
1366    #[test]
1367    fn test_gen_bridge_wrapper_struct_contains_inner_field() {
1368        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1369        let config = make_trait_bridge_config(None, None);
1370        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1371        let generator = MockBridgeGenerator;
1372        let result = gen_bridge_wrapper_struct(&spec, &generator);
1373        assert!(result.contains("inner: Py<PyAny>"), "missing inner field in:\n{result}");
1374    }
1375
1376    #[test]
1377    fn test_gen_bridge_wrapper_struct_contains_cached_name() {
1378        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1379        let config = make_trait_bridge_config(None, None);
1380        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1381        let generator = MockBridgeGenerator;
1382        let result = gen_bridge_wrapper_struct(&spec, &generator);
1383        assert!(
1384            result.contains("cached_name: String"),
1385            "missing cached_name field in:\n{result}"
1386        );
1387    }
1388
1389    // ---------------------------------------------------------------------------
1390    // gen_bridge_plugin_impl
1391    // ---------------------------------------------------------------------------
1392
1393    #[test]
1394    fn test_gen_bridge_plugin_impl_returns_none_when_no_super_trait() {
1395        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1396        let config = make_trait_bridge_config(None, None);
1397        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1398        let generator = MockBridgeGenerator;
1399        assert!(gen_bridge_plugin_impl(&spec, &generator).is_none());
1400    }
1401
1402    #[test]
1403    fn test_gen_bridge_plugin_impl_returns_some_when_super_trait_configured() {
1404        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1405        let config = make_trait_bridge_config(Some("Plugin"), None);
1406        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1407        let generator = MockBridgeGenerator;
1408        assert!(gen_bridge_plugin_impl(&spec, &generator).is_some());
1409    }
1410
1411    #[test]
1412    fn test_gen_bridge_plugin_impl_uses_qualified_super_trait_path() {
1413        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1414        let config = make_trait_bridge_config(Some("Plugin"), None);
1415        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1416        let generator = MockBridgeGenerator;
1417        let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1418        assert!(
1419            result.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1420            "missing qualified super-trait path in:\n{result}"
1421        );
1422    }
1423
1424    #[test]
1425    fn test_gen_bridge_plugin_impl_uses_already_qualified_super_trait_path() {
1426        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1427        let config = make_trait_bridge_config(Some("other_crate::Plugin"), None);
1428        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1429        let generator = MockBridgeGenerator;
1430        let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1431        assert!(
1432            result.contains("impl other_crate::Plugin for PyOcrBackendBridge"),
1433            "wrong super-trait path in:\n{result}"
1434        );
1435    }
1436
1437    #[test]
1438    fn test_gen_bridge_plugin_impl_contains_name_fn() {
1439        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1440        let config = make_trait_bridge_config(Some("Plugin"), None);
1441        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1442        let generator = MockBridgeGenerator;
1443        let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1444        assert!(
1445            result.contains("fn name(") && result.contains("cached_name"),
1446            "missing name() using cached_name in:\n{result}"
1447        );
1448    }
1449
1450    #[test]
1451    fn test_gen_bridge_plugin_impl_contains_version_fn() {
1452        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1453        let config = make_trait_bridge_config(Some("Plugin"), None);
1454        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1455        let generator = MockBridgeGenerator;
1456        let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1457        assert!(result.contains("fn version("), "missing version() in:\n{result}");
1458    }
1459
1460    #[test]
1461    fn test_gen_bridge_plugin_impl_contains_initialize_fn() {
1462        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1463        let config = make_trait_bridge_config(Some("Plugin"), None);
1464        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1465        let generator = MockBridgeGenerator;
1466        let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1467        assert!(result.contains("fn initialize("), "missing initialize() in:\n{result}");
1468    }
1469
1470    #[test]
1471    fn test_gen_bridge_plugin_impl_contains_shutdown_fn() {
1472        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1473        let config = make_trait_bridge_config(Some("Plugin"), None);
1474        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1475        let generator = MockBridgeGenerator;
1476        let result = gen_bridge_plugin_impl(&spec, &generator).unwrap();
1477        assert!(result.contains("fn shutdown("), "missing shutdown() in:\n{result}");
1478    }
1479
1480    // ---------------------------------------------------------------------------
1481    // gen_bridge_trait_impl
1482    // ---------------------------------------------------------------------------
1483
1484    #[test]
1485    fn test_gen_bridge_trait_impl_includes_impl_header() {
1486        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1487        let config = make_trait_bridge_config(None, None);
1488        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1489        let generator = MockBridgeGenerator;
1490        let result = gen_bridge_trait_impl(&spec, &generator);
1491        assert!(
1492            result.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1493            "missing impl header in:\n{result}"
1494        );
1495    }
1496
1497    #[test]
1498    fn test_gen_bridge_trait_impl_includes_method_signatures() {
1499        let methods = vec![make_method(
1500            "process",
1501            vec![],
1502            TypeRef::String,
1503            false,
1504            false,
1505            None,
1506            None,
1507        )];
1508        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1509        let config = make_trait_bridge_config(None, None);
1510        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1511        let generator = MockBridgeGenerator;
1512        let result = gen_bridge_trait_impl(&spec, &generator);
1513        assert!(result.contains("fn process("), "missing method signature in:\n{result}");
1514    }
1515
1516    #[test]
1517    fn test_gen_bridge_trait_impl_includes_method_body_from_generator() {
1518        let methods = vec![make_method(
1519            "process",
1520            vec![],
1521            TypeRef::String,
1522            false,
1523            false,
1524            None,
1525            None,
1526        )];
1527        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1528        let config = make_trait_bridge_config(None, None);
1529        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1530        let generator = MockBridgeGenerator;
1531        let result = gen_bridge_trait_impl(&spec, &generator);
1532        assert!(
1533            result.contains("// sync body for process"),
1534            "missing sync method body in:\n{result}"
1535        );
1536    }
1537
1538    #[test]
1539    fn test_gen_bridge_trait_impl_async_method_uses_async_body() {
1540        let methods = vec![make_method(
1541            "process_async",
1542            vec![],
1543            TypeRef::String,
1544            true,
1545            false,
1546            None,
1547            None,
1548        )];
1549        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1550        let config = make_trait_bridge_config(None, None);
1551        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1552        let generator = MockBridgeGenerator;
1553        let result = gen_bridge_trait_impl(&spec, &generator);
1554        assert!(
1555            result.contains("// async body for process_async"),
1556            "missing async method body in:\n{result}"
1557        );
1558        assert!(
1559            result.contains("async fn process_async("),
1560            "missing async keyword in method signature in:\n{result}"
1561        );
1562    }
1563
1564    #[test]
1565    fn test_gen_bridge_trait_impl_filters_trait_source_methods() {
1566        // Methods with trait_source set come from super-traits and should be excluded
1567        let methods = vec![
1568            make_method("own_method", vec![], TypeRef::String, false, false, None, None),
1569            make_method(
1570                "inherited_method",
1571                vec![],
1572                TypeRef::String,
1573                false,
1574                false,
1575                Some("other_crate::OtherTrait"),
1576                None,
1577            ),
1578        ];
1579        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1580        let config = make_trait_bridge_config(None, None);
1581        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1582        let generator = MockBridgeGenerator;
1583        let result = gen_bridge_trait_impl(&spec, &generator);
1584        assert!(
1585            result.contains("fn own_method("),
1586            "own method should be present in:\n{result}"
1587        );
1588        assert!(
1589            !result.contains("fn inherited_method("),
1590            "inherited method should be filtered out in:\n{result}"
1591        );
1592    }
1593
1594    #[test]
1595    fn test_gen_bridge_trait_impl_method_with_params() {
1596        let params = vec![
1597            make_param("input", TypeRef::String, true),
1598            make_param("count", TypeRef::Primitive(PrimitiveType::U32), false),
1599        ];
1600        let methods = vec![make_method(
1601            "process",
1602            params,
1603            TypeRef::String,
1604            false,
1605            false,
1606            None,
1607            None,
1608        )];
1609        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1610        let config = make_trait_bridge_config(None, None);
1611        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1612        let generator = MockBridgeGenerator;
1613        let result = gen_bridge_trait_impl(&spec, &generator);
1614        assert!(result.contains("input: &str"), "missing &str param in:\n{result}");
1615        assert!(result.contains("count: u32"), "missing u32 param in:\n{result}");
1616    }
1617
1618    #[test]
1619    fn test_gen_bridge_trait_impl_return_type_with_error() {
1620        let methods = vec![make_method(
1621            "process",
1622            vec![],
1623            TypeRef::String,
1624            false,
1625            false,
1626            None,
1627            Some("MyError"),
1628        )];
1629        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1630        let config = make_trait_bridge_config(None, None);
1631        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1632        let generator = MockBridgeGenerator;
1633        let result = gen_bridge_trait_impl(&spec, &generator);
1634        assert!(
1635            result.contains("-> std::result::Result<String, mylib::MyError>"),
1636            "missing std::result::Result return type in:\n{result}"
1637        );
1638    }
1639
1640    // ---------------------------------------------------------------------------
1641    // gen_bridge_registration_fn
1642    // ---------------------------------------------------------------------------
1643
1644    #[test]
1645    fn test_gen_bridge_registration_fn_returns_none_without_register_fn() {
1646        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1647        let config = make_trait_bridge_config(None, None);
1648        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1649        let generator = MockBridgeGenerator;
1650        assert!(gen_bridge_registration_fn(&spec, &generator).is_none());
1651    }
1652
1653    #[test]
1654    fn test_gen_bridge_registration_fn_returns_some_with_register_fn() {
1655        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1656        let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1657        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1658        let generator = MockBridgeGenerator;
1659        let result = gen_bridge_registration_fn(&spec, &generator);
1660        assert!(result.is_some());
1661        let code = result.unwrap();
1662        assert!(
1663            code.contains("register_ocr_backend"),
1664            "missing register fn name in:\n{code}"
1665        );
1666    }
1667
1668    // ---------------------------------------------------------------------------
1669    // gen_bridge_all
1670    // ---------------------------------------------------------------------------
1671
1672    #[test]
1673    fn test_gen_bridge_all_includes_imports() {
1674        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1675        let config = make_trait_bridge_config(None, None);
1676        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1677        let generator = MockBridgeGenerator;
1678        let output = gen_bridge_all(&spec, &generator);
1679        assert!(output.imports.contains(&"pyo3::prelude::*".to_string()));
1680        assert!(output.imports.contains(&"pyo3::types::PyString".to_string()));
1681    }
1682
1683    #[test]
1684    fn test_gen_bridge_all_includes_wrapper_struct() {
1685        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1686        let config = make_trait_bridge_config(None, None);
1687        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1688        let generator = MockBridgeGenerator;
1689        let output = gen_bridge_all(&spec, &generator);
1690        assert!(
1691            output.code.contains("pub struct PyOcrBackendBridge"),
1692            "missing struct in:\n{}",
1693            output.code
1694        );
1695    }
1696
1697    #[test]
1698    fn test_gen_bridge_all_includes_constructor() {
1699        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1700        let config = make_trait_bridge_config(None, None);
1701        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1702        let generator = MockBridgeGenerator;
1703        let output = gen_bridge_all(&spec, &generator);
1704        assert!(
1705            output.code.contains("pub fn new("),
1706            "missing constructor in:\n{}",
1707            output.code
1708        );
1709    }
1710
1711    #[test]
1712    fn test_gen_bridge_all_includes_trait_impl() {
1713        let methods = vec![make_method(
1714            "process",
1715            vec![],
1716            TypeRef::String,
1717            false,
1718            false,
1719            None,
1720            None,
1721        )];
1722        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", methods);
1723        let config = make_trait_bridge_config(None, None);
1724        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1725        let generator = MockBridgeGenerator;
1726        let output = gen_bridge_all(&spec, &generator);
1727        assert!(
1728            output.code.contains("impl mylib::OcrBackend for PyOcrBackendBridge"),
1729            "missing trait impl in:\n{}",
1730            output.code
1731        );
1732    }
1733
1734    #[test]
1735    fn test_gen_bridge_all_includes_plugin_impl_when_super_trait_set() {
1736        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1737        let config = make_trait_bridge_config(Some("Plugin"), None);
1738        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1739        let generator = MockBridgeGenerator;
1740        let output = gen_bridge_all(&spec, &generator);
1741        assert!(
1742            output.code.contains("impl mylib::Plugin for PyOcrBackendBridge"),
1743            "missing plugin impl in:\n{}",
1744            output.code
1745        );
1746    }
1747
1748    #[test]
1749    fn test_gen_bridge_all_no_plugin_impl_when_no_super_trait() {
1750        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1751        let config = make_trait_bridge_config(None, None);
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("fn name(") || !output.code.contains("cached_name"),
1757            "unexpected plugin impl present without super_trait"
1758        );
1759    }
1760
1761    #[test]
1762    fn test_gen_bridge_all_includes_registration_fn_when_configured() {
1763        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1764        let config = make_trait_bridge_config(None, Some("register_ocr_backend"));
1765        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1766        let generator = MockBridgeGenerator;
1767        let output = gen_bridge_all(&spec, &generator);
1768        assert!(
1769            output.code.contains("register_ocr_backend"),
1770            "missing registration fn in:\n{}",
1771            output.code
1772        );
1773    }
1774
1775    #[test]
1776    fn test_gen_bridge_all_no_registration_fn_when_absent() {
1777        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1778        let config = make_trait_bridge_config(None, None);
1779        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1780        let generator = MockBridgeGenerator;
1781        let output = gen_bridge_all(&spec, &generator);
1782        assert!(
1783            !output.code.contains("register_ocr_backend"),
1784            "unexpected registration fn present:\n{}",
1785            output.code
1786        );
1787    }
1788
1789    #[test]
1790    fn test_gen_bridge_all_ordering_struct_before_trait_impl() {
1791        let trait_def = make_type_def("OcrBackend", "mylib::OcrBackend", vec![]);
1792        let config = make_trait_bridge_config(None, None);
1793        let spec = make_spec(&trait_def, &config, "Py", HashMap::new());
1794        let generator = MockBridgeGenerator;
1795        let output = gen_bridge_all(&spec, &generator);
1796        let struct_pos = output.code.find("pub struct PyOcrBackendBridge").unwrap();
1797        let impl_pos = output
1798            .code
1799            .find("impl mylib::OcrBackend for PyOcrBackendBridge")
1800            .unwrap();
1801        assert!(struct_pos < impl_pos, "struct should appear before trait impl");
1802    }
1803
1804    // ---------------------------------------------------------------------------
1805    // find_bridge_param / find_bridge_field
1806    // ---------------------------------------------------------------------------
1807
1808    fn make_bridge(
1809        type_alias: Option<&str>,
1810        param_name: Option<&str>,
1811        bind_via: BridgeBinding,
1812        options_type: Option<&str>,
1813        options_field: Option<&str>,
1814    ) -> TraitBridgeConfig {
1815        TraitBridgeConfig {
1816            trait_name: "HtmlVisitor".to_string(),
1817            super_trait: None,
1818            registry_getter: None,
1819            register_fn: None,
1820            unregister_fn: None,
1821            clear_fn: None,
1822            type_alias: type_alias.map(str::to_string),
1823            param_name: param_name.map(str::to_string),
1824            register_extra_args: None,
1825            exclude_languages: vec![],
1826            bind_via,
1827            options_type: options_type.map(str::to_string),
1828            options_field: options_field.map(str::to_string),
1829        }
1830    }
1831
1832    #[test]
1833    fn find_bridge_param_returns_first_param_match_in_function_param_mode() {
1834        let func = make_func(
1835            "convert",
1836            vec![
1837                make_param("html", TypeRef::String, true),
1838                make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1839            ],
1840        );
1841        let bridges = vec![make_bridge(
1842            Some("VisitorHandle"),
1843            Some("visitor"),
1844            BridgeBinding::FunctionParam,
1845            None,
1846            None,
1847        )];
1848        let result = find_bridge_param(&func, &bridges).expect("bridge match");
1849        assert_eq!(result.0, 1);
1850    }
1851
1852    #[test]
1853    fn find_bridge_param_skips_options_field_bridges() {
1854        let func = make_func(
1855            "convert",
1856            vec![
1857                make_param("html", TypeRef::String, true),
1858                make_param("visitor", TypeRef::Named("VisitorHandle".to_string()), false),
1859            ],
1860        );
1861        let bridges = vec![make_bridge(
1862            Some("VisitorHandle"),
1863            Some("visitor"),
1864            BridgeBinding::OptionsField,
1865            Some("ConversionOptions"),
1866            Some("visitor"),
1867        )];
1868        assert!(
1869            find_bridge_param(&func, &bridges).is_none(),
1870            "bridges configured with bind_via=options_field must not be returned by find_bridge_param"
1871        );
1872    }
1873
1874    #[test]
1875    fn find_bridge_field_detects_field_via_alias() {
1876        let opts_type = TypeDef {
1877            name: "ConversionOptions".to_string(),
1878            rust_path: "mylib::ConversionOptions".to_string(),
1879            original_rust_path: String::new(),
1880            fields: vec![
1881                make_field("debug", TypeRef::Primitive(PrimitiveType::Bool)),
1882                make_field(
1883                    "visitor",
1884                    TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
1885                ),
1886            ],
1887            methods: vec![],
1888            is_opaque: false,
1889            is_clone: true,
1890            is_copy: false,
1891            doc: String::new(),
1892            cfg: None,
1893            is_trait: false,
1894            has_default: true,
1895            has_stripped_cfg_fields: false,
1896            is_return_type: false,
1897            serde_rename_all: None,
1898            has_serde: false,
1899            super_traits: vec![],
1900        };
1901        let func = make_func(
1902            "convert",
1903            vec![
1904                make_param("html", TypeRef::String, true),
1905                make_param(
1906                    "options",
1907                    TypeRef::Optional(Box::new(TypeRef::Named("ConversionOptions".to_string()))),
1908                    false,
1909                ),
1910            ],
1911        );
1912        let bridges = vec![make_bridge(
1913            Some("VisitorHandle"),
1914            Some("visitor"),
1915            BridgeBinding::OptionsField,
1916            Some("ConversionOptions"),
1917            None,
1918        )];
1919        let m = find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).expect("bridge field match");
1920        assert_eq!(m.param_index, 1);
1921        assert_eq!(m.param_name, "options");
1922        assert_eq!(m.options_type, "ConversionOptions");
1923        assert!(m.param_is_optional);
1924        assert_eq!(m.field_name, "visitor");
1925    }
1926
1927    #[test]
1928    fn find_bridge_field_returns_none_for_function_param_bridge() {
1929        let opts_type = TypeDef {
1930            name: "ConversionOptions".to_string(),
1931            rust_path: "mylib::ConversionOptions".to_string(),
1932            original_rust_path: String::new(),
1933            fields: vec![make_field(
1934                "visitor",
1935                TypeRef::Optional(Box::new(TypeRef::Named("VisitorHandle".to_string()))),
1936            )],
1937            methods: vec![],
1938            is_opaque: false,
1939            is_clone: true,
1940            is_copy: false,
1941            doc: String::new(),
1942            cfg: None,
1943            is_trait: false,
1944            has_default: true,
1945            has_stripped_cfg_fields: false,
1946            is_return_type: false,
1947            serde_rename_all: None,
1948            has_serde: false,
1949            super_traits: vec![],
1950        };
1951        let func = make_func(
1952            "convert",
1953            vec![make_param(
1954                "options",
1955                TypeRef::Named("ConversionOptions".to_string()),
1956                false,
1957            )],
1958        );
1959        let bridges = vec![make_bridge(
1960            Some("VisitorHandle"),
1961            Some("visitor"),
1962            BridgeBinding::FunctionParam,
1963            None,
1964            None,
1965        )];
1966        assert!(find_bridge_field(&func, std::slice::from_ref(&opts_type), &bridges).is_none());
1967    }
1968}