use super::*;
use crate::core::config::ResolvedCrateConfig;
use std::collections::HashMap;
pub(crate) type TextVariants = HashMap<String, (String, String, bool)>;
pub(crate) fn text_variants(api: &ApiSurface, configured: &[String]) -> anyhow::Result<TextVariants> {
api.enums
.iter()
.filter(|definition| configured.contains(&definition.name))
.map(|definition| {
let variant = definition
.variants
.iter()
.find(|variant| variant.fields.len() == 1 && matches!(variant.fields[0].ty, TypeRef::String))
.ok_or_else(|| {
anyhow::anyhow!("text-projected enum {} has no single-String variant", definition.name)
})?;
Ok((
definition.name.clone(),
(
super::super::enums::swift_variant_case(&variant.name),
super::super::enums::swift_associated_label(&variant.fields[0].name, 0),
definition.has_serde,
),
))
})
.collect()
}
pub(crate) fn error_type_name(api: &ApiSurface, config: &ResolvedCrateConfig, module: &str) -> String {
let name = api
.errors
.first()
.map(|error| error.name.clone())
.unwrap_or_else(|| config.error_type_name());
if name == "Error" {
format!("{module}Error")
} else {
name
}
}
fn projected_read_expression(field: &FieldDef, accessor: &str, variants: &TextVariants) -> Option<String> {
let configured = variants.keys().map(String::as_str).collect();
crate::backends::swift::gen_rust_crate::type_bridge::text_projected_getter_type(field, &configured)?;
let named = match &field.ty {
TypeRef::Optional(inner) => inner.as_ref(),
other => other,
};
let TypeRef::Named(name) = named else { return None };
let (variant, label, _) = variants.get(name)?;
Some(
crate::backends::swift::template_env::render(
"swift_text_projected_read.swift.jinja",
crate::alef_context! { name => name, variant => variant, label => label, accessor => accessor,
optional => field.optional || matches!(field.ty, TypeRef::Optional(_)) },
)
.trim_end()
.to_string(),
)
}
pub(super) fn assignments(context: Initializer<'_>) -> String {
let mut output = String::new();
for field in binding_fields(&context.ty.fields) {
output.push_str(&crate::backends::swift::template_env::render(
"swift_self_assignment.swift.jinja",
crate::alef_context! { field => swift_case_ident(&field.name.to_lower_camel_case()),
expr => context.expression(field) },
));
}
output
}
pub(super) struct Initializer<'a> {
pub(super) ty: &'a TypeDef,
pub(super) mapper: &'a SwiftMapper,
pub(super) exclude_fields: &'a HashSet<String>,
pub(super) known_dto_names: &'a HashSet<String>,
pub(super) unit_enum_names: &'a HashSet<String>,
pub(super) untagged_enum_names: &'a HashSet<String>,
pub(super) serde_struct_names: &'a HashSet<String>,
pub(super) error_type_name: &'a str,
pub(super) text_variants: &'a TextVariants,
}
impl Initializer<'_> {
fn expression(&self, field: &FieldDef) -> String {
let optional = field.optional || matches!(field.ty, TypeRef::Optional(_));
if is_field_unbridgeable_for_init(self.ty, field, self.exclude_fields, self.known_dto_names) {
return if optional {
"nil".to_string()
} else {
format!(
"try JSONDecoder().decode({}.self, from: Data(\"null\".utf8))",
self.mapper.map_type(&field.ty)
)
};
}
let accessor = swift_ident(&field.name.to_lower_camel_case());
if field.optional && matches!(field.ty, TypeRef::Vec(_)) {
return json_read(&accessor, &format!("{}?", self.mapper.map_type(&field.ty)), false);
}
if is_vec_of_serde_struct(&field.ty, self.serde_struct_names) {
return self.serde_vector(field, &accessor, optional);
}
if let Some(expression) = self.projected_text(field, &accessor) {
return expression;
}
if is_untagged_enum_type(&field.ty, self.untagged_enum_names) {
return json_read(
&accessor,
&optional_type(self.mapper.map_type(&field.ty), field),
optional
&& !crate::backends::swift::gen_rust_crate::type_bridge::field_needs_json_bridge(
&field.ty,
field.optional,
),
);
}
if needs_json_bridge_for_swift(&field.ty) {
return json_read(
&accessor,
&optional_type(swift_field_type(self.mapper, &field.ty), field),
false,
);
}
swift_ffi_read_expr(
&field.ty,
optional,
&accessor,
self.known_dto_names,
self.unit_enum_names,
self.untagged_enum_names,
self.error_type_name,
)
}
fn projected_text(&self, field: &FieldDef, accessor: &str) -> Option<String> {
let fallback = projected_read_expression(field, accessor, self.text_variants)?;
let named = match &field.ty {
TypeRef::Optional(inner) => inner.as_ref(),
other => other,
};
if self.ty.has_serde
&& !field.serde_skip
&& let TypeRef::Named(name) = named
&& self.text_variants.get(name).is_some_and(|(_, _, serde)| *serde)
{
let getter = crate::backends::swift::gen_rust_crate::text_projection::json_getter_name(&field.name);
return Some(json_read(
&getter,
&optional_type(self.mapper.map_type(&field.ty), field),
false,
));
}
Some(fallback)
}
fn serde_vector(&self, field: &FieldDef, accessor: &str, optional: bool) -> String {
if optional {
return json_read(accessor, &optional_type(self.mapper.map_type(&field.ty), field), false);
}
if let TypeRef::Vec(inner) = &field.ty
&& let TypeRef::Named(name) = inner.as_ref()
{
return crate::backends::swift::template_env::render(
"swift_serde_vector_read.swift.jinja",
crate::alef_context! { accessor => accessor, name => name },
)
.trim_end()
.to_string();
}
format!("rb.{accessor}()")
}
}
fn optional_type(swift_type: String, field: &FieldDef) -> String {
if field.optional && !matches!(field.ty, TypeRef::Optional(_)) {
format!("{swift_type}?")
} else {
swift_type
}
}
fn json_read(accessor: &str, swift_type: &str, optional_accessor: bool) -> String {
let source = if optional_accessor {
format!("rb.{accessor}()?.toString() ?? \"null\"")
} else {
format!("rb.{accessor}().toString()")
};
format!("try JSONDecoder().decode({swift_type}.self, from: (({source}).data(using: .utf8) ?? Data(\"null\".utf8)))")
}