alef 0.110.11

Opinionated polyglot binding generator for Rust libraries
Documentation
use super::dto::swift_field_type;
use super::zero_arg_default::swift_constructor_default_literal;
use crate::backends::swift::naming::swift_source_ident;
use crate::backends::swift::type_map::SwiftMapper;
use crate::core::ir::{FieldDef, TypeDef, TypeRef};
use anyhow::Context;
use heck::ToLowerCamelCase;
use serde::Serialize;

pub(super) fn parameters(owner: &TypeDef, mapper: &SwiftMapper, fields: &[&FieldDef]) -> Vec<String> {
    fields
        .iter()
        .map(|field| {
            let name = swift_source_ident(&field.name.to_lower_camel_case());
            let already_optional = matches!(&field.ty, TypeRef::Optional(_));
            let swift_ty = swift_field_type(mapper, &field.ty);
            let optional_suffix = if field.optional && !already_optional { "?" } else { "" };
            match field_default(owner, field) {
                Some(value) => format!("{name}: {swift_ty}{optional_suffix} = {value}"),
                None => format!("{name}: {swift_ty}{optional_suffix}"),
            }
        })
        .collect()
}

fn field_default(owner: &TypeDef, field: &FieldDef) -> Option<String> {
    swift_constructor_default_literal(owner, field)
        .or_else(|| (field.optional || matches!(field.ty, TypeRef::Optional(_))).then(|| "nil".into()))
}

#[derive(Debug, Serialize)]
pub(super) struct LegacyInitializer {
    params: String,
    args: String,
}

pub(super) fn legacy_initializer(
    owner: &TypeDef,
    fields: &[&FieldDef],
    parameters: &[String],
) -> anyhow::Result<Option<LegacyInitializer>> {
    let legacy = owner.legacy_constructor_fields();
    if legacy.is_empty() || fields.iter().map(|field| &field.name).eq(legacy.iter()) {
        return Ok(None);
    }
    let params = legacy
        .iter()
        .map(|name| {
            let index = fields.iter().position(|field| &field.name == name).with_context(|| {
                format!(
                    "Swift DTO {} recorded initializer field `{name}` is unavailable",
                    owner.name
                )
            })?;
            parameters.get(index).cloned().with_context(|| {
                format!(
                    "Swift DTO {} recorded initializer parameter `{name}` is unavailable",
                    owner.name
                )
            })
        })
        .collect::<anyhow::Result<Vec<_>>>()?;
    let args = fields
        .iter()
        .map(|field| {
            let name = swift_source_ident(&field.name.to_lower_camel_case());
            let value = if legacy.contains(&field.name) {
                name.clone()
            } else {
                field_default(owner, field).with_context(|| {
                    format!(
                        "Swift DTO {} initializer extension `{}` has no representable Swift default",
                        owner.name, field.name
                    )
                })?
            };
            Ok(format!("{name}: {value}"))
        })
        .collect::<anyhow::Result<Vec<_>>>()?;
    Ok(Some(LegacyInitializer {
        params: params.join(", "),
        args: args.join(", "),
    }))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn missing_recorded_field_returns_a_contextual_error() {
        let mut owner = TypeDef {
            name: "Options".into(),
            ..Default::default()
        };
        owner.version.legacy_constructor_fields = vec!["missing".into()];
        let error = legacy_initializer(&owner, &[], &[]).unwrap_err().to_string();
        assert_eq!(
            error,
            "Swift DTO Options recorded initializer field `missing` is unavailable"
        );
    }
}