hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
use hara_wasm::core::Value;
use hara_wasm::hta;
use hara_wasm::native_cli::RuntimeBroker;
use hara_wasm::{InvokeHtaError, Runtime};

fn arguments(values: Vec<Value>) -> Vec<u8> {
    hta::encode(&Value::Vector(values.into())).expect("canonical arguments")
}

fn number(bytes: &[u8]) -> i64 {
    let Value::Number(value) = hta::decode(bytes).expect("result HTA") else {
        panic!("expected integer result")
    };
    value
}

#[test]
fn runtime_invokes_only_an_already_loaded_qualified_var() {
    let mut runtime = Runtime::core();
    runtime
        .eval_native("(ns invoke.sample) (defn add [a b] (+ a b)) (def answer 42)")
        .expect("load sample namespace");

    let result = runtime
        .invoke_hta(
            "invoke.sample/add",
            &arguments(vec![Value::Number(20), Value::Number(22)]),
        )
        .expect("invoke add");
    assert_eq!(number(&result), 42);

    assert_eq!(
        runtime.invoke_hta("add", &arguments(vec![])),
        Err(InvokeHtaError::InvalidQualifiedVar)
    );
    assert_eq!(
        runtime.invoke_hta("missing.namespace/add", &arguments(vec![])),
        Err(InvokeHtaError::NamespaceMissing(
            "missing.namespace".to_owned()
        ))
    );
    assert_eq!(
        runtime.invoke_hta("invoke.sample/missing", &arguments(vec![])),
        Err(InvokeHtaError::VarMissing(
            "invoke.sample/missing".to_owned()
        ))
    );
    assert_eq!(
        runtime.invoke_hta("invoke.sample/answer", &arguments(vec![])),
        Err(InvokeHtaError::VarNotCallable(
            "invoke.sample/answer".to_owned()
        ))
    );
}

#[test]
fn runtime_rejects_malformed_noncanonical_and_non_vector_arguments() {
    let mut runtime = Runtime::core();
    runtime
        .eval_native("(ns invoke.input) (defn identity* [value] value)")
        .expect("load input namespace");

    assert!(matches!(
        runtime.invoke_hta("invoke.input/identity*", b"not-hta"),
        Err(InvokeHtaError::MalformedInput(_))
    ));
    assert_eq!(
        runtime.invoke_hta(
            "invoke.input/identity*",
            &hta::encode(&Value::Number(1)).expect("scalar HTA")
        ),
        Err(InvokeHtaError::ArgumentsNotVector)
    );

    let mut noncanonical = b"HTA0".to_vec();
    noncanonical.push(11);
    noncanonical.extend_from_slice(&2_u32.to_be_bytes());
    for (key, value) in [(b'z', 1_i64), (b'a', 2_i64)] {
        noncanonical.push(6);
        noncanonical.extend_from_slice(&1_u32.to_be_bytes());
        noncanonical.push(key);
        noncanonical.push(3);
        noncanonical.extend_from_slice(&value.to_be_bytes());
    }
    assert_eq!(
        runtime.invoke_hta("invoke.input/identity*", &noncanonical),
        Err(InvokeHtaError::NoncanonicalInput)
    );
}

#[test]
fn broker_keeps_invoke_hta_session_isolated() {
    let broker = RuntimeBroker::start_core().expect("broker");
    broker
        .eval("ROOT", "(ns invoke.broker) (defn value [] 1)")
        .expect("root function");
    broker.create("SECOND").expect("second session");
    broker
        .eval("SECOND", "(ns invoke.broker) (defn value [] 2)")
        .expect("second function");

    assert_eq!(
        number(
            &broker
                .invoke_hta("ROOT", "invoke.broker/value", &arguments(vec![]))
                .expect("root invoke")
        ),
        1
    );
    assert_eq!(
        number(
            &broker
                .invoke_hta("SECOND", "invoke.broker/value", &arguments(vec![]))
                .expect("second invoke")
        ),
        2
    );
    assert_eq!(
        broker.invoke_hta("MISSING", "invoke.broker/value", &arguments(vec![])),
        Err(InvokeHtaError::SessionMissing("MISSING".to_owned()))
    );
}