hara_native/
package_wasm_loader.rs1#![cfg(not(target_arch = "wasm32"))]
8
9use std::fs;
10use std::path::Path;
11
12use crate::extension::{ExtensionManifest, WasmAbi, WasmExtension};
13use crate::package_manifest::{
14 PackageArtifactType, PackageManifest, PackageRuntimeRequirements, PackageSelection,
15};
16use crate::wasmtime_provider::WasmtimeExtensionProvider;
17
18pub struct LoadedPackageWasm {
19 pub identity: String,
20 pub entry_point: String,
21 pub extension: WasmExtension,
22}
23
24pub fn load_wasm_package(
25 manifest: &PackageManifest,
26 package_root: &Path,
27 requirements: &PackageRuntimeRequirements,
28 extension_manifest_source: &str,
29) -> Result<LoadedPackageWasm, String> {
30 let module = manifest
31 .wasm_imports
32 .keys()
33 .next()
34 .cloned()
35 .ok_or_else(|| {
36 "package/missing-wasm-import: package declares no Wasm imports".to_owned()
37 })?;
38 load_wasm_import_package(
39 manifest,
40 package_root,
41 &module,
42 requirements,
43 extension_manifest_source,
44 )
45}
46
47pub fn load_wasm_import_package(
48 manifest: &PackageManifest,
49 package_root: &Path,
50 module: &str,
51 requirements: &PackageRuntimeRequirements,
52 extension_manifest_source: &str,
53) -> Result<LoadedPackageWasm, String> {
54 manifest
55 .verify_files_at(package_root)
56 .map_err(|error| error.to_string())?;
57 let selection = manifest
58 .select_wasm_import(module, requirements)
59 .map_err(|error| error.to_string())?;
60 let PackageSelection::Variant(variant) = &selection else {
61 return Err("package/missing-artifact: portable package has no Wasm artifact".into());
62 };
63 if variant.artifact.artifact_type != PackageArtifactType::Wasm {
64 return Err(format!(
65 "package/artifact-type-mismatch: expected :wasm, got :{}",
66 variant.artifact.artifact_type.keyword()
67 ));
68 }
69 if variant.artifact.abi != "core.v1" && variant.artifact.abi != "memory.v1" {
70 return Err(format!(
71 "package/abi-mismatch: direct Wasm loader does not support {}",
72 variant.artifact.abi
73 ));
74 }
75
76 let artifact_path = package_root.join(&variant.artifact.path);
77 let bytes = fs::read(&artifact_path).map_err(|error| {
78 format!(
79 "package/missing-artifact: cannot read {}: {error}",
80 variant.artifact.path.display()
81 )
82 })?;
83 manifest
84 .verify_artifact_bytes(&selection, &bytes)
85 .map_err(|error| error.to_string())?;
86
87 let extension_manifest = ExtensionManifest::parse(extension_manifest_source, "package")?;
88 if extension_manifest.identity.as_deref() != Some(manifest.identity.as_str()) {
89 return Err("package/identity-mismatch: extension identity differs from package".into());
90 }
91 let expected_abi = match variant.artifact.abi.as_str() {
92 "core.v1" => WasmAbi::CoreV1,
93 "memory.v1" => WasmAbi::MemoryV1,
94 _ => unreachable!(),
95 };
96 if extension_manifest.abi != expected_abi {
97 return Err(
98 "package/abi-mismatch: extension manifest differs from selected variant".into(),
99 );
100 }
101 if extension_manifest.provider != "wasm" {
102 return Err(
103 "package/provider-mismatch: direct Wasm artifact requires :provider :wasm".into(),
104 );
105 }
106 if !variant.required_capabilities.iter().all(|capability| {
107 extension_manifest
108 .capabilities
109 .iter()
110 .any(|declared| declared == capability)
111 }) {
112 return Err(
113 "package/manifest-mismatch: required capabilities differ from extension".into(),
114 );
115 }
116 if !variant.host_calls.is_empty() || !extension_manifest.host_calls.is_empty() {
117 return Err(
118 "package/host-call-denied: direct Wasm packages cannot request host calls".into(),
119 );
120 }
121 if !variant.exports.iter().all(|export| {
122 extension_manifest
123 .exports
124 .iter()
125 .any(|(declared, _)| declared == export)
126 }) {
127 return Err(
128 "package/manifest-mismatch: selected exports are not declared by extension".into(),
129 );
130 }
131 if !extension_manifest.exports.iter().any(|(name, spec)| {
132 name == &variant.artifact.entry_point || spec.raw_name(name) == variant.artifact.entry_point
133 }) {
134 return Err("package/entry-point-mismatch: selected entry point is not exported".into());
135 }
136
137 let provider = match expected_abi {
138 WasmAbi::CoreV1 => WasmtimeExtensionProvider::compile(&bytes)?,
139 WasmAbi::MemoryV1 => {
140 return Err(
141 "package/binding-plan-required: memory.v1 package loading requires verified bindings.edn"
142 .into(),
143 )
144 }
145 WasmAbi::HtaV1 => unreachable!(),
146 };
147 let extension = WasmExtension::new(extension_manifest, provider)?;
148 Ok(LoadedPackageWasm {
149 identity: manifest.identity.clone(),
150 entry_point: variant.artifact.entry_point.clone(),
151 extension,
152 })
153}