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