use crate::codegen::generators::trait_bridge::{TraitBridgeGenerator as _, TraitBridgeSpec};
use crate::core::config::{BridgeBinding, TraitBridgeConfig};
use crate::core::ir::{ApiSurface, MethodDef, ParamDef, TypeDef, TypeRef};
use heck::ToSnakeCase;
use crate::backends::swift::gen_rust_crate::trait_bridge::SwiftBridgeGenerator;
use crate::backends::swift::gen_rust_crate::type_bridge::{needs_json_bridge, swift_bridge_rust_type};
fn inbound_bridge_type(ty: &TypeRef) -> String {
if needs_inbound_json_bridge(ty) {
return "String".to_string();
}
match ty {
TypeRef::Vec(inner) => format!("Vec<{}>", inbound_bridge_type(inner)),
_ => swift_bridge_rust_type(ty),
}
}
fn needs_inbound_json_bridge(ty: &TypeRef) -> bool {
if needs_json_bridge(ty) {
return true;
}
matches!(ty, TypeRef::Named(_))
}
pub(crate) fn emit_extern_block_for_inbound_registration(
trait_def: &TypeDef,
bridge_config: &TraitBridgeConfig,
) -> String {
let trait_name = &trait_def.name;
let box_name = format!("Swift{trait_name}Box");
let mut block = String::new();
let mut has_any = false;
block.push_str(" extern \"Rust\" {\n");
if let Some(register_fn) = bridge_config.register_fn.as_deref() {
let camel = heck::AsLowerCamelCase(register_fn).to_string();
block.push_str(&crate::backends::swift::template_env::render(
"inbound_registration_fn.rs.jinja",
minijinja::context! {
camel => &camel,
fn_name => register_fn,
params => format!("swift_box: {box_name}"),
},
));
has_any = true;
}
if let Some(unregister_fn) = bridge_config.unregister_fn.as_deref() {
let camel = heck::AsLowerCamelCase(unregister_fn).to_string();
block.push_str(&crate::backends::swift::template_env::render(
"inbound_registration_fn.rs.jinja",
minijinja::context! {
camel => &camel,
fn_name => unregister_fn,
params => "name: String",
},
));
has_any = true;
}
if let Some(clear_fn) = bridge_config.clear_fn.as_deref() {
let camel = heck::AsLowerCamelCase(clear_fn).to_string();
block.push_str(&crate::backends::swift::template_env::render(
"inbound_registration_fn.rs.jinja",
minijinja::context! {
camel => &camel,
fn_name => clear_fn,
params => "",
},
));
has_any = true;
}
block.push_str(" }\n\n");
if has_any { block } else { String::new() }
}
fn has_plugin_super(bridge_config: &TraitBridgeConfig) -> bool {
bridge_config
.super_trait
.as_deref()
.map(|s| s == "Plugin" || s.ends_with("::Plugin"))
.unwrap_or(false)
}
pub(crate) fn emit_extern_block_for_inbound(trait_def: &TypeDef, bridge_config: &TraitBridgeConfig) -> String {
let trait_name = &trait_def.name;
let box_name = format!("Swift{trait_name}Box");
let _trait_snake = heck::AsSnakeCase(trait_name.as_str()).to_string();
let emit_plugin_shims = has_plugin_super(bridge_config);
let mut block = String::new();
block.push_str(" extern \"Swift\" {\n");
block.push_str(&crate::backends::swift::template_env::render(
"inbound_swift_type.rs.jinja",
minijinja::context! {
box_name => &box_name,
},
));
if emit_plugin_shims {
block.push_str(" fn alef_name(&self) -> String;\n");
block.push_str(" fn alef_version(&self) -> String;\n");
block.push_str(" fn alef_initialize(&self) -> String;\n");
block.push_str(" fn alef_shutdown(&self) -> String;\n");
}
for method in &trait_def.methods {
let method_snake = method.name.to_snake_case();
let mut params = vec!["&self".to_string()];
for p in &method.params {
let bridge_ty = if p.optional {
format!("Option<{}>", inbound_bridge_type(&p.ty))
} else {
inbound_bridge_type(&p.ty)
};
let name = p.name.to_snake_case();
params.push(format!("{name}: {bridge_ty}"));
}
let return_ty = inbound_return_type(method);
let params_str = params.join(", ");
block.push_str(&crate::backends::swift::template_env::render(
"inbound_swift_method.rs.jinja",
minijinja::context! {
method_snake => &method_snake,
params => ¶ms_str,
return_ty => &return_ty,
},
));
let _ = box_name; }
block.push_str(" }\n\n");
block
}
pub(crate) fn emit_inbound_wrapper(
trait_def: &TypeDef,
bridge_config: &TraitBridgeConfig,
api: &ApiSurface,
source_crate: &str,
type_paths: &std::collections::HashMap<String, String>,
error_type: &str,
error_constructor: &str,
) -> String {
let trait_name = &trait_def.name;
let trait_snake = heck::AsSnakeCase(trait_name.as_str()).to_string();
let box_name = format!("Swift{trait_name}Box");
let wrapper_name = format!("Swift{trait_name}Wrapper");
let trait_path = if trait_def.rust_path.is_empty() {
format!("{source_crate}::{trait_name}")
} else {
trait_def.rust_path.replace('-', "_")
};
let plugin_path = api
.types
.iter()
.find(|t| t.is_trait && (t.name == "Plugin" || t.name.ends_with("::Plugin")))
.map(|t| t.rust_path.replace('-', "_"))
.unwrap_or_else(|| format!("{source_crate}::plugins::Plugin"));
let emit_plugin = has_plugin_super(bridge_config);
let mut out = String::new();
if emit_plugin {
out.push_str(&crate::backends::swift::template_env::render(
"inbound_wrapper_struct.rs.jinja",
minijinja::context! {
trait_name => trait_name,
wrapper_name => &wrapper_name,
box_name => &box_name,
},
));
} else {
out.push_str(&format!(
"/// Rust-side wrapper around a Swift class implementing the `{trait_name}` protocol.\n\
///\n\
/// The Swift instance is held via a `swift-bridge` opaque handle that retains\n\
/// the underlying ARC reference for the lifetime of this struct. Send + Sync are\n\
/// asserted unsafely: Swift classes used as trait bridges must be thread-safe.\n\
pub struct {wrapper_name} {{\n\
\x20 inner: ffi::{box_name},\n\
}}\n\
unsafe impl Send for {wrapper_name} {{}}\n\
unsafe impl Sync for {wrapper_name} {{}}\n\
\n\
impl {wrapper_name} {{\n\
\x20 /// Construct a new wrapper from a Swift `{box_name}` handle.\n\
\x20 pub fn new(inner: ffi::{box_name}) -> Self {{\n\
\x20 Self {{ inner }}\n\
\x20 }}\n\
}}\n"
));
if trait_def
.super_traits
.iter()
.any(|s| s == "Debug" || s.ends_with("::Debug"))
{
out.push_str(&format!(
"impl ::std::fmt::Debug for {wrapper_name} {{\n\
\x20 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {{\n\
\x20 f.debug_struct(\"{wrapper_name}\").finish_non_exhaustive()\n\
\x20 }}\n\
}}\n"
));
}
}
if emit_plugin {
out.push_str(&crate::backends::swift::template_env::render(
"inbound_plugin_impl.rs.jinja",
minijinja::context! {
plugin_path => &plugin_path,
wrapper_name => &wrapper_name,
result_type => result_type(source_crate, error_type, "()"),
},
));
}
let _ = trait_snake;
let has_async = trait_def.methods.iter().any(|m| m.is_async);
out.push_str(&crate::backends::swift::template_env::render(
"inbound_trait_impl_open.rs.jinja",
minijinja::context! {
has_async => has_async,
trait_path => &trait_path,
wrapper_name => &wrapper_name,
},
));
for method in &trait_def.methods {
emit_inbound_method_impl(
&mut out,
method,
&trait_snake,
source_crate,
type_paths,
error_type,
emit_plugin,
);
}
out.push_str("}\n\n");
if let Some(register_fn) = bridge_config.register_fn.as_deref() {
if let Some(registry_getter) = bridge_config.registry_getter.as_deref() {
let extra_args = bridge_config
.register_extra_args
.as_deref()
.map(|a| format!(", {a}"))
.unwrap_or_default();
out.push_str(&crate::backends::swift::template_env::render(
"inbound_register_fn.rs.jinja",
minijinja::context! {
trait_name => trait_name,
register_fn => register_fn,
box_name => &box_name,
trait_path => &trait_path,
wrapper_name => &wrapper_name,
registry_getter => registry_getter,
extra_args => &extra_args,
},
));
}
}
let spec = build_bridge_spec(
bridge_config,
trait_def,
source_crate,
type_paths,
error_type,
error_constructor,
);
let generator = SwiftBridgeGenerator;
let unregister_code = generator.gen_unregistration_fn(&spec);
if !unregister_code.is_empty() {
out.push_str(&unregister_code);
out.push('\n');
}
let clear_code = generator.gen_clear_fn(&spec);
if !clear_code.is_empty() {
out.push_str(&clear_code);
out.push('\n');
}
out
}
fn build_bridge_spec<'a>(
bridge_config: &'a TraitBridgeConfig,
trait_def: &'a TypeDef,
source_crate: &'a str,
type_paths: &std::collections::HashMap<String, String>,
error_type: &str,
error_constructor: &str,
) -> TraitBridgeSpec<'a> {
TraitBridgeSpec {
trait_def,
bridge_config,
core_import: source_crate,
wrapper_prefix: "Swift",
type_paths: type_paths.clone(),
error_type: error_type.to_string(),
error_constructor: error_constructor.to_string(),
}
}
pub(crate) fn emit_plugin_error_helper(source_crate: &str, error_type: &str, error_constructor: &str) -> String {
let error_type_path = error_type_path(source_crate, error_type);
let plugin_error_constructor = error_constructor.replace("{msg}", "message");
crate::backends::swift::template_env::render(
"plugin_error_helper.rs.jinja",
minijinja::context! {
error_type_path => &error_type_path,
plugin_error_constructor => &plugin_error_constructor,
},
)
}
fn emit_inbound_method_impl(
out: &mut String,
method: &MethodDef,
trait_snake: &str,
source_crate: &str,
type_paths: &std::collections::HashMap<String, String>,
error_type: &str,
emit_plugin: bool,
) {
if emit_plugin && method.has_default_impl {
return;
}
let method_snake = method.name.to_snake_case();
let receiver_token = match &method.receiver {
Some(crate::core::ir::ReceiverKind::RefMut) => "&mut self",
Some(crate::core::ir::ReceiverKind::Owned) => "self",
_ => "&self",
};
let mut sig_params = vec![receiver_token.to_string()];
for p in &method.params {
let mut prefix = String::new();
if p.is_ref {
prefix.push('&');
}
if p.is_mut {
prefix.push_str("mut ");
}
let inner_ty = if p.is_ref {
match &p.ty {
TypeRef::Vec(inner) => {
let elem = inbound_native_ty_owned(inner, source_crate, type_paths);
format!("[{elem}]")
}
other => inbound_native_ty(other, source_crate, type_paths),
}
} else {
inbound_native_ty(&p.ty, source_crate, type_paths)
};
let full_ty = if p.optional {
format!("Option<{prefix}{inner_ty}>")
} else {
format!("{prefix}{inner_ty}")
};
sig_params.push(format!("{}: {full_ty}", p.name.to_snake_case()));
}
let return_ty = inbound_impl_return_type(method, source_crate, type_paths, error_type);
let async_kw = if method.is_async { "async " } else { "" };
let params = sig_params.join(", ");
out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_open.rs.jinja",
minijinja::context! {
async_kw => async_kw,
method_snake => &method_snake,
params => ¶ms,
return_ty => &return_ty,
},
));
for p in &method.params {
if let Some(line) = inbound_param_to_bridge(p) {
out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_binding.rs.jinja",
minijinja::context! {
line => &line,
},
));
}
}
let call_args: Vec<String> = method.params.iter().map(inbound_local_name).collect();
let call_expr = format!("self.inner.alef_{method_snake}({})", call_args.join(", "));
let is_mime_types_pattern = method.returns_ref
&& matches!(&method.return_type, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String));
if method.error_type.is_some() {
if matches!(method.return_type, TypeRef::Unit) {
out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_result_unit.rs.jinja",
minijinja::context! {
call_expr => &call_expr,
},
));
} else {
let native_ty = inbound_native_return_ty(&method.return_type, source_crate, type_paths);
out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_result_value.rs.jinja",
minijinja::context! {
call_expr => &call_expr,
native_ty => &native_ty,
},
));
}
} else if is_mime_types_pattern {
out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_mime_types.rs.jinja",
minijinja::context! {
call_expr => &call_expr,
},
));
} else if needs_inbound_json_bridge(&method.return_type) {
let native_ty = inbound_native_return_ty(&method.return_type, source_crate, type_paths);
out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_json_return.rs.jinja",
minijinja::context! {
call_expr => &call_expr,
native_ty => &native_ty,
trait_snake => trait_snake,
method_snake => &method_snake,
},
));
} else {
match &method.return_type {
TypeRef::Unit => out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_unit_call.rs.jinja",
minijinja::context! {
call_expr => &call_expr,
},
)),
_ => out.push_str(&crate::backends::swift::template_env::render(
"inbound_method_value_call.rs.jinja",
minijinja::context! {
call_expr => &call_expr,
},
)),
}
}
out.push_str(" }\n\n");
}
fn inbound_param_to_bridge(p: &ParamDef) -> Option<String> {
let local = inbound_local_name(p);
let name = p.name.to_snake_case();
if needs_inbound_json_bridge(&p.ty) {
if p.optional {
return Some(format!(
"let {local} = {name}.map(|v| ::serde_json::to_string(&v).expect(\"serializable param {name}\"));"
));
}
return Some(format!(
"let {local} = ::serde_json::to_string(&{name}).expect(\"serializable param {name}\");"
));
}
if p.optional {
return match &p.ty {
TypeRef::Path => Some(format!(
"let {local} = {name}.map(|v| v.to_string_lossy().into_owned());"
)),
TypeRef::Bytes if p.is_ref => Some(format!("let {local} = {name}.map(|v| v.to_vec());")),
TypeRef::String if p.is_ref => Some(format!("let {local} = {name}.map(|v| v.to_string());")),
TypeRef::Vec(_) if p.is_ref => Some(format!("let {local} = {name}.map(|v| v.to_vec());")),
_ => None,
};
}
match &p.ty {
TypeRef::Path => {
Some(format!("let {local} = {name}.to_string_lossy().into_owned();"))
}
TypeRef::Bytes => {
if p.is_ref {
Some(format!("let {local} = {name}.to_vec();"))
} else {
None
}
}
TypeRef::String => {
if p.is_ref {
Some(format!("let {local} = {name}.to_string();"))
} else {
None
}
}
TypeRef::Vec(_) if p.is_ref => Some(format!("let {local} = {name}.to_vec();")),
_ => None,
}
}
fn inbound_local_name(p: &ParamDef) -> String {
p.name.to_snake_case()
}
fn inbound_return_type(method: &MethodDef) -> String {
if method.error_type.is_some() {
return "String".to_string();
}
inbound_bridge_type(&method.return_type)
}
fn inbound_impl_return_type(
method: &MethodDef,
source_crate: &str,
type_paths: &std::collections::HashMap<String, String>,
error_type: &str,
) -> String {
if method.returns_ref {
if let TypeRef::Vec(inner) = &method.return_type {
let elem = match inner.as_ref() {
TypeRef::String => "&'static str".to_string(),
other => inbound_native_ty(other, source_crate, type_paths),
};
return format!("&'static [{elem}]");
}
}
let inner = inbound_native_ty_owned(&method.return_type, source_crate, type_paths);
if method.error_type.is_some() {
if matches!(method.return_type, TypeRef::Unit) {
result_type(source_crate, error_type, "()")
} else {
result_type(source_crate, error_type, &inner)
}
} else {
inner
}
}
fn result_type(source_crate: &str, error_type: &str, ok_type: &str) -> String {
format!(
"std::result::Result<{ok_type}, {}>",
error_type_path(source_crate, error_type)
)
}
fn error_type_path(source_crate: &str, error_type: &str) -> String {
if error_type.contains("::") || error_type.contains('<') {
error_type.to_string()
} else {
format!("{source_crate}::{error_type}")
}
}
fn resolve_named_path(
name: &str,
source_crate: &str,
type_paths: &std::collections::HashMap<String, String>,
) -> String {
if let Some(path) = type_paths.get(name) {
return path.replace('-', "_");
}
format!("{source_crate}::{name}")
}
fn inbound_native_return_ty(
ty: &TypeRef,
source_crate: &str,
type_paths: &std::collections::HashMap<String, String>,
) -> String {
match ty {
TypeRef::Named(name) => resolve_named_path(name, source_crate, type_paths),
TypeRef::Vec(inner) => format!("Vec<{}>", inbound_native_return_ty(inner, source_crate, type_paths)),
TypeRef::Optional(inner) => format!("Option<{}>", inbound_native_return_ty(inner, source_crate, type_paths)),
TypeRef::Map(k, v) => format!(
"::std::collections::HashMap<{}, {}>",
inbound_native_return_ty(k, source_crate, type_paths),
inbound_native_return_ty(v, source_crate, type_paths)
),
TypeRef::String => "String".to_string(),
TypeRef::Bytes => "Vec<u8>".to_string(),
TypeRef::Path => "::std::path::PathBuf".to_string(),
_ => swift_bridge_rust_type(ty),
}
}
fn inbound_native_ty(
ty: &TypeRef,
source_crate: &str,
type_paths: &std::collections::HashMap<String, String>,
) -> String {
match ty {
TypeRef::Unit => "()".to_string(),
TypeRef::String => "str".to_string(),
TypeRef::Bytes => "[u8]".to_string(),
TypeRef::Path => "::std::path::Path".to_string(),
TypeRef::Char => "char".to_string(),
TypeRef::Json => "::serde_json::Value".to_string(),
TypeRef::Duration => "::std::time::Duration".to_string(),
TypeRef::Primitive(p) => primitive_str(p).to_string(),
TypeRef::Named(name) => resolve_named_path(name, source_crate, type_paths),
TypeRef::Vec(inner) => format!("Vec<{}>", inbound_native_ty_owned(inner, source_crate, type_paths)),
TypeRef::Optional(inner) => format!("Option<{}>", inbound_native_ty_owned(inner, source_crate, type_paths)),
TypeRef::Map(k, v) => format!(
"::std::collections::HashMap<{}, {}>",
inbound_native_ty_owned(k, source_crate, type_paths),
inbound_native_ty_owned(v, source_crate, type_paths)
),
}
}
fn inbound_native_ty_owned(
ty: &TypeRef,
source_crate: &str,
type_paths: &std::collections::HashMap<String, String>,
) -> String {
match ty {
TypeRef::String => "String".to_string(),
TypeRef::Bytes => "Vec<u8>".to_string(),
TypeRef::Path => "::std::path::PathBuf".to_string(),
_ => inbound_native_ty(ty, source_crate, type_paths),
}
}
fn primitive_str(p: &crate::core::ir::PrimitiveType) -> &'static str {
use crate::core::ir::PrimitiveType::*;
match p {
Bool => "bool",
I8 => "i8",
I16 => "i16",
I32 => "i32",
I64 => "i64",
Isize => "isize",
U8 => "u8",
U16 => "u16",
U32 => "u32",
U64 => "u64",
Usize => "usize",
F32 => "f32",
F64 => "f64",
}
}
pub(crate) fn emit_options_field_factory(
trait_def: &TypeDef,
bridge_config: &TraitBridgeConfig,
api: &ApiSurface,
source_crate: &str,
) -> (String, String) {
debug_assert_eq!(bridge_config.bind_via, BridgeBinding::OptionsField);
let trait_name = &trait_def.name;
let trait_snake = heck::AsSnakeCase(trait_name.as_str()).to_string();
let box_name = format!("Swift{trait_name}Box");
let wrapper_name = format!("Swift{trait_name}Wrapper");
let type_alias = match bridge_config.type_alias.as_deref() {
Some(a) => a,
None => return (String::new(), String::new()),
};
let alias_def = api.types.iter().find(|t| t.name == type_alias);
let inner_path = match alias_def {
Some(td) if !td.rust_path.is_empty() => td.rust_path.replace('-', "_"),
_ => format!("{source_crate}::{type_alias}"),
};
let fn_name = format!("make_{trait_snake}_handle");
let swift_name = heck::AsLowerCamelCase(fn_name.as_str()).to_string();
let extern_decl = format!(
" extern \"Rust\" {{\n\
\n\
\x20\x20\x20\x20\x20\x20\x20\x20 #[swift_bridge(swift_name = \"{swift_name}\")]\n\
\x20\x20\x20\x20\x20\x20\x20\x20 fn {fn_name}(swift_box: {box_name}) -> {type_alias};\n\
\n\
\x20\x20\x20\x20}}\n\n"
);
let fn_body = format!(
"/// Construct a `{type_alias}` from a Swift `{box_name}` handle.\n\
/// Called by Swift e2e tests via `{swift_name}(...)` to build a\n\
/// `{type_alias}` that can be passed to the options-with-visitor helper.\n\
pub fn {fn_name}(swift_box: ffi::{box_name}) -> {type_alias} {{\n\
\x20 let __wrapper = {wrapper_name}::new(swift_box);\n\
\x20 let __inner: {inner_path} = ::std::sync::Arc::new(::std::sync::Mutex::new(__wrapper));\n\
\x20 {type_alias}::from(__inner)\n\
}}\n"
);
(extern_decl, fn_body)
}
pub(crate) fn emit_options_field_from_impls(
bridge_config: &TraitBridgeConfig,
api: &ApiSurface,
source_crate: &str,
already_emitted: &mut std::collections::HashSet<String>,
) -> String {
debug_assert_eq!(bridge_config.bind_via, BridgeBinding::OptionsField);
let type_alias = match bridge_config.type_alias.as_deref() {
Some(a) => a,
None => return String::new(),
};
let options_type = match bridge_config.options_type.as_deref() {
Some(o) => o,
None => return String::new(),
};
let alias_def = api.types.iter().find(|t| t.name == type_alias);
let inner_path = match alias_def {
Some(td) if !td.rust_path.is_empty() => td.rust_path.replace('-', "_"),
_ => format!("{source_crate}::{type_alias}"),
};
let opts_def = api.types.iter().find(|t| t.name == options_type);
let core_options_path = match opts_def {
Some(td) if !td.rust_path.is_empty() => td.rust_path.replace('-', "_"),
_ => format!("{source_crate}::{options_type}"),
};
let mut out = String::new();
let alias_key = format!("alias::{type_alias}::{inner_path}");
if !already_emitted.contains(&alias_key) {
already_emitted.insert(alias_key);
out.push_str(&format!(
"impl From<{inner_path}> for {type_alias} {{\n \
fn from(v: {inner_path}) -> Self {{ Self(v) }}\n\
}}\n\
impl From<{type_alias}> for {inner_path} {{\n \
fn from(v: {type_alias}) -> Self {{ v.0 }}\n\
}}\n"
));
}
let opts_key = format!("opts::{options_type}::{core_options_path}");
if !already_emitted.contains(&opts_key) {
already_emitted.insert(opts_key);
out.push_str(&format!(
"impl From<{core_options_path}> for {options_type} {{\n \
fn from(v: {core_options_path}) -> Self {{ Self(v) }}\n\
}}\n"
));
}
out
}
pub(crate) fn emit_options_field_options_helper(
bridge_config: &TraitBridgeConfig,
api: &ApiSurface,
source_crate: &str,
) -> (String, String) {
debug_assert_eq!(bridge_config.bind_via, BridgeBinding::OptionsField);
let type_alias = match bridge_config.type_alias.as_deref() {
Some(a) => a,
None => return (String::new(), String::new()),
};
let options_type = match bridge_config.options_type.as_deref() {
Some(o) => o,
None => return (String::new(), String::new()),
};
let field = match bridge_config.resolved_options_field() {
Some(f) => f.to_string(),
None => return (String::new(), String::new()),
};
let options_snake = heck::AsSnakeCase(options_type).to_string();
let fn_name = format!("{options_snake}_from_json_with_{field}");
let swift_name = heck::AsLowerCamelCase(fn_name.as_str()).to_string();
let alias_def = api.types.iter().find(|t| t.name == type_alias);
let inner_path = match alias_def {
Some(td) if !td.rust_path.is_empty() => td.rust_path.replace('-', "_"),
_ => format!("{source_crate}::{type_alias}"),
};
let opts_def = api.types.iter().find(|t| t.name == options_type);
let core_options_path = match opts_def {
Some(td) if !td.rust_path.is_empty() => td.rust_path.replace('-', "_"),
_ => format!("{source_crate}::{options_type}"),
};
let extern_decl = format!(
" extern \"Rust\" {{\n\
\n\
\x20\x20\x20\x20\x20\x20\x20\x20 #[swift_bridge(swift_name = \"{swift_name}\")]\n\
\x20\x20\x20\x20\x20\x20\x20\x20 fn {fn_name}(json: String, {field}: Option<{type_alias}>) -> Result<{options_type}, String>;\n\
\n\
\x20\x20\x20\x20}}\n\n"
);
let fn_body = format!(
"/// Deserialise a `{options_type}` from JSON and attach a visitor handle to its\n\
/// `{field}` field. Used by Swift e2e tests to thread a `{type_alias}` into the\n\
/// conversion call without needing a mutable post-construction setter.\n\
pub fn {fn_name}(json: String, {field}: Option<{type_alias}>) -> Result<{options_type}, String> {{\n\
\x20 let mut __core: {core_options_path} = ::serde_json::from_str(&json).map_err(|e| e.to_string())?;\n\
\x20 __core.{field} = {field}.map(|h| <{inner_path}>::from(h));\n\
\x20 Ok({options_type}::from(__core))\n\
}}\n"
);
(extern_decl, fn_body)
}