alef 0.48.15

Opinionated polyglot binding generator for Rust libraries
Documentation
// SAFETY: PHP objects are single-threaded; method calls are safe within a request.
let result = unsafe { (*self.inner).try_call_method("{{ method_name }}", {{ args_expr }}) };
{% if is_result_type %}
match result {
{% if is_unit_return %}
    Ok(_) => Ok(()),
{% elif is_primitive_return %}
    Ok(val) => {
        // For primitives, extract directly from Zval without JSON parsing
{% if return_type == "bool" %}
        let int_val = val.long().unwrap_or_default() != 0;
{% else %}
        let int_val = val.long().unwrap_or_default() as {{ return_type }};
{% endif %}
        Ok(int_val)
    }
{% else %}
    Ok(val) => {
{%- if native_return_binding %}
        // Native-object return: extract the host's native `#[php_class]` object and convert via
        // `From<{{ native_return_binding }}>` for core, falling back to the array/JSON path.
        if let Some(native) = <&{{ native_return_binding }} as ext_php_rs::convert::FromZval>::from_zval(&val) {
            Ok(native.clone().into())
        } else {
            let json_str = val.string().unwrap_or_default();
            serde_json::from_str(&json_str).map_err(|e| {{ deserialize_error_expr }})
        }
{%- elif native_decodable %}
        // The return type is php-native (String/primitive/Vec of those, recursively) — decode
        // the PHP array/scalar directly via `FromZval`, no JSON round-trip needed (#1304).
        <{{ return_type }} as ext_php_rs::convert::FromZval>::from_zval(&val)
            .ok_or_else(|| {{ native_decode_error_expr }})
{%- else %}
        let json_str = val.string().unwrap_or_default();
        serde_json::from_str(&json_str).map_err(|e| {{ deserialize_error_expr }})
{%- endif %}
    }
{% endif %}
    Err(e) => Err({{ call_error_expr }}),
}
{% else %}
match result {
{% if is_unit_return %}
    Ok(_) => (),
{% elif is_primitive_return %}
    Ok(val) => match val.long() {
{% if return_type == "bool" %}
        Some(v) => v != 0,
{% else %}
        Some(v) => v as {{ return_type }},
{% endif %}
        None => {
            tracing::warn!(wrapper = "{{ wrapper }}", method = "{{ method_name }}", "host returned a non-integer value; returning default");
            Default::default()
        }
    },
{% else %}
    Ok(val) => {
{%- if native_return_binding %}
        if let Some(native) = <&{{ native_return_binding }} as ext_php_rs::convert::FromZval>::from_zval(&val) {
            native.clone().into()
        } else {
            match val.string() {
                Some(json_str) => match serde_json::from_str(&json_str) {
                    Ok(v) => v,
                    Err(e) => {
                        tracing::warn!(wrapper = "{{ wrapper }}", method = "{{ method_name }}", error = %e, "host returned an unparseable value; returning default");
                        Default::default()
                    }
                },
                None => {
                    tracing::warn!(wrapper = "{{ wrapper }}", method = "{{ method_name }}", "host returned a non-string value; returning default");
                    Default::default()
                }
            }
        }
{%- elif native_decodable %}
        // The return type is php-native (String/primitive/Vec of those, recursively) — decode
        // the PHP array/scalar directly via `FromZval`, no JSON round-trip needed (#1304).
        match <{{ return_type }} as ext_php_rs::convert::FromZval>::from_zval(&val) {
            Some(v) => v,
            None => {
                tracing::warn!(wrapper = "{{ wrapper }}", method = "{{ method_name }}", "host returned an undecodable value; returning default");
                Default::default()
            }
        }
{%- else %}
        match val.string() {
            Some(json_str) => match serde_json::from_str(&json_str) {
                Ok(v) => v,
                Err(e) => {
                    tracing::warn!(wrapper = "{{ wrapper }}", method = "{{ method_name }}", error = %e, "host returned an unparseable value; returning default");
                    Default::default()
                }
            },
            None => {
                tracing::warn!(wrapper = "{{ wrapper }}", method = "{{ method_name }}", "host returned a non-string value; returning default");
                Default::default()
            }
        }
{%- endif %}
    }
{% endif %}
    // This method's Rust signature is infallible, so a PHP exception cannot
    // propagate. Log before substituting the default — a silent default is
    // indistinguishable from a real result to the caller.
    Err(e) => {
        tracing::warn!(wrapper = "{{ wrapper }}", method = "{{ method_name }}", error = %e, "host callback threw; {% if is_unit_return %}ignoring{% else %}returning default{% endif %}");
        Default::default()
    }
}
{% endif %}