alef 0.115.2

Opinionated polyglot binding generator for Rust libraries
Documentation
use crate::codegen::naming::{PublicIdentifierKind, internal_rust_identifier, public_host_identifier};
use crate::core::config::Language;
use crate::core::ir::{ErrorDef, ErrorVariant, FieldDef, TypeRef};

pub(super) fn typed_error_definition<'a>(error_type: Option<&str>, errors: &'a [ErrorDef]) -> Option<&'a ErrorDef> {
    let name = error_type?;
    errors.iter().find(|error| {
        !error.binding_excluded
            && [&error.rust_path, &error.original_rust_path, &error.name]
                .iter()
                .any(|path| path.as_str() == name || path.replace('-', "_") == name)
    })
}

pub(super) fn uses_typed_error(error_type: Option<&str>, errors: &[ErrorDef]) -> bool {
    typed_error_definition(error_type, errors).is_some()
}

pub(super) fn core_error_path(error: &ErrorDef, source_crate: &str) -> String {
    if error.rust_path.is_empty() {
        format!("{source_crate}::{}", error.name)
    } else {
        error.rust_path.replace('-', "_")
    }
}

pub(super) fn result_error_type(error_type: Option<&str>, errors: &[ErrorDef]) -> &'static str {
    if uses_typed_error(error_type, errors) {
        "AlefBridgeError"
    } else {
        "String"
    }
}

pub(super) fn error_mapper(error_type: Option<&str>, errors: &[ErrorDef]) -> &'static str {
    if uses_typed_error(error_type, errors) {
        "AlefBridgeError::from"
    } else {
        "|e| e.to_string()"
    }
}

pub(super) fn emit(out: &mut String, errors: &[ErrorDef], source_crate: &str) {
    let errors = errors
        .iter()
        .filter(|error| !error.binding_excluded)
        .collect::<Vec<_>>();
    if errors.is_empty() {
        return;
    }
    let invalid_input = carrier_name("InvalidInput", &errors);
    let native_failure = carrier_name("NativeFailure", &errors);
    let mut decode_arms = Vec::new();
    let mut encode_arms = Vec::new();
    let entries = errors.iter().map(|error| {
        let dart_name = public_host_identifier(Language::Dart, PublicIdentifierKind::Type, &error.name);
        let dart_factory = public_host_identifier(Language::Dart, PublicIdentifierKind::Method, &error.name);
        decode_arms.push(format!("AlefBridgeErrorWire_{dart_name}(:final error) => error"));
        encode_arms.push(format!("{dart_name} error => AlefBridgeErrorWire.{dart_factory}(error: error)"));
        crate::alef_context! {
            name => &error.name,
            core_path => core_error_path(error, source_crate),
            needs_message => error.variants.iter().any(|variant| variant.is_tuple && variant.fields.iter().all(|field| field.binding_excluded)),
            arms => error.variants.iter().map(|variant| variant_arm(error, variant, source_crate)).collect::<Vec<_>>(),
        }
    }).collect::<Vec<_>>();
    for (carrier, exception, message) in [
        (&invalid_input, "ArgumentError", "error.message.toString()"),
        (&native_failure, "StateError", "error.message"),
    ] {
        let dart_name = public_host_identifier(Language::Dart, PublicIdentifierKind::Type, carrier);
        let dart_factory = public_host_identifier(Language::Dart, PublicIdentifierKind::Method, carrier);
        decode_arms.push(format!(
            "AlefBridgeErrorWire_{dart_name}(:final message) => {exception}(message)"
        ));
        encode_arms.push(format!(
            "{exception} error => AlefBridgeErrorWire.{dart_factory}(message: {message})"
        ));
    }
    encode_arms.push("_ => throw ArgumentError.value({}, 'error', 'Unsupported bridge error')".into());
    out.push_str(&crate::backends::dart::error_display::metadata_comments(&errors));
    out.push_str(&crate::backends::dart::template_env::render(
        "rust_typed_error_bridge.jinja",
        crate::alef_context! {
            errors => entries,
            invalid_input => invalid_input,
            native_failure => native_failure,
            decode_code => format!("{:?}", format!("switch ({{}}) {{ {} }}", decode_arms.join(", "))),
            encode_code => format!("{:?}", format!("switch ({{}}) {{ {} }}", encode_arms.join(", "))),
        },
    ));
}

fn carrier_name(preferred: &str, errors: &[&ErrorDef]) -> String {
    let mut name = preferred.to_string();
    let mut suffix = 0;
    while errors.iter().any(|error| {
        public_host_identifier(Language::Dart, PublicIdentifierKind::Type, &error.name)
            == public_host_identifier(Language::Dart, PublicIdentifierKind::Type, &name)
    }) {
        suffix += 1;
        name = format!("{preferred}{suffix}");
    }
    name
}

fn variant_arm(error: &ErrorDef, variant: &ErrorVariant, source_crate: &str) -> minijinja::Value {
    let core_path = core_error_path(error, source_crate);
    crate::alef_context! {
        pattern => variant_pattern(&core_path, variant),
        variant => &variant.name,
        fields => variant_fields(error, variant, source_crate),
    }
}

fn variant_pattern(core_path: &str, variant: &ErrorVariant) -> String {
    if variant.is_unit {
        return format!("{core_path}::{}", variant.name);
    }
    if variant.is_tuple {
        let bindings = variant
            .fields
            .iter()
            .enumerate()
            .map(|(index, field)| {
                if field.binding_excluded {
                    "_".to_string()
                } else {
                    format!("field{index}")
                }
            })
            .collect::<Vec<_>>();
        return format!("{core_path}::{}({})", variant.name, bindings.join(", "));
    }
    let bindings = variant
        .fields
        .iter()
        .filter(|field| !field.binding_excluded)
        .map(|field| internal_rust_identifier(&field.name))
        .collect::<Vec<_>>();
    let bindings = if bindings.is_empty() {
        String::new()
    } else {
        format!("{},", bindings.join(", "))
    };
    format!("{core_path}::{} {{ {bindings} .. }}", variant.name)
}

fn variant_fields(error: &ErrorDef, variant: &ErrorVariant, source_crate: &str) -> Vec<minijinja::Value> {
    let mut fields = super::error_fields::visible(variant)
        .enumerate().map(|(visible_index, (index, field))| {
            let binding = if variant.is_tuple { format!("field{index}") } else { internal_rust_identifier(&field.name) };
            let mut field = field.clone();
            if variant.is_tuple { field.optional = false; }
            crate::alef_context! {
                name => if variant.is_tuple { format!("field{visible_index}") } else { internal_rust_identifier(&field.name) },
                value => field_expression(&binding, &field, source_crate, &super::mirror::static_text_module_name(error)),
            }
        }).collect::<Vec<_>>();
    if variant.is_tuple && fields.is_empty() {
        fields.push(crate::alef_context! { name => "field0", value => "message" });
    }
    fields
}

fn optional_field_expression(
    binding: &str,
    flatten: &str,
    field: &FieldDef,
    source_crate: &str,
    static_text_module: &str,
) -> String {
    let mapper = if !field.sanitized
        && field.ty == TypeRef::String
        && field.core_wrapper == crate::core::ir::CoreWrapper::StaticStr
    {
        format!("{static_text_module}::register")
    } else {
        format!(
            "|value| {}",
            field_expression("value", field, source_crate, static_text_module)
        )
    };
    format!("{binding}{flatten}.map({mapper})")
}

fn field_expression(binding: &str, field: &FieldDef, source_crate: &str, static_text_module: &str) -> String {
    if let TypeRef::Optional(inner_ty) = &field.ty {
        let inner = FieldDef {
            ty: *inner_ty.clone(),
            optional: false,
            ..field.clone()
        };
        let flatten = if field.optional { ".flatten()" } else { "" };
        return optional_field_expression(binding, flatten, &inner, source_crate, static_text_module);
    }
    if field.optional {
        let inner = FieldDef {
            optional: false,
            ..field.clone()
        };
        return optional_field_expression(binding, "", &inner, source_crate, static_text_module);
    }
    if let TypeRef::Duration = field.ty {
        return format!("{binding}.as_millis() as i64");
    }
    if field.sanitized {
        return format!("{binding}.to_string()");
    }
    if field.ty == TypeRef::String && field.core_wrapper == crate::core::ir::CoreWrapper::StaticStr {
        return format!("{static_text_module}::register({binding})");
    }
    if field.ty == TypeRef::String {
        return format!("{binding}.into()");
    }
    super::enum_fields::enum_variant_field_conv(binding, field, source_crate)
}