hara_native/
package_hta_loader.rs1#![cfg(not(target_arch = "wasm32"))]
8
9use std::collections::BTreeSet;
10use std::fs;
11use std::path::Path;
12use std::rc::Rc;
13
14use crate::extension::{ExtensionManifest, Value, WasmAbi, WasmExtension};
15use crate::package_manifest::{
16 PackageArtifactType, PackageManifest, PackageRuntimeRequirements, PackageSelection,
17};
18use crate::wasmtime_provider::WasmtimeExtensionProvider;
19
20pub struct LoadedPackageHta {
21 pub identity: String,
22 pub entry_point: String,
23 pub extension: WasmExtension,
24}
25
26pub fn load_hta_package(
27 manifest: &PackageManifest,
28 package_root: &Path,
29 requirements: &PackageRuntimeRequirements,
30 extension_manifest_source: &str,
31) -> Result<LoadedPackageHta, String> {
32 let module = match manifest.wasm_imports.len() {
33 0 => {
34 return Err(
35 "package/missing-require-artifact: package declares no HTA artifacts".into(),
36 )
37 }
38 1 => manifest
39 .wasm_imports
40 .keys()
41 .next()
42 .cloned()
43 .expect("one HTA artifact"),
44 _ => {
45 return Err(
46 "package/ambiguous-require-artifact: package declares multiple HTA artifacts"
47 .into(),
48 )
49 }
50 };
51 load_hta_require_package(
52 manifest,
53 package_root,
54 &module,
55 requirements,
56 extension_manifest_source,
57 None,
58 )
59}
60
61pub fn load_hta_require_package(
62 manifest: &PackageManifest,
63 package_root: &Path,
64 module: &str,
65 requirements: &PackageRuntimeRequirements,
66 extension_manifest_source: &str,
67 host_handler: Option<Rc<dyn Fn(String, String, Vec<Value>) -> Result<Value, String>>>,
68) -> Result<LoadedPackageHta, String> {
69 manifest
70 .verify_files_at(package_root)
71 .map_err(|error| error.to_string())?;
72 let selection = manifest
73 .select_hta_require(module, requirements)
74 .map_err(|error| error.to_string())?;
75 let PackageSelection::Variant(variant) = &selection else {
76 return Err("package/missing-artifact: portable package has no HTA artifact".into());
77 };
78 if variant.artifact.artifact_type != PackageArtifactType::Hta {
79 return Err(format!(
80 "package/artifact-type-mismatch: expected :hta, got :{}",
81 variant.artifact.artifact_type.keyword()
82 ));
83 }
84 if variant.artifact.abi != "hta.v1" {
85 return Err(format!(
86 "package/abi-mismatch: HTA loader does not support {}",
87 variant.artifact.abi
88 ));
89 }
90
91 let artifact_path = package_root.join(&variant.artifact.path);
92 let bytes = fs::read(&artifact_path).map_err(|error| {
93 format!(
94 "package/missing-artifact: cannot read {}: {error}",
95 variant.artifact.path.display()
96 )
97 })?;
98 manifest
99 .verify_artifact_bytes(&selection, &bytes)
100 .map_err(|error| error.to_string())?;
101
102 let extension_manifest = ExtensionManifest::parse(extension_manifest_source, "package")?;
103 if extension_manifest.identity.as_deref() != Some(manifest.identity.as_str()) {
104 return Err("package/identity-mismatch: extension identity differs from package".into());
105 }
106 if extension_manifest.provider != "wasm" {
107 return Err("package/provider-mismatch: HTA artifact requires :provider :wasm".into());
108 }
109 if extension_manifest.abi != WasmAbi::HtaV1 {
110 return Err(
111 "package/abi-mismatch: extension manifest differs from selected variant".into(),
112 );
113 }
114 let required_capabilities = extension_manifest
115 .capabilities
116 .iter()
117 .cloned()
118 .collect::<BTreeSet<_>>();
119 if required_capabilities != variant.required_capabilities {
120 return Err(
121 "package/manifest-mismatch: required capabilities differ from extension".into(),
122 );
123 }
124 let declared_host_calls = extension_manifest
125 .host_calls
126 .iter()
127 .flat_map(|(service, methods)| {
128 methods
129 .iter()
130 .map(move |method| format!("{service}/{method}"))
131 })
132 .collect::<BTreeSet<_>>();
133 if declared_host_calls != variant.host_calls {
134 return Err("package/manifest-mismatch: declared host calls differ from extension".into());
135 }
136 if !variant.exports.iter().all(|export| {
137 extension_manifest
138 .exports
139 .iter()
140 .any(|(declared, _)| declared == export)
141 }) {
142 return Err(
143 "package/manifest-mismatch: selected exports are not declared by extension".into(),
144 );
145 }
146 let artifact_path = variant.artifact.path.to_string_lossy();
147 let library_path = extension_manifest
148 .assets
149 .iter()
150 .find(|path| path.ends_with(".wasm") && path.as_str() != artifact_path)
151 .cloned();
152 let provider = if let Some(library_path) = library_path {
153 let library_bytes = fs::read(package_root.join(&library_path)).map_err(|error| {
154 format!(
155 "package/missing-library: cannot read {}: {error}",
156 library_path
157 )
158 })?;
159 WasmtimeExtensionProvider::compile_hta_with_library(&bytes, &library_bytes, host_handler)?
160 } else {
161 WasmtimeExtensionProvider::compile_hta_with_host_handler(&bytes, host_handler)?
162 };
163 let extension = WasmExtension::new(extension_manifest, provider)?;
164 Ok(LoadedPackageHta {
165 identity: manifest.identity.clone(),
166 entry_point: variant.artifact.entry_point.clone(),
167 extension,
168 })
169}