hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
use hara_wasm::wasm_binding::{
    import_wit, project_wit, WasmInterface, WitImportOptions, WitProjectionOptions, WitRoute,
};

const SCALAR: &str = r#"
package demo:calculator;
interface calculator {
  add: func(left: s64, right: s64) -> s64;
}
world calculator-world {
  export calculator;
}
"#;

#[test]
fn scalar_import_and_projection_are_deterministic() {
    let options = WitImportOptions {
        module: Some("calculator.wasm".into()),
        ..WitImportOptions::default()
    };
    let first = import_wit(SCALAR, "scalar.wit", &options).unwrap();
    let second = import_wit(SCALAR, "scalar.wit", &options).unwrap();
    assert_eq!(first, second);
    assert_eq!(first.route, WitRoute::DirectImport);
    let interface = WasmInterface::parse(&first.interface_source, "scalar.hal").unwrap();
    let projection = project_wit(
        &interface,
        &WitProjectionOptions {
            strict: true,
            ..WitProjectionOptions::default()
        },
    )
    .unwrap();
    assert!(projection
        .source
        .contains("add: func(left: s64, right: s64) -> s64;"));
}

#[test]
fn strict_import_lists_lossy_features() {
    let source = r#"
package demo:rich;
interface rich {
  resource stream;
  invoke: func(value: option<string>) -> result<string, string>;
}
world rich-world {
  import host;
  export rich;
}
"#;
    let error = import_wit(
        source,
        "rich.wit",
        &WitImportOptions {
            strict: true,
            ..WitImportOptions::default()
        },
    )
    .unwrap_err();
    assert!(error.starts_with("wasm-wit/strict"));
    assert!(error.contains("option"));
    assert!(error.contains("world-import"));
}

#[test]
fn imports_interface_exports_and_direct_world_functions() {
    let interface_export = r#"
package demo:calculator@1.2.3;
interface calculator {
  add: func(left: s32, right: s32) -> s32;
}
world calculator-world {
  export calculator: interface;
}
"#;
    let imported = import_wit(
        interface_export,
        "interface-export.wit",
        &WitImportOptions {
            strict: true,
            ..WitImportOptions::default()
        },
    )
    .unwrap();
    assert_eq!(imported.route, WitRoute::DirectImport);
    assert_eq!(
        WasmInterface::parse(&imported.interface_source, "interface-export.hal")
            .unwrap()
            .exports[0]
            .name,
        "add"
    );

    let world_function = r#"
package demo:calculator;
world calculator-world {
  export add: func(left: s32, right: s32) -> s32;
}
"#;
    let imported = import_wit(
        world_function,
        "world-function.wit",
        &WitImportOptions {
            strict: true,
            ..WitImportOptions::default()
        },
    )
    .unwrap();
    assert_eq!(imported.route, WitRoute::DirectImport);
    assert_eq!(
        WasmInterface::parse(&imported.interface_source, "world-function.hal")
            .unwrap()
            .exports[0]
            .name,
        "add"
    );

    let qualified_import = interface_export.replace(
        "  export calculator: interface;",
        "  import wasi:io/streams;\n  export calculator: interface;",
    );
    let imported = import_wit(
        &qualified_import,
        "qualified-import.wit",
        &WitImportOptions::default(),
    )
    .unwrap();
    assert_eq!(imported.route, WitRoute::HtaRequire);
    assert!(imported
        .diagnostics
        .iter()
        .any(|diagnostic| diagnostic.code == "world-import"));

    let malformed = [interface_export, "}"].concat();
    assert!(import_wit(
        &malformed,
        "trailing-token.wit",
        &WitImportOptions::default()
    )
    .is_err());
}

#[test]
fn default_namespace_strips_package_version_and_avoids_duplicate_name() {
    let source = r#"
package demo:calculator@1.2.3;
interface calculator {
  add: func(left: s32, right: s32) -> s32;
}
"#;
    let imported = import_wit(source, "versioned.wit", &WitImportOptions::default()).unwrap();
    assert_eq!(imported.namespace, "demo.calculator");
}

#[test]
fn rejects_unsafe_module_options() {
    for module in [
        "../calculator.wasm",
        "calculator\\module.wasm",
        "drive:calculator.wasm",
    ] {
        assert!(import_wit(
            SCALAR,
            "unsafe-module.wit",
            &WitImportOptions {
                module: Some(module.into()),
                ..WitImportOptions::default()
            }
        )
        .unwrap_err()
        .starts_with("wasm-wit/malformed"));
    }
}

#[test]
fn rejects_duplicate_wit_declarations() {
    for source in [
        "interface calculator {} interface calculator {}",
        "interface calculator { add: func(); add: func(); }",
        "interface calculator { type value = s32; type value = s64; }",
    ] {
        assert!(import_wit(source, "duplicate.wit", &WitImportOptions::default()).is_err());
    }
}

#[test]
fn reports_lossy_integer_widths_and_strictly_rejects_them() {
    let source = r#"
package demo:numbers;
interface numbers {
  take: func(value: u32);
}
"#;
    let imported = import_wit(source, "numbers.wit", &WitImportOptions::default()).unwrap();
    assert!(imported
        .diagnostics
        .iter()
        .any(|diagnostic| diagnostic.code == "integer-width"));
    assert!(import_wit(
        source,
        "numbers.wit",
        &WitImportOptions {
            strict: true,
            ..WitImportOptions::default()
        }
    )
    .is_err());
}