alef 0.58.3

Opinionated polyglot binding generator for Rust libraries
Documentation
// Shims for instance methods on *value* types — the non-opaque structs the
// Kotlin/Android binding materialises as `data class`es.
//
// Unlike client shims there is no handle to cast: the Kotlin object already
// holds every field, so it serialises `this` and the shim rebuilds the core
// receiver with serde before delegating to the real core method. That keeps
// semantics the Kotlin side cannot reproduce — value clamping in the
// `PaddleOcrConfig::with_*` builders, `validate()`'s error text — in exactly
// one place.
//
// Parameters always arrive as a JSON object keyed by parameter name. The
// single-parameter shorthands used by [`emit_method_shim`] are deliberately not
// reused here so the Kotlin caller has one encoding to implement.

/// Emit a value-method shim for every bridgeable instance method on `ty`.
fn emit_value_type_shims(
    out: &mut String,
    ty: &TypeDef,
    package: &str,
    bridge: &str,
    serde_type_names: &std::collections::HashSet<&str>,
) {
    for method in bridgeable_value_methods(ty, serde_type_names) {
        let symbol = jni_symbol(package, bridge, &bridge_method_name(&ty.name, &method.name));
        emit_value_method_shim(out, &symbol, &ty.name, method);
    }
}

fn emit_value_method_shim(out: &mut String, symbol: &str, type_name: &str, method: &MethodDef) {
    let rust_method = method.name.replace('-', "_");
    let returns_receiver = is_functional_ref_mut_value_method(method);
    let return_type = value_method_return_type(type_name, method);
    let ret_decl = method_return_type_decl(&return_type);
    let ret_null = method_return_null(&return_type);

    let request_param = if method.params.is_empty() {
        String::new()
    } else {
        "    request_json: JString,\n".to_string()
    };

    out.push_str(&template_env::render(
        "value_method_shim_open.rs.jinja",
        context! {
            symbol => symbol,
            request_param => request_param,
            ret_decl => ret_decl,
        },
    ));

    out.push_str(&template_env::render(
        "value_method_receiver.rs.jinja",
        context! {
            type_name => type_name,
            ret_null => ret_null,
        },
    ));

    let call_args = emit_value_param_unmarshal(out, &method.params, ret_null);

    let call_expr = if returns_receiver {
        format!("{{ client.{rust_method}({call_args}); client }}")
    } else {
        format!("client.{rust_method}({call_args})")
    };

    if method.error_type.is_some() {
        let mut ok_body = String::new();
        emit_return_marshal(&mut ok_body, &return_type, ret_null);
        render_call_result_body(out, &call_expr, false, true, ret_null, &ok_body, "");
    } else {
        let mut value_body = String::new();
        emit_return_marshal_with_indent(&mut value_body, &return_type, "    ", ret_null);
        render_call_result_body(out, &call_expr, false, false, ret_null, "", &value_body);
    }
}

/// Unmarshal every parameter out of the `request_json` object and return the
/// comma-joined call-site argument list.
fn emit_value_param_unmarshal(out: &mut String, params: &[ParamDef], ret_null: &str) -> String {
    if params.is_empty() {
        return String::new();
    }
    out.push_str(&template_env::render(
        "request_map_unmarshal.rs.jinja",
        context! {
            ret_null => ret_null,
        },
    ));

    let mut args = Vec::with_capacity(params.len());
    for param in params {
        let rust_name = param.name.replace('-', "_");
        let is_path = matches!(&param.ty, TypeRef::Path);
        let type_path = if is_path {
            "String".to_string()
        } else {
            type_ref_to_core_path_with_btree(&param.ty, "core_crate", param.map_is_btree)
        };
        out.push_str(&template_env::render(
            "request_map_param_unmarshal.rs.jinja",
            context! {
                name => rust_name,
                type_path => type_path,
                ret_null => ret_null,
            },
        ));
        if is_path {
            out.push_str(&format!(
                "    let {rust_name} = std::path::PathBuf::from({rust_name});\n"
            ));
        }
        if needs_vec_string_refs(param, &param.ty) {
            out.push_str(&render_vec_string_refs_binding(&rust_name));
            args.push(vec_string_refs_arg(&rust_name));
        } else if param.is_ref {
            args.push(format!("&{rust_name}"));
        } else {
            args.push(rust_name);
        }
    }
    args.join(", ")
}