1use super::Error;
2use anyhow::{anyhow, Context, Result};
3use indexmap::IndexMap;
4use miette::SourceSpan;
5use std::{collections::HashMap, fs, path::PathBuf};
6use wac_types::BorrowedPackageKey;
7
8pub struct FileSystemPackageResolver {
10 root: PathBuf,
11 overrides: HashMap<String, PathBuf>,
12 error_on_unknown: bool,
13}
14
15impl FileSystemPackageResolver {
16 pub fn new(
18 root: impl Into<PathBuf>,
19 overrides: HashMap<String, PathBuf>,
20 error_on_unknown: bool,
21 ) -> Self {
22 Self {
23 root: root.into(),
24 overrides,
25 error_on_unknown,
26 }
27 }
28
29 pub fn resolve<'a>(
31 &self,
32 keys: &IndexMap<BorrowedPackageKey<'a>, SourceSpan>,
33 ) -> Result<IndexMap<BorrowedPackageKey<'a>, Vec<u8>>, Error> {
34 let mut packages = IndexMap::new();
35 for (key, span) in keys.iter() {
36 let path = match self.overrides.get(key.name) {
37 Some(path) if key.version.is_none() => {
38 if !path.is_file() {
39 return Err(Error::PackageResolutionFailure {
40 name: key.name.to_string(),
41 span: *span,
42 source: anyhow!(
43 "local path `{path}` for package `{name}` does not exist",
44 path = path.display(),
45 name = key.name
46 ),
47 });
48 }
49
50 path.clone()
51 }
52 _ => {
53 let mut path = self.root.clone();
54 for segment in key.name.split(':') {
55 path.push(segment);
56 }
57
58 if let Some(version) = key.version {
59 path.push(version.to_string());
60 }
61
62 if !path.is_dir() {
64 append_extension(&mut path, "wasm");
65
66 #[cfg(feature = "wat")]
67 {
68 path.set_extension("wat");
69 if !path.exists() {
70 path.set_extension("wasm");
71 }
72 }
73 }
74
75 path
76 }
77 };
78
79 #[cfg(feature = "wit")]
82 {
83 let pkg_res_failure = |e| Error::PackageResolutionFailure {
84 name: key.name.to_string(),
85 span: *span,
86 source: e,
87 };
88 let mut resolve = wit_parser::Resolve::new();
89 let pkg = if path.is_dir() {
90 log::debug!(
91 "loading WIT package from directory `{path}`",
92 path = path.display()
93 );
94
95 let (pkg, _) = resolve.push_dir(&path).map_err(pkg_res_failure)?;
96 Some(pkg)
97 } else if path.extension().and_then(std::ffi::OsStr::to_str) == Some("wit") {
98 let pkg = resolve.push_file(&path).map_err(pkg_res_failure)?;
99 Some(pkg)
100 } else {
101 None
102 };
103 if let Some(pkg) = pkg {
104 packages.insert(
105 *key,
106 wit_component::encode(&resolve, pkg)
107 .with_context(|| {
108 format!(
109 "failed to encode WIT package from `{path}`",
110 path = path.display()
111 )
112 })
113 .map_err(pkg_res_failure)?,
114 );
115
116 continue;
117 }
118 }
119
120 if !path.is_file() {
121 log::debug!(
122 "package `{key}` does not exist at `{path}`",
123 path = path.display()
124 );
125 if self.error_on_unknown {
126 return Err(Error::UnknownPackage {
127 name: key.name.to_string(),
128 span: *span,
129 });
130 }
131 continue;
132 }
133
134 log::debug!(
135 "loading package `{key}` from `{path}`",
136 path = path.display()
137 );
138 let bytes = fs::read(&path)
139 .with_context(|| format!("failed to read package `{path}`", path = path.display()))
140 .map_err(|e| Error::PackageResolutionFailure {
141 name: key.name.to_string(),
142 span: *span,
143 source: e,
144 })?;
145
146 #[cfg(feature = "wat")]
147 if path.extension().and_then(std::ffi::OsStr::to_str) == Some("wat") {
148 let bytes = match wat::parse_bytes(&bytes) {
149 Ok(std::borrow::Cow::Borrowed(_)) => bytes,
150 Ok(std::borrow::Cow::Owned(wat)) => wat,
151 Err(mut e) => {
152 e.set_path(path);
153 return Err(Error::PackageResolutionFailure {
154 name: key.name.to_string(),
155 span: *span,
156 source: anyhow!(e),
157 });
158 }
159 };
160
161 packages.insert(*key, bytes);
162 continue;
163 }
164
165 packages.insert(*key, bytes);
166 }
167
168 Ok(packages)
169 }
170}
171
172impl Default for FileSystemPackageResolver {
173 fn default() -> Self {
174 Self::new("deps", Default::default(), true)
175 }
176}
177
178fn append_extension(path: &mut PathBuf, extension: &str) {
181 let os_str = path.as_mut_os_string();
182 os_str.push(".");
183 os_str.push(extension)
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn append_extension_if_no_existing_extension() {
192 let mut path = PathBuf::from("a/b/c");
193 append_extension(&mut path, "txt");
194 assert_eq!("a/b/c.txt", path.display().to_string());
195 }
196
197 #[test]
198 fn append_extension_if_existing_extension() {
199 let mut path = PathBuf::from("a/b/0.0.1");
200 append_extension(&mut path, "wasm");
201 assert_eq!("a/b/0.0.1.wasm", path.display().to_string());
202 }
203}