use crate::backends::swift::gen_rust_crate::default_construction::{
emit_default_construction_body, emit_direct_field_inits,
};
use crate::backends::swift::gen_rust_crate::type_bridge::{
bridge_type, bridge_type_enum_aware_ref, is_enum_named, is_vec_of_enum, needs_json_bridge,
needs_json_bridge_with_handles, swift_bridge_rust_type,
};
use crate::codegen::generators::type_paths::resolve_type_path;
use crate::core::ir::{CoreWrapper, FieldDef, ReceiverKind, TypeDef, TypeRef};
use crate::core::keywords::swift_ident;
use heck::ToSnakeCase;
use std::collections::{HashMap, HashSet};
pub(crate) fn is_unbridgeable_getter(
ty: &TypeDef,
field: &FieldDef,
exclude_fields: &HashSet<String>,
type_paths: &HashMap<String, String>,
no_serde_names: &HashSet<&str>,
) -> bool {
let name = field.name.to_snake_case();
let field_key = format!("{}.{}", ty.name, name);
if field.binding_excluded || exclude_fields.contains(&field_key) {
return true;
}
if needs_json_bridge(&field.ty) {
let inner_named = match &field.ty {
TypeRef::Optional(inner) | TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(n) => Some(n.as_str()),
_ => None,
},
TypeRef::Named(n) => Some(n.as_str()),
_ => None,
};
if let Some(n) = inner_named {
if !type_paths.contains_key(n) || no_serde_names.contains(n) {
return true;
}
}
}
if let TypeRef::Vec(inner) = &field.ty {
if !ty.has_serde && !matches!(inner.as_ref(), TypeRef::Primitive(_) | TypeRef::Bytes) {
return true;
}
if field.sanitized && !matches!(inner.as_ref(), TypeRef::Primitive(_) | TypeRef::Bytes) {
return true;
}
}
false
}
pub(crate) fn emit_type_wrapper(
ty: &TypeDef,
source_crate: &str,
type_paths: &HashMap<String, String>,
enum_names: &HashSet<&str>,
no_serde_names: &HashSet<&str>,
exclude_fields: &HashSet<String>,
) -> String {
let mut out = String::new();
let source_path = resolve_type_path(&ty.name, source_crate, type_paths);
out.push_str(&crate::backends::swift::template_env::render(
"struct_newtype.jinja",
minijinja::context! {
name => &ty.name,
source_path => &source_path,
has_lifetime_params => ty.has_lifetime_params,
},
));
if !ty.fields.is_empty() {
out.push_str(&crate::backends::swift::template_env::render(
"impl_header.jinja",
minijinja::context! {
name => &ty.name,
},
));
let constructor_fields: Vec<_> = ty
.fields
.iter()
.filter(|f| {
let field_key = format!("{}.{}", ty.name, f.name.to_snake_case());
!f.binding_excluded && !exclude_fields.contains(&field_key)
})
.collect();
let params: Vec<String> = constructor_fields
.iter()
.map(|f| {
let bridge_ty = bridge_type(&f.ty);
let bridge_ty = if f.optional && !needs_json_bridge(&f.ty) {
format!("Option<{bridge_ty}>")
} else {
bridge_ty
};
let name = swift_ident(&f.name.to_snake_case());
format!("{name}: {bridge_ty}")
})
.collect();
let all_primitive_fields = constructor_fields.iter().all(|f| matches!(f.ty, TypeRef::Primitive(_)));
let has_vec_non_primitive = constructor_fields.iter().any(|f| {
matches!(&f.ty, TypeRef::Vec(inner) if !matches!(inner.as_ref(), TypeRef::Primitive(_) | TypeRef::Bytes))
});
let has_non_serde_string_field = !ty.has_serde
&& constructor_fields
.iter()
.any(|f| matches!(f.ty, TypeRef::String | TypeRef::Path | TypeRef::Json | TypeRef::Char));
let needs_default_construction = !all_primitive_fields
&& (ty.has_serde
|| has_vec_non_primitive
|| has_non_serde_string_field
|| ty.has_stripped_cfg_fields
|| constructor_fields
.iter()
.any(|f| needs_json_bridge(&f.ty) || matches!(f.ty, TypeRef::Named(_))));
if needs_default_construction && !ty.has_default {
} else {
out.push_str(&crate::backends::swift::template_env::render(
"fn_new_signature.jinja",
minijinja::context! {
params => params.join(", "),
name => &ty.name,
},
));
if needs_default_construction && ty.has_default {
let body = emit_default_construction_body(
ty,
&source_path,
type_paths,
enum_names,
no_serde_names,
exclude_fields,
);
out.push_str(&body);
} else {
let field_inits = emit_direct_field_inits(ty, type_paths, enum_names, no_serde_names, exclude_fields);
out.push_str(&crate::backends::swift::template_env::render(
"struct_literal_open.jinja",
minijinja::context! {
name => &ty.name,
source_path => &source_path,
},
));
for init in &field_inits {
out.push_str(init);
out.push_str(",\n");
}
out.push_str(" })\n");
}
out.push_str(" }\n");
}
emit_getters(ty, type_paths, enum_names, no_serde_names, exclude_fields, &mut out);
out.push_str("}\n");
}
out
}
struct GetterCtx {
name: String,
getter_name: String,
bridge_ty_owned: String,
}
fn emit_getters(
ty: &TypeDef,
type_paths: &HashMap<String, String>,
enum_names: &HashSet<&str>,
no_serde_names: &HashSet<&str>,
exclude_fields: &HashSet<String>,
out: &mut String,
) {
for field in &ty.fields {
let bridge_ty = bridge_type_enum_aware_ref(&field.ty, enum_names);
let bridge_ty_owned = if field.optional && !needs_json_bridge(&field.ty) {
if is_vec_of_enum(&field.ty, enum_names) {
"String".to_string()
} else {
format!("Option<{bridge_ty}>")
}
} else {
bridge_ty
};
let name = field.name.to_snake_case();
let getter_name = swift_ident(&name);
if is_unbridgeable_getter(ty, field, exclude_fields, type_paths, no_serde_names) {
if field.binding_excluded {
continue;
}
out.push_str(&crate::backends::swift::template_env::render(
"getter_skip_comment.jinja",
minijinja::context! {
name => &name,
},
));
continue;
}
let ctx = GetterCtx {
name,
getter_name,
bridge_ty_owned,
};
if needs_json_bridge(&field.ty) {
out.push_str(&crate::backends::swift::template_env::render(
"getter_json_bridge.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
} else if is_enum_named(&field.ty, enum_names) {
emit_enum_string_getter(field, &ctx, enum_names, out);
} else if is_vec_of_enum(&field.ty, enum_names) {
emit_vec_enum_string_getter(field, &ctx, enum_names, out);
} else if let TypeRef::Named(wrapper) = &field.ty {
emit_named_getter(field, wrapper, &ctx, enum_names, out);
} else if let TypeRef::Vec(inner) = &field.ty {
emit_vec_getter(ty, field, inner, &ctx, enum_names, out);
} else if matches!(
field.ty,
TypeRef::String | TypeRef::Path | TypeRef::Char | TypeRef::Json
) {
emit_string_like_getter(ty, field, &ctx, out);
} else if matches!(field.ty, TypeRef::Bytes) {
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_optional_bytes.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_bytes.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
}
} else if matches!(field.ty, TypeRef::Duration) {
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_optional_duration.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
name => &ctx.name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_duration.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
name => &ctx.name,
},
));
}
} else if ty.has_serde && matches!(&field.ty, TypeRef::Vec(_) | TypeRef::Primitive(_)) {
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_serde_optional.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_serde.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
}
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_simple_clone.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
}
}
}
fn emit_enum_string_getter(
field: &crate::core::ir::FieldDef,
ctx: &GetterCtx,
enum_names: &HashSet<&str>,
out: &mut String,
) {
let TypeRef::Named(wrapper) = &field.ty else {
return;
};
let is_enum = enum_names.contains(wrapper.as_str());
debug_assert!(is_enum, "emit_enum_string_getter called with non-enum Named type");
let name = &ctx.name;
let getter_name = &ctx.getter_name;
if field.optional {
let map_expr = if field.is_boxed {
format!("self.0.{name}.clone().map(|w| {wrapper}::from(*w).to_string())")
} else if matches!(field.core_wrapper, CoreWrapper::Arc) {
format!("self.0.{name}.clone().map(|w| {wrapper}::from((*w).clone()).to_string())")
} else {
format!("self.0.{name}.clone().map(|w| {wrapper}::from(w).to_string())")
};
out.push_str(&crate::backends::swift::template_env::render(
"getter_enum_string_optional.jinja",
minijinja::context! {
getter_name => getter_name,
map_expr => map_expr,
},
));
} else {
let expr = if field.is_boxed {
format!("{wrapper}::from(*self.0.{name}.clone()).to_string()")
} else if matches!(field.core_wrapper, CoreWrapper::Arc) {
format!("{wrapper}::from((*self.0.{name}).clone()).to_string()")
} else {
format!("{wrapper}::from(self.0.{name}.clone()).to_string()")
};
out.push_str(&crate::backends::swift::template_env::render(
"getter_enum_string.jinja",
minijinja::context! {
getter_name => getter_name,
expr => expr,
},
));
}
}
fn emit_vec_enum_string_getter(
field: &crate::core::ir::FieldDef,
ctx: &GetterCtx,
enum_names: &HashSet<&str>,
out: &mut String,
) {
let TypeRef::Vec(inner) = &field.ty else {
return;
};
let TypeRef::Named(wrapper) = inner.as_ref() else {
return;
};
let is_enum = enum_names.contains(wrapper.as_str());
debug_assert!(is_enum, "emit_vec_enum_string_getter called with non-enum Vec<Named>");
let name = &ctx.name;
let getter_name = &ctx.getter_name;
let elem_expr = match field.vec_inner_core_wrapper {
CoreWrapper::Arc => format!("{wrapper}::from((**elem).clone()).to_string()"),
_ => format!("{wrapper}::from(elem.clone()).to_string()"),
};
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_enum_string_optional.jinja",
minijinja::context! {
getter_name => getter_name,
name => name,
elem_expr => elem_expr,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_enum_string.jinja",
minijinja::context! {
getter_name => getter_name,
name => name,
elem_expr => elem_expr,
},
));
}
}
fn emit_named_getter(
field: &crate::core::ir::FieldDef,
wrapper: &str,
ctx: &GetterCtx,
enum_names: &HashSet<&str>,
out: &mut String,
) {
let name = &ctx.name;
let getter_name = &ctx.getter_name;
let is_enum = enum_names.contains(wrapper);
if field.optional {
let getter_expr = if field.is_boxed {
if is_enum {
format!("self.0.{name}.clone().map(|w| {wrapper}::from(*w))")
} else {
format!("self.0.{name}.clone().map(|w| {wrapper}(*w))")
}
} else if matches!(field.core_wrapper, CoreWrapper::Arc) {
if is_enum {
format!("self.0.{name}.clone().map(|w| {wrapper}::from((*w).clone()))")
} else {
format!("self.0.{name}.clone().map(|w| {wrapper}((*w).clone()))")
}
} else if is_enum {
format!("self.0.{name}.clone().map({wrapper}::from)")
} else {
format!("self.0.{name}.clone().map({wrapper})")
};
out.push_str(&crate::backends::swift::template_env::render(
"getter_optional_named.jinja",
minijinja::context! {
getter_name => getter_name,
wrapper => wrapper,
getter_expr => &getter_expr,
},
));
} else {
let expr = if field.is_boxed {
if is_enum {
format!("{wrapper}::from(*self.0.{name}.clone())")
} else {
format!("{wrapper}(*self.0.{name}.clone())")
}
} else if matches!(field.core_wrapper, CoreWrapper::Arc) {
if is_enum {
format!("{wrapper}::from((*self.0.{name}).clone())")
} else {
format!("{wrapper}((*self.0.{name}).clone())")
}
} else if is_enum {
format!("{wrapper}::from(self.0.{name}.clone())")
} else {
format!("{wrapper}(self.0.{name}.clone())")
};
out.push_str(&crate::backends::swift::template_env::render(
"getter_named.jinja",
minijinja::context! {
getter_name => getter_name,
wrapper => wrapper,
expr => &expr,
},
));
}
}
fn emit_vec_getter(
ty: &TypeDef,
field: &crate::core::ir::FieldDef,
inner: &TypeRef,
ctx: &GetterCtx,
enum_names: &HashSet<&str>,
out: &mut String,
) {
let _name = &ctx.name;
let _getter_name = &ctx.getter_name;
let _bridge_ty_owned = &ctx.bridge_ty_owned;
if let TypeRef::Named(wrapper) = inner {
let is_enum = enum_names.contains(wrapper.as_str());
let elem_expr = match field.vec_inner_core_wrapper {
CoreWrapper::Arc if !is_enum => format!("{wrapper}((**elem).clone())"),
CoreWrapper::Arc => format!("{wrapper}::from((**elem).clone())"),
_ if is_enum => format!("{wrapper}::from(elem.clone())"),
_ => format!("{wrapper}(elem.clone())"),
};
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_named_optional.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
wrapper => wrapper,
name => &ctx.name,
elem_expr => &elem_expr,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_named.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
wrapper => wrapper,
name => &ctx.name,
elem_expr => &elem_expr,
},
));
}
} else if !matches!(inner, TypeRef::Primitive(_) | TypeRef::Bytes) {
if ty.has_serde {
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_complex_serde_optional.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_complex_serde.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
}
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_complex_skip.jinja",
minijinja::context! {
name => &ctx.name,
},
));
}
} else {
if ty.has_serde {
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_primitive_serde_optional.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_primitive_serde.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
}
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_vec_primitive_clone.jinja",
minijinja::context! {
getter_name => &ctx.getter_name,
return_type => &ctx.bridge_ty_owned,
name => &ctx.name,
},
));
}
}
}
fn emit_string_like_getter(ty: &TypeDef, field: &crate::core::ir::FieldDef, ctx: &GetterCtx, out: &mut String) {
let name = &ctx.name;
let getter_name = &ctx.getter_name;
let bridge_ty_owned = &ctx.bridge_ty_owned;
if matches!(field.ty, TypeRef::Char) {
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_char_optional.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_char.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
}
return;
}
if !ty.has_serde {
if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_string_like_debug_optional.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_string_like_debug.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
}
} else if matches!(field.ty, TypeRef::String)
&& !field.sanitized
&& matches!(field.core_wrapper, crate::core::ir::CoreWrapper::None)
{
out.push_str(&crate::backends::swift::template_env::render(
"getter_simple_clone.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
} else if matches!(field.ty, TypeRef::String)
&& matches!(field.core_wrapper, crate::core::ir::CoreWrapper::Cow)
&& !field.optional
{
out.push_str(&crate::backends::swift::template_env::render(
"getter_string_cow.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
} else if matches!(field.ty, TypeRef::String)
&& matches!(field.core_wrapper, crate::core::ir::CoreWrapper::Cow)
&& field.optional
{
out.push_str(&crate::backends::swift::template_env::render(
"getter_string_cow_optional.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
} else if field.optional {
out.push_str(&crate::backends::swift::template_env::render(
"getter_string_like_serde_optional.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
} else {
out.push_str(&crate::backends::swift::template_env::render(
"getter_string_like_serde.jinja",
minijinja::context! {
getter_name => getter_name,
return_type => bridge_ty_owned,
name => name,
},
));
}
}
pub(crate) fn emit_type_constructor_shim(
ty: &TypeDef,
source_crate: &str,
type_paths: &HashMap<String, String>,
custom_body: Option<&str>,
) -> String {
let type_snake = ty.name.to_snake_case();
let fn_name = format!("create_{type_snake}");
let type_name = &ty.name;
let source_path = resolve_type_path(type_name, source_crate, type_paths);
if let Some(body) = custom_body {
let interpolated = body
.replace("{type_name}", type_name)
.replace("{source_path}", &source_path);
return format!(
"pub fn {fn_name}(api_key: String, base_url: Option<String>) -> Result<{type_name}, String> {{\n{interpolated}\n}}\n"
);
}
format!(
"pub fn {fn_name}(api_key: String, base_url: Option<String>) -> Result<{type_name}, String> {{\n \
{source_path}::new(api_key, base_url)\n \
.map_err(|e| e.to_string())\n \
.map({type_name})\n}}\n"
)
}
pub(crate) fn emit_type_method_shims(
ty: &TypeDef,
_source_crate: &str,
type_paths: &HashMap<String, String>,
handle_returned_types: &std::collections::HashSet<String>,
unit_enum_names: &HashSet<&str>,
) -> String {
let type_snake = ty.name.to_snake_case();
let type_name = &ty.name;
let mut out = String::new();
let mut trait_uses: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for method in &ty.methods {
if method.sanitized {
continue;
}
if let Some(path) = method.trait_source.as_deref() {
trait_uses.insert(path.to_string());
}
}
for path in &trait_uses {
out.push_str(&crate::backends::swift::template_env::render(
"rust_trait_use.rs.jinja",
minijinja::context! {
path => path,
},
));
}
if !trait_uses.is_empty() {
out.push('\n');
}
for method in &ty.methods {
if method.sanitized {
continue;
}
if method.is_static {
continue;
}
let method_snake = method.name.to_snake_case();
let fn_name = format!("{type_snake}_{method_snake}");
let client_receiver = if matches!(method.receiver, Some(ReceiverKind::RefMut)) {
format!("client: &mut {type_name}")
} else {
format!("client: &{type_name}")
};
let mut params_vec: Vec<String> = vec![client_receiver];
for p in &method.params {
let bridge_ty = bridge_type_enum_aware_ref(&p.ty, unit_enum_names);
let bridge_ty = if p.optional && !needs_json_bridge(&p.ty) {
format!("Option<{bridge_ty}>")
} else {
bridge_ty
};
let name = swift_ident(&p.name.to_snake_case());
params_vec.push(format!("{name}: {bridge_ty}"));
}
let params_str = params_vec.join(", ");
let return_ty = if method.error_type.is_some() {
let ok_ty = crate::backends::swift::gen_rust_crate::type_bridge::bridge_type_with_handles(
&method.return_type,
handle_returned_types,
);
if matches!(method.return_type, TypeRef::Unit) {
"Result<(), String>".to_string()
} else {
format!("Result<{ok_ty}, String>")
}
} else {
crate::backends::swift::gen_rust_crate::type_bridge::bridge_type_with_handles(
&method.return_type,
handle_returned_types,
)
};
let call_args: Vec<String> = method
.params
.iter()
.map(|p| {
let name = p.name.to_snake_case();
if matches!(&p.ty, TypeRef::Json) {
return format!(
"serde_json::from_str::<serde_json::Value>(&{name}).unwrap_or(serde_json::Value::Null)"
);
}
if let TypeRef::Vec(vec_inner) = &p.ty {
if let TypeRef::Named(n) = vec_inner.as_ref() {
if unit_enum_names.contains(n.as_str()) {
let source_enum_ty = type_paths
.get(n.as_str())
.map(|p| p.replace('-', "_"))
.unwrap_or_else(|| n.clone());
let map_expr = format!(
"{name}.into_iter().map(|s| <{source_enum_ty} as ::std::convert::From<String>>::from(s)).collect::<Vec<_>>()"
);
if p.is_ref {
return format!("&{map_expr}");
}
if p.optional {
let opt_map = format!(
"{name}.map(|values| values.into_iter().map(|s| <{source_enum_ty} as ::std::convert::From<String>>::from(s)).collect::<Vec<_>>())"
);
return opt_map;
}
return map_expr;
}
}
}
if let TypeRef::Named(n) = &p.ty {
if unit_enum_names.contains(n.as_str()) {
let source_enum_ty = type_paths
.get(n.as_str())
.map(|p| p.replace('-', "_"))
.unwrap_or_else(|| n.clone());
let from_expr =
format!("<{source_enum_ty} as ::std::convert::From<String>>::from({name})");
if p.optional {
return format!(
"{name}.map(|s| <{source_enum_ty} as ::std::convert::From<String>>::from(s))"
);
}
if p.is_ref {
return format!("&{from_expr}");
}
return from_expr;
}
}
if needs_json_bridge(&p.ty) {
let native_ty = swift_bridge_rust_type(&p.ty);
return format!("serde_json::from_str::<{native_ty}>(&{name}).expect(\"valid JSON for {name}\")");
}
if p.optional {
if let TypeRef::Named(n) = &p.ty {
if !unit_enum_names.contains(n.as_str()) {
return format!("{name}.map(|v| v.0)");
}
}
}
match &p.ty {
TypeRef::Named(n) if p.is_ref && !unit_enum_names.contains(n.as_str()) => format!("&{name}.0"),
TypeRef::Named(n) if p.is_ref && unit_enum_names.contains(n.as_str()) => format!("&{name}"),
TypeRef::Named(n) if !unit_enum_names.contains(n.as_str()) => format!("{name}.0"),
TypeRef::Named(n) if unit_enum_names.contains(n.as_str()) => name,
TypeRef::String => format!("&{name}"),
TypeRef::Path => format!("::std::path::PathBuf::from({name})"),
TypeRef::Bytes if p.is_ref => format!("&{name}"),
TypeRef::Vec(_)
if p.is_ref
&& p.vec_inner_is_ref
&& matches!(&p.ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String)) =>
{
format!("&{name}.iter().map(|s| s.as_str()).collect::<Vec<_>>()")
}
TypeRef::Vec(_) if p.is_ref => {
format!("&{name}")
}
_ => name,
}
})
.collect();
let call_args_str = call_args.join(", ");
let is_owned_receiver = matches!(method.receiver.as_ref(), Some(ReceiverKind::Owned));
let inner_access = if is_owned_receiver {
"client.0.clone()"
} else {
"client.0"
};
let method_call = format!("{inner_access}.{method_snake}({call_args_str})");
let json_wrap_ok = needs_json_bridge_with_handles(&method.return_type, handle_returned_types);
let wrap_return = |source: String| -> String {
if json_wrap_ok {
return format!("serde_json::to_string(&({source})).expect(\"serializable return\")");
}
match &method.return_type {
TypeRef::Named(t) => format!("{t}({source})"),
TypeRef::Optional(inner) => {
if let TypeRef::Named(t) = inner.as_ref() {
format!("({source}).map({t})")
} else {
source
}
}
TypeRef::String => format!("{source}.to_string()"),
TypeRef::Path => format!("{source}.to_string_lossy().into_owned()"),
TypeRef::Vec(inner) if method.returns_ref && matches!(inner.as_ref(), TypeRef::String) => {
format!("{source}.iter().map(|s| s.to_string()).collect()")
}
_ => source,
}
};
let body = if method.is_async {
let chain = if method.error_type.is_some() {
let ok_wrap = if json_wrap_ok {
".map(|v| serde_json::to_string(&v).expect(\"serializable return\"))".to_string()
} else {
match &method.return_type {
TypeRef::Named(t) => format!(".map({t})"),
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
if let TypeRef::Named(t) = inner.as_ref() {
format!(".map(|vec| vec.into_iter().map({t}).collect())")
} else {
String::new()
}
}
TypeRef::String | TypeRef::Path => ".map(|s| s.to_string())".to_string(),
TypeRef::Bytes => ".map(|b| b.to_vec())".to_string(),
_ => String::new(),
}
};
format!("{method_call}.await.map_err(|e| e.to_string()){ok_wrap}")
} else {
wrap_return(format!("{method_call}.await"))
};
format!(" crate::__alef_tokio_runtime().block_on(async {{ {chain} }})")
} else if method.error_type.is_some() {
let ok_wrap = if json_wrap_ok {
".map(|v| serde_json::to_string(&v).expect(\"serializable return\"))".to_string()
} else {
match &method.return_type {
TypeRef::Named(t) => format!(".map({t})"),
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
if let TypeRef::Named(t) = inner.as_ref() {
format!(".map(|vec| vec.into_iter().map({t}).collect())")
} else {
String::new()
}
}
TypeRef::String | TypeRef::Path => ".map(|s| s.to_string())".to_string(),
TypeRef::Bytes => ".map(|b| b.to_vec())".to_string(),
_ => String::new(),
}
};
format!(" {method_call}.map_err(|e| e.to_string()){ok_wrap}")
} else {
format!(" {}", wrap_return(method_call))
};
let return_clause = if return_ty == "()" {
String::new()
} else {
format!(" -> {return_ty}")
};
out.push_str(&crate::backends::swift::template_env::render(
"rust_wrapper_free_fn.rs.jinja",
minijinja::context! {
fn_name => fn_name,
params => params_str,
return_clause => return_clause,
body => body,
},
));
}
out
}
pub(crate) fn emit_streaming_adapter_shims(
adapters: &[crate::core::config::AdapterConfig],
source_crate: &str,
) -> String {
use crate::core::config::AdapterPattern;
use heck::{ToPascalCase, ToSnakeCase};
let mut out = String::new();
for adapter in adapters
.iter()
.filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
.filter(|a| a.owner_type.is_some())
{
let owner_type = adapter.owner_type.as_deref().unwrap_or("");
let item_type = adapter
.item_type
.as_deref()
.expect("streaming adapter must declare item_type for Swift backend");
let owner_snake = owner_type.to_snake_case();
let adapter_pascal = adapter.name.to_pascal_case();
let owner_pascal = owner_type.to_pascal_case();
let handle_name = format!("{owner_pascal}{adapter_pascal}StreamHandle");
let fn_start = format!("{owner_snake}_{}_start", adapter.name);
let core_item = format!("{source_crate}::{item_type}");
let mut start_params_vec: Vec<String> = vec![format!("client: &{owner_type}")];
for p in &adapter.params {
let simple_ty = p.ty.rsplit("::").next().unwrap_or(&p.ty);
let param_name = swift_ident(&p.name.to_snake_case());
start_params_vec.push(format!("{param_name}: &{simple_ty}"));
}
let start_params_str = start_params_vec.join(", ");
let call_args: Vec<String> = adapter
.params
.iter()
.map(|p| {
let name = p.name.to_snake_case();
format!("{name}.0.clone()")
})
.collect();
let call_args_str = call_args.join(", ");
let core_call = if adapter.core_path.contains("::") {
format!("{}(&client.0, {call_args_str})", adapter.core_path)
} else {
format!("client.0.{}({call_args_str})", adapter.core_path)
};
out.push_str(&crate::backends::swift::template_env::render(
"rust_stream_handle_struct.rs.jinja",
minijinja::context! {
item_type => &item_type,
fn_start => &fn_start,
handle_name => &handle_name,
core_item => &core_item,
},
));
out.push_str(&crate::backends::swift::template_env::render(
"rust_stream_handle_start.rs.jinja",
minijinja::context! {
owner_type => owner_type,
adapter_name => &adapter.name,
handle_name => &handle_name,
fn_start => &fn_start,
start_params => &start_params_str,
core_call => &core_call,
core_item => &core_item,
},
));
out.push_str(&crate::backends::swift::template_env::render(
"rust_stream_handle_next.rs.jinja",
minijinja::context! {
handle_name => &handle_name,
},
));
}
out
}