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