alef 0.109.6

Opinionated polyglot binding generator for Rust libraries
Documentation
#[cfg(test)]
mod transparent_string_tests {
    use super::*;
    use crate::core::ir::{NewtypeContainer, NewtypeWrapper, NewtypeWrapperMetadata};

    fn config() -> crate::core::config::ResolvedCrateConfig {
        let raw: crate::core::config::NewAlefConfig = toml::from_str(
            r#"
[workspace]
languages = ["kotlin_android", "jni"]

[[crates]]
name = "demo"
sources = ["src/lib.rs"]

[crates.kotlin_android]
package = "dev.demo"
"#,
        )
        .expect("fixture config parses");
        raw.resolve().expect("fixture config resolves").remove(0)
    }

    fn wrapper(containers: Vec<NewtypeContainer>) -> String {
        wrapper_with_path("demo::SecretString", containers)
    }

    fn wrapper_with_path(path: &str, containers: Vec<NewtypeContainer>) -> String {
        wrapper_with_constructor(path, "from", containers)
    }

    fn wrapper_with_constructor(path: &str, constructor: &str, containers: Vec<NewtypeContainer>) -> String {
        NewtypeWrapper::encode_explicit(&[NewtypeWrapperMetadata::transparent_string(
            path,
            constructor,
            "into_inner",
            containers,
        )])
    }

    fn secret_param(optional: bool, is_ref: bool) -> ParamDef {
        ParamDef {
            name: "value".to_string(),
            ty: TypeRef::String,
            optional,
            is_ref,
            newtype_wrapper: Some(wrapper(if optional {
                vec![NewtypeContainer::Optional]
            } else {
                Vec::new()
            })),
            ..Default::default()
        }
    }

    fn secret_function(name: &str, optional: bool, is_ref: bool) -> crate::core::ir::FunctionDef {
        crate::core::ir::FunctionDef {
            name: name.to_string(),
            rust_path: format!("demo::{name}"),
            params: vec![secret_param(optional, is_ref)],
            return_type: if optional {
                TypeRef::Optional(Box::new(TypeRef::String))
            } else {
                TypeRef::String
            },
            return_newtype_wrapper: Some(wrapper(if optional {
                vec![NewtypeContainer::Optional]
            } else {
                Vec::new()
            })),
            ..Default::default()
        }
    }

    fn api_with_functions(functions: Vec<crate::core::ir::FunctionDef>) -> ApiSurface {
        ApiSurface {
            crate_name: "demo".to_string(),
            functions,
            ..Default::default()
        }
    }

    #[test]
    fn free_functions_apply_explicit_wrapper_operations_for_all_call_shapes() {
        let mut async_secret = secret_function("async_secret", false, false);
        async_secret.is_async = true;
        let mut fallible_secret = secret_function("fallible_secret", false, false);
        fallible_secret.error_type = Some("SecretError".to_string());
        let content = emit_lib_rs(
            &api_with_functions(vec![
                secret_function("echo_secret", false, false),
                secret_function("borrow_secret", false, true),
                secret_function("optional_secret", true, false),
                secret_function("borrow_optional_secret", true, true),
                async_secret,
                fallible_secret,
            ]),
            &config(),
        );

        assert!(
            content.contains("core_crate::echo_secret(core_crate::SecretString::from(value))"),
            "owned input must construct the wrapper: {content}"
        );
        assert!(
            content.contains("let value_newtype = core_crate::SecretString::from(value);")
                && content.contains("core_crate::borrow_secret(&value_newtype)"),
            "borrowed input must keep an owned wrapper alive for the call: {content}"
        );
        assert!(
            content.contains("core_crate::optional_secret((if value.is_empty() { None } else { Some(value) }).map(core_crate::SecretString::from))"),
            "optional input must convert only Some: {content}"
        );
        assert!(
            content.contains("let value_newtype = (if value.is_empty() { None } else { Some(value) }).map(core_crate::SecretString::from);")
                && content.contains("core_crate::borrow_optional_secret(value_newtype.as_ref())"),
            "optional borrowed input must convert before borrowing: {content}"
        );
        assert!(
            !content.contains("map(|value| core_crate::SecretString::from(value))"),
            "root optional wrappers must not emit Clippy's redundant-closure shape: {content}"
        );
        assert!(
            content.contains("runtime().block_on(core_crate::async_secret(core_crate::SecretString::from(value)))"),
            "async input must construct the wrapper before awaiting: {content}"
        );
        assert!(
            content.contains("core_crate::fallible_secret(core_crate::SecretString::from(value))"),
            "fallible input must construct the wrapper before matching Result: {content}"
        );
        assert_eq!(
            content.matches("let v = (v).into_inner();").count(),
            4,
            "owned, borrowed-input, async, and Result-ok returns must use the explicit extractor: {content}"
        );
        assert!(
            content.contains("let v = (v).map(|value| (value).into_inner());"),
            "optional return must convert only Some: {content}"
        );
        assert!(
            !content.contains("v.to_string()"),
            "wrapper return must not require Display"
        );
        assert!(
            !content.contains("v.as_ref()"),
            "wrapper return must not require AsRef<str>"
        );
        syn::parse_file(&content).expect("generated JNI crate parses");
    }

    #[test]
    fn optional_param_uses_the_configured_constructor_as_a_function_item() {
        let function = crate::core::ir::FunctionDef {
            name: "optional_secret".to_string(),
            rust_path: "demo::optional_secret".to_string(),
            params: vec![ParamDef {
                name: "value".to_string(),
                ty: TypeRef::String,
                optional: true,
                newtype_wrapper: Some(wrapper_with_constructor(
                    "demo::SecretString",
                    "new_secret",
                    vec![NewtypeContainer::Optional],
                )),
                ..Default::default()
            }],
            ..Default::default()
        };

        let content = emit_lib_rs(&api_with_functions(vec![function]), &config());

        assert!(
            content.contains(".map(core_crate::SecretString::new_secret)"),
            "configured constructor must be emitted directly: {content}"
        );
        assert!(
            !content.contains("map(|value| core_crate::SecretString::new_secret(value))"),
            "configured constructor must not be wrapped in a redundant closure: {content}"
        );
    }

    #[test]
    fn optional_map_function_converts_wrapped_values_in_both_directions() {
        let map_type = TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String));
        let metadata = wrapper(vec![NewtypeContainer::Optional, NewtypeContainer::MapValue]);
        let function = crate::core::ir::FunctionDef {
            name: "optional_secret_map".to_string(),
            rust_path: "demo::optional_secret_map".to_string(),
            params: vec![ParamDef {
                name: "value".to_string(),
                ty: map_type.clone(),
                optional: true,
                newtype_wrapper: Some(metadata.clone()),
                ..Default::default()
            }],
            return_type: TypeRef::Optional(Box::new(map_type)),
            return_newtype_wrapper: Some(metadata),
            ..Default::default()
        };

        let content = emit_lib_rs(&api_with_functions(vec![function]), &config());

        assert!(
            content.contains("core_crate::optional_secret_map((value).map(|value| (value).into_iter().map(|(key, value)| (key, core_crate::SecretString::from(value))).collect()))"),
            "optional map input must wrap every value: {content}"
        );
        assert!(
            content.contains("let v = (v).map(|value| (value).into_iter().map(|(key, value)| (key, (value).into_inner())).collect::<std::collections::HashMap<_, _>>());"),
            "optional map return must unwrap every value: {content}"
        );
        syn::parse_file(&content).expect("generated JNI crate parses");
    }

    #[test]
    fn instance_methods_apply_explicit_wrapper_operations() {
        let mut replace = crate::core::ir::MethodDef {
            name: "replace_secret".to_string(),
            params: vec![secret_param(false, false)],
            return_type: TypeRef::String,
            return_newtype_wrapper: Some(wrapper(Vec::new())),
            receiver: Some(crate::core::ir::ReceiverKind::RefMut),
            ..Default::default()
        };
        replace.error_type = Some("SecretError".to_string());
        let optional_map_type = TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String));
        let optional_map_wrapper = wrapper(vec![NewtypeContainer::Optional, NewtypeContainer::MapValue]);
        let optional_map = crate::core::ir::MethodDef {
            name: "optional_secret_map".to_string(),
            params: vec![ParamDef {
                name: "value".to_string(),
                ty: optional_map_type.clone(),
                optional: true,
                is_ref: true,
                newtype_wrapper: Some(optional_map_wrapper.clone()),
                ..Default::default()
            }],
            return_type: TypeRef::Optional(Box::new(optional_map_type)),
            return_newtype_wrapper: Some(optional_map_wrapper),
            receiver: Some(crate::core::ir::ReceiverKind::Ref),
            ..Default::default()
        };
        let api = ApiSurface {
            crate_name: "demo".to_string(),
            types: vec![crate::core::ir::TypeDef {
                name: "Vault".to_string(),
                rust_path: "demo::Vault".to_string(),
                is_opaque: true,
                methods: vec![replace, optional_map],
                ..Default::default()
            }],
            ..Default::default()
        };

        let content = emit_lib_rs(&api, &config());

        assert!(
            content.contains("client.replace_secret(core_crate::SecretString::from(value))"),
            "method input must construct the wrapper: {content}"
        );
        assert!(
            content.contains("let v = (v).into_inner();"),
            "method Result-ok return must use the explicit extractor: {content}"
        );
        assert!(
            content.contains("client.optional_secret_map(value_newtype.as_ref())"),
            "optional borrowed method input must convert before borrowing: {content}"
        );
        assert!(
            content.contains("let v = (v).map(|value| (value).into_iter().map(|(key, value)| (key, (value).into_inner())).collect::<std::collections::HashMap<_, _>>());"),
            "optional method return must unwrap every value: {content}"
        );
        assert!(
            !content.contains("v.to_string()"),
            "wrapper return must not require Display"
        );
        assert!(
            !content.contains("v.as_ref()"),
            "wrapper return must not require AsRef<str>"
        );
        syn::parse_file(&content).expect("generated JNI crate parses");
    }

    #[test]
    fn value_methods_apply_explicit_wrapper_operations() {
        let method = crate::core::ir::MethodDef {
            name: "borrow_secret".to_string(),
            params: vec![secret_param(false, true)],
            return_type: TypeRef::String,
            return_newtype_wrapper: Some(wrapper(Vec::new())),
            receiver: Some(crate::core::ir::ReceiverKind::Ref),
            ..Default::default()
        };
        let symbol = "Java_dev_demo_DemoBridge_nativeSegmentBorrowSecret";
        let mut content = emit_jni_lib_header(&ApiSurface::default(), &config(), "dev.demo");
        emit_value_method_shim(&mut content, symbol, "Segment", &method);

        assert!(
            content.contains(symbol),
            "value-method shim must be present, not only JNI boilerplate: {content}"
        );
        assert!(
            content.contains("let value_newtype = core_crate::SecretString::from(value);")
                && content.contains("client.borrow_secret(&value_newtype)"),
            "value-method input must convert before borrowing: {content}"
        );
        assert!(
            content.contains("let v = (v).into_inner();"),
            "value-method return must use the explicit extractor: {content}"
        );
        syn::parse_file(&content).expect("generated JNI crate parses");
    }

    #[test]
    fn value_method_optional_borrowed_map_converts_wrapped_values() {
        let map_type = TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String));
        let metadata = wrapper(vec![NewtypeContainer::Optional, NewtypeContainer::MapValue]);
        let method = crate::core::ir::MethodDef {
            name: "optional_secret_map".to_string(),
            params: vec![ParamDef {
                name: "value".to_string(),
                ty: map_type.clone(),
                optional: true,
                is_ref: true,
                newtype_wrapper: Some(metadata.clone()),
                ..Default::default()
            }],
            return_type: TypeRef::Optional(Box::new(map_type)),
            return_newtype_wrapper: Some(metadata),
            receiver: Some(crate::core::ir::ReceiverKind::Ref),
            ..Default::default()
        };
        let mut content = emit_jni_lib_header(&ApiSurface::default(), &config(), "dev.demo");
        emit_value_method_shim(
            &mut content,
            "Java_dev_demo_DemoBridge_nativeSegmentOptionalSecretMap",
            "Segment",
            &method,
        );

        assert!(
            content.contains(
                "let value: Option<std::collections::HashMap<String, String>> = match req_map.get(\"value\")"
            ),
            "value-method optional map must deserialize as Option<HashMap>: {content}"
        );
        assert!(
            content.contains("let value_newtype = (value).map(|value| (value).into_iter().map(|(key, value)| (key, core_crate::SecretString::from(value))).collect());")
                && content.contains("client.optional_secret_map(value_newtype.as_ref())"),
            "value-method optional map must convert before borrowing: {content}"
        );
        assert!(
            content.contains("let v = (v).map(|value| (value).into_iter().map(|(key, value)| (key, (value).into_inner())).collect::<std::collections::HashMap<_, _>>());"),
            "value-method optional map return must unwrap every value: {content}"
        );
        syn::parse_file(&content).expect("generated JNI crate parses");
    }

    #[test]
    fn nested_wrapper_paths_keep_the_module_suffix_under_the_core_alias() {
        let encoded = wrapper_with_path("demo::auth::SecretString", Vec::new());
        let remapped = jni_newtype_wrapper(&encoded);
        let decoded = NewtypeWrapper::decode(&remapped).expect("remapped metadata decodes");

        assert_eq!(decoded.explicit_paths()[0].rust_path, "core_crate::auth::SecretString");
    }
}