folk-runtime-embed 0.1.9

Embedded PHP runtime for Folk — PHP interpreter runs in-process via FFI
Documentation
//! Tests for direct data passing (Phase A + B).
//!
//! Phase A: raw binary via FFI pointers (no base64)
//! Phase B: array construction/reading via Zend API
//!
//! IMPORTANT: Requires PHP with embed SAPI.

use folk_runtime_embed::php::{PhpInstance, RequestContext};

#[test]
fn call_binary_round_trip() {
    let mut php = PhpInstance::boot_custom_sapi().expect("boot failed");

    let mut ctx = RequestContext::new("GET", "/");
    php.set_request_context(&mut ctx);
    php.request_startup().expect("startup failed");

    // Define a PHP function that receives binary and returns binary
    php.eval(
        r#"
        function folk_embed_dispatch(string $method, string $params): string {
            // Echo back: method + '|' + params
            return $method . '|' . $params;
        }
    "#,
    )
    .expect("define function failed");

    // Call with raw binary
    let params = b"\x01\x02\x03\x04\x00\xFF";
    let result = php
        .call_binary("folk_embed_dispatch", "test.method", params)
        .expect("call_binary failed");

    let expected = b"test.method|\x01\x02\x03\x04\x00\xFF";
    assert_eq!(result, expected);

    php.request_shutdown();
}

#[test]
fn call_binary_with_msgpack() {
    let mut php = PhpInstance::boot_custom_sapi().expect("boot failed");

    let mut ctx = RequestContext::new("GET", "/");
    php.set_request_context(&mut ctx);
    php.request_startup().expect("startup failed");

    // Define handler that uses msgpack
    php.eval(
        r#"
        function folk_embed_dispatch(string $method, string $params): string {
            // Just return the params length as msgpack-encoded integer
            $len = strlen($params);
            return msgpack_pack($len);
        }
    "#,
    )
    .expect("define function failed");

    // Send some msgpack data
    let input = rmp_serde::to_vec(&"hello world").unwrap();
    let result = php
        .call_binary("folk_embed_dispatch", "test", &input)
        .expect("call_binary failed");

    // PHP should return msgpack-encoded length
    if !result.is_empty() {
        let len: usize = rmp_serde::from_slice(&result).unwrap_or(0);
        assert_eq!(len, input.len());
    }

    php.request_shutdown();
}

#[test]
fn call_binary_empty_params() {
    let mut php = PhpInstance::boot_custom_sapi().expect("boot failed");

    let mut ctx = RequestContext::new("GET", "/");
    php.set_request_context(&mut ctx);
    php.request_startup().expect("startup failed");

    php.eval(
        r#"
        function folk_embed_dispatch(string $method, string $params): string {
            return $method;
        }
    "#,
    )
    .expect("define failed");

    let result = php
        .call_binary("folk_embed_dispatch", "ping", b"")
        .expect("call failed");
    assert_eq!(result, b"ping");

    php.request_shutdown();
}

#[test]
fn call_binary_large_payload() {
    let mut php = PhpInstance::boot_custom_sapi().expect("boot failed");

    let mut ctx = RequestContext::new("GET", "/");
    php.set_request_context(&mut ctx);
    php.request_startup().expect("startup failed");

    php.eval(
        r#"
        function folk_embed_dispatch(string $method, string $params): string {
            return $params; // echo back
        }
    "#,
    )
    .expect("define failed");

    // 1MB payload
    let payload = vec![0xABu8; 1_000_000];
    let result = php
        .call_binary("folk_embed_dispatch", "large", &payload)
        .expect("call failed");
    assert_eq!(result.len(), 1_000_000);
    assert!(result.iter().all(|&b| b == 0xAB));

    php.request_shutdown();
}

#[test]
fn call_binary_many_requests() {
    let mut php = PhpInstance::boot_custom_sapi().expect("boot failed");

    let mut ctx = RequestContext::new("GET", "/");
    php.set_request_context(&mut ctx);
    php.request_startup().expect("startup failed");

    php.eval(
        r#"
        function folk_embed_dispatch(string $method, string $params): string {
            return strrev($params);
        }
    "#,
    )
    .expect("define failed");

    php.request_shutdown();

    // Run many requests
    for i in 0..1000 {
        let mut ctx = RequestContext::new("GET", "/");
        php.set_request_context(&mut ctx);
        php.request_startup().expect("startup failed");

        let data = format!("request-{i}");
        let result = php
            .call_binary("folk_embed_dispatch", "test", data.as_bytes())
            .expect("call failed");
        let expected: String = data.chars().rev().collect();
        assert_eq!(result, expected.as_bytes());

        php.request_shutdown();
    }
}