1use cargo_toml::Dependency;
2use flate2::Compression;
3use flate2::read::GzDecoder;
4use flate2::write::GzEncoder;
5use pyro_spec::{InterfaceSpec, ModuleFunc};
6use sha2::{Digest, Sha256};
7use std::collections::{BTreeMap, HashMap};
8use std::future::Future;
9use std::io::{self, Read, Write};
10use std::ops::Deref;
11use std::path::Path;
12use tar::{Builder, Header};
13use tokio::fs;
14
15use crate::cargo::{CapabilityIdent, CapabilityManifest, ResolvedCapability};
16
17pub enum CapBinary {
18 Pe(Vec<u8>),
19 MachO(Vec<u8>),
20 Elf(Vec<u8>),
21}
22
23impl Deref for CapBinary {
24 type Target = [u8];
25
26 fn deref(&self) -> &Self::Target {
27 match self {
28 CapBinary::Pe(items) => &*items,
29 CapBinary::MachO(items) => &*items,
30 CapBinary::Elf(items) => &*items,
31 }
32 }
33}
34
35pub struct CapabilityBinary {
36 pub ident: CapabilityIdent,
37 pub libs: Vec<CapBinary>,
38 pub interface: InterfaceSpec<'static>,
39}
40
41pub struct CapabilitySource {
42 pub manifest: CapabilityManifest,
43 pub cargo_toml: String,
44 pub cargo_lock: String,
45 pub src_lib_rs: String,
46}
47
48pub struct Interface {
49 pub manifest: CapabilityManifest,
50 pub src_lib_rs: String,
51 pub interface: InterfaceSpec<'static>,
52}
53
54#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
55pub struct ModuleDependencies {
56 pub dependencies: BTreeMap<String, Dependency>,
57 pub capabilities: Vec<ResolvedCapability>,
58}
59
60#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
61pub struct ModuleSource {
62 pub dependencies: ModuleDependencies,
63 pub source: String,
64}
65
66#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
67pub struct ModuleSpec {
68 pub hash: String,
69 pub func: ModuleFunc<'static>,
70 pub capabilities: Vec<ResolvedCapability>,
71}
72
73#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
74pub struct ModuleBinary {
75 pub wasm: Vec<u8>,
76 pub spec: ModuleSpec,
77}
78
79#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
80pub enum Module {
81 Source(ModuleSource),
82 Binary(ModuleBinary),
83}
84
85#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
87pub struct Playbook {
88 pub hash: String,
89 #[serde(default)]
91 pub configurations: HashMap<String, Option<serde_json::Value>>,
92}
93
94impl ModuleSource {
95 pub fn hash(&self) -> String {
97 Self::compute_hash(
98 &self.source,
99 &self.dependencies.dependencies,
100 &self.dependencies.capabilities,
101 )
102 }
103
104 pub fn compute_hash(
105 code: &str,
106 dependencies: &std::collections::BTreeMap<String, cargo_toml::Dependency>,
107 capabilities: &[crate::cargo::ResolvedCapability],
108 ) -> String {
109 let mut hasher = Sha256::new();
110
111 hasher.update(code.as_bytes());
112
113 if let Ok(deps_json) = serde_json::to_string(dependencies) {
114 hasher.update(deps_json.as_bytes());
115 }
116
117 let mut sorted_caps = capabilities.to_vec();
118 sorted_caps.sort_by(|a, b| {
119 a.package
120 .cmp(&b.package)
121 .then_with(|| a.author.cmp(&b.author))
122 .then_with(|| a.version.cmp(&b.version))
123 });
124
125 if let Ok(caps_json) = serde_json::to_string(&sorted_caps) {
126 hasher.update(caps_json.as_bytes());
127 }
128
129 format!("{:x}", hasher.finalize())
130 }
131}
132
133impl ModuleBinary {
134 pub fn hash(&self) -> String {
135 self.spec.hash.clone()
136 }
137}
138
139impl Module {
140 pub fn hash(&self) -> String {
141 match self {
142 Module::Source(m) => m.hash(),
143 Module::Binary(m) => m.hash(),
144 }
145 }
146}
147
148pub enum Artifacts {
149 CapabilityBinary(CapabilityBinary),
150 CapabilitySource(CapabilitySource),
151 Interface(Interface),
152 Module(Module),
153}
154
155impl From<CapabilityBinary> for Artifacts {
156 fn from(value: CapabilityBinary) -> Self {
157 Artifacts::CapabilityBinary(value)
158 }
159}
160
161impl From<CapabilitySource> for Artifacts {
162 fn from(value: CapabilitySource) -> Self {
163 Artifacts::CapabilitySource(value)
164 }
165}
166
167impl From<Interface> for Artifacts {
168 fn from(value: Interface) -> Self {
169 Artifacts::Interface(value)
170 }
171}
172
173impl From<Module> for Artifacts {
174 fn from(value: Module) -> Self {
175 Artifacts::Module(value)
176 }
177}
178
179impl From<ModuleBinary> for Artifacts {
180 fn from(value: ModuleBinary) -> Self {
181 Artifacts::Module(Module::Binary(value))
182 }
183}
184
185impl From<ModuleSource> for Artifacts {
186 fn from(value: ModuleSource) -> Self {
187 Artifacts::Module(Module::Source(value))
188 }
189}
190
191pub trait Artifact: Sized {
194 fn write_to_directory(&self, path: &Path) -> impl Future<Output = io::Result<()>> + Send;
195 fn to_tarball(&self) -> Result<Vec<u8>, io::Error>;
196
197 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error>;
198 fn from_dir(path: &Path) -> impl Future<Output = Result<Self, io::Error>> + Send;
199}
200
201pub(crate) fn append_file<W: Write>(
203 tar: &mut Builder<W>,
204 name: &str,
205 data: &[u8],
206) -> Result<(), io::Error> {
207 let mut header = Header::new_gnu();
208 header.set_size(data.len() as u64);
209 header.set_mode(0o644);
210 header.set_cksum();
211 tar.append_data(&mut header, name, data)
212}
213
214impl Artifact for CapabilityBinary {
219 async fn write_to_directory(&self, path: &Path) -> io::Result<()> {
220 fs::create_dir_all(path).await?;
221 for lib in &self.libs {
222 match lib {
223 CapBinary::Pe(bytes) => fs::write(path.join("lib.dll"), bytes).await?,
224 CapBinary::MachO(bytes) => fs::write(path.join("lib.dylib"), bytes).await?,
225 CapBinary::Elf(bytes) => fs::write(path.join("lib.so"), bytes).await?,
226 }
227 }
228 fs::write(
229 path.join("ident.json"),
230 serde_json::to_string(&self.ident)
231 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
232 )
233 .await?;
234 let interface_json = serde_json::to_string_pretty(&self.interface)
235 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
236 fs::write(path.join("interface.json"), interface_json).await?;
237 Ok(())
238 }
239
240 fn to_tarball(&self) -> Result<Vec<u8>, io::Error> {
241 let encoder = GzEncoder::new(Vec::new(), Compression::default());
242 let mut tar = Builder::new(encoder);
243
244 for lib in &self.libs {
245 match lib {
246 CapBinary::Pe(bytes) => append_file(&mut tar, "lib.dll", bytes)?,
247 CapBinary::MachO(bytes) => append_file(&mut tar, "lib.dylib", bytes)?,
248 CapBinary::Elf(bytes) => append_file(&mut tar, "lib.so", bytes)?,
249 }
250 }
251 append_file(
252 &mut tar,
253 "ident.json",
254 serde_json::to_string(&self.ident)
255 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
256 .as_bytes(),
257 )?;
258 append_file(
259 &mut tar,
260 "interface.json",
261 serde_json::to_string_pretty(&self.interface)
262 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
263 .as_bytes(),
264 )?;
265
266 tar.into_inner()?.finish()
267 }
268
269 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error> {
270 let tar = GzDecoder::new(bytes);
271 let mut archive = tar::Archive::new(tar);
272
273 let mut libs = Vec::new();
274 let mut ident = None;
275 let mut interface = None;
276
277 for file in archive.entries()? {
278 let mut file = file?;
279 let path = file.path()?.to_path_buf();
280 let mut content = Vec::new();
281 file.read_to_end(&mut content)?;
282
283 match path.to_string_lossy().as_ref() {
284 "lib.dll" => libs.push(CapBinary::Pe(content)),
285 "lib.dylib" => libs.push(CapBinary::MachO(content)),
286 "lib.so" => libs.push(CapBinary::Elf(content)),
287 "ident.json" => {
288 ident = serde_json::from_slice(&content).map_err(|e| {
289 io::Error::new(
290 io::ErrorKind::InvalidData,
291 format!("Unable to deserialize manifest: {}", e),
292 )
293 })?;
294 }
295 "interface.json" => {
296 interface = serde_json::from_slice(&content).map_err(|e| {
297 io::Error::new(
298 io::ErrorKind::InvalidData,
299 format!("Unable to deserialize interface: {}", e),
300 )
301 })?;
302 }
303 _ => {}
304 }
305 }
306
307 if libs.is_empty() {
308 return Err(io::Error::new(io::ErrorKind::NotFound, "Missing library"));
309 }
310
311 Ok(CapabilityBinary {
312 ident: ident
313 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing ident.json"))?,
314 libs,
315 interface: interface.ok_or_else(|| {
316 io::Error::new(io::ErrorKind::InvalidData, "Missing interface.json")
317 })?,
318 })
319 }
320
321 async fn from_dir(path: &Path) -> Result<Self, io::Error> {
322 let mut libs = Vec::new();
323 if let Ok(bytes) = fs::read(path.join("lib.dll")).await {
324 libs.push(CapBinary::Pe(bytes));
325 }
326 if let Ok(bytes) = fs::read(path.join("lib.dylib")).await {
327 libs.push(CapBinary::MachO(bytes));
328 }
329 if let Ok(bytes) = fs::read(path.join("lib.so")).await {
330 libs.push(CapBinary::Elf(bytes));
331 }
332
333 if libs.is_empty() {
334 return Err(io::Error::new(
335 io::ErrorKind::NotFound,
336 "Missing capability library",
337 ));
338 }
339
340 let ident_string = fs::read(path.join("ident.json")).await?;
341 let ident = serde_json::from_slice(&ident_string).map_err(|e| {
342 io::Error::new(
343 io::ErrorKind::InvalidData,
344 format!("Unable to deserialize ident: {}", e),
345 )
346 })?;
347
348 let interface_string = fs::read(path.join("interface.json")).await?;
349 let interface = serde_json::from_slice(&interface_string).map_err(|e| {
350 io::Error::new(
351 io::ErrorKind::InvalidData,
352 format!("Unable to deserialize interface: {}", e),
353 )
354 })?;
355
356 Ok(CapabilityBinary {
357 libs,
358 ident,
359 interface,
360 })
361 }
362}
363
364impl Artifact for CapabilitySource {
365 async fn write_to_directory(&self, path: &Path) -> io::Result<()> {
366 fs::create_dir_all(path).await?;
367 let manifest = toml::to_string_pretty(&self.manifest).map_err(|e| {
368 io::Error::new(
369 io::ErrorKind::InvalidData,
370 format!("Unable to serialize manifest: {}", e),
371 )
372 })?;
373 fs::write(path.join("Capability.toml"), &manifest).await?;
374 fs::write(path.join("Cargo.toml"), &self.cargo_toml).await?;
375 fs::write(path.join("Cargo.lock"), &self.cargo_lock).await?;
376
377 let src_dir = path.join("src");
378 fs::create_dir_all(&src_dir).await?;
379 fs::write(src_dir.join("lib.rs"), &self.src_lib_rs).await?;
380 Ok(())
381 }
382
383 fn to_tarball(&self) -> Result<Vec<u8>, io::Error> {
384 let encoder = GzEncoder::new(Vec::new(), Compression::default());
385 let mut tar = Builder::new(encoder);
386
387 let manifest = toml::to_string_pretty(&self.manifest).map_err(|e| {
388 io::Error::new(
389 io::ErrorKind::InvalidData,
390 format!("Unable to serialize manifest: {}", e),
391 )
392 })?;
393 append_file(&mut tar, "Capability.toml", manifest.as_bytes())?;
394 append_file(&mut tar, "Cargo.toml", self.cargo_toml.as_bytes())?;
395 append_file(&mut tar, "Cargo.lock", self.cargo_lock.as_bytes())?;
396 append_file(&mut tar, "src/lib.rs", self.src_lib_rs.as_bytes())?;
397
398 tar.into_inner()?.finish()
399 }
400
401 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error> {
402 let tar = GzDecoder::new(bytes);
403 let mut archive = tar::Archive::new(tar);
404
405 let mut manifest = None;
406 let mut cargo_toml = None;
407 let mut cargo_lock = None;
408 let mut src_lib_rs = None;
409
410 for file in archive.entries()? {
411 let mut file = file?;
412 let path = file.path()?.to_path_buf();
413 let mut content = Vec::new();
414 file.read_to_end(&mut content)?;
415
416 match path.to_string_lossy().as_ref() {
417 "Capability.toml" => {
418 manifest = toml::from_slice(&content).map_err(|e| {
419 io::Error::new(
420 io::ErrorKind::InvalidData,
421 format!("Unable to deserialize manifest: {}", e),
422 )
423 })?;
424 }
425 "Cargo.toml" => cargo_toml = String::from_utf8(content).ok(),
426 "Cargo.lock" => cargo_lock = String::from_utf8(content).ok(),
427 "src/lib.rs" => src_lib_rs = String::from_utf8(content).ok(),
428 _ => {}
429 }
430 }
431
432 Ok(CapabilitySource {
433 manifest: manifest.ok_or_else(|| {
434 io::Error::new(io::ErrorKind::InvalidData, "Missing Capability.toml")
435 })?,
436 cargo_toml: cargo_toml
437 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing Cargo.toml"))?,
438 cargo_lock: cargo_lock
439 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing Cargo.lock"))?,
440 src_lib_rs: src_lib_rs
441 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing src/lib.rs"))?,
442 })
443 }
444
445 async fn from_dir(path: &Path) -> Result<Self, io::Error> {
446 let manifest_string = fs::read(path.join("Capability.toml")).await?;
447 let manifest = toml::from_slice(&manifest_string).map_err(|e| {
448 io::Error::new(
449 io::ErrorKind::InvalidData,
450 format!("Unable to deserialize manifest: {}", e),
451 )
452 })?;
453
454 Ok(CapabilitySource {
455 manifest,
456 cargo_toml: fs::read_to_string(path.join("Cargo.toml")).await?,
457 cargo_lock: fs::read_to_string(path.join("Cargo.lock")).await?,
458 src_lib_rs: fs::read_to_string(path.join("src").join("lib.rs")).await?,
459 })
460 }
461}
462
463impl Artifact for Interface {
464 async fn write_to_directory(&self, path: &Path) -> io::Result<()> {
465 let manifest = toml::to_string_pretty(&self.manifest).map_err(|e| {
466 io::Error::new(
467 io::ErrorKind::InvalidData,
468 format!("Unable to serialize manifest: {}", e),
469 )
470 })?;
471 fs::create_dir_all(path).await?;
472 fs::write(path.join("Capability.toml"), &manifest).await?;
473 let interface_str = serde_json::to_string_pretty(&self.interface).map_err(|e| {
474 io::Error::new(
475 io::ErrorKind::InvalidData,
476 format!("Unable to serialize manifest: {}", e),
477 )
478 })?;
479 fs::write(path.join("interface.json"), &interface_str).await?;
480
481 let src_dir = path.join("src");
482 fs::create_dir_all(&src_dir).await?;
483 fs::write(src_dir.join("lib.rs"), &self.src_lib_rs).await?;
484 Ok(())
485 }
486
487 fn to_tarball(&self) -> Result<Vec<u8>, io::Error> {
488 let encoder = GzEncoder::new(Vec::new(), Compression::default());
489 let mut tar = Builder::new(encoder);
490 let manifest = toml::to_string_pretty(&self.manifest).map_err(|e| {
491 io::Error::new(
492 io::ErrorKind::InvalidData,
493 format!("Unable to serialize manifest: {}", e),
494 )
495 })?;
496 let interface = serde_json::to_string_pretty(&self.interface).map_err(|e| {
497 io::Error::new(
498 io::ErrorKind::InvalidData,
499 format!("Unable to serialize interface: {}", e),
500 )
501 })?;
502
503 append_file(&mut tar, "Capability.toml", manifest.as_bytes())?;
504 append_file(&mut tar, "interface.json", interface.as_bytes())?;
505 append_file(&mut tar, "src/lib.rs", self.src_lib_rs.as_bytes())?;
506
507 tar.into_inner()?.finish()
508 }
509
510 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error> {
511 let tar = GzDecoder::new(bytes);
512 let mut archive = tar::Archive::new(tar);
513
514 let mut manifest = None;
515 let mut src_lib_rs = None;
516 let mut interface = None;
517
518 for file in archive.entries()? {
519 let mut file = file?;
520 let path = file.path()?.to_path_buf();
521 let mut content = Vec::new();
522 file.read_to_end(&mut content)?;
523
524 match path.to_string_lossy().as_ref() {
525 "Capability.toml" => {
526 manifest = toml::from_slice(&content).map_err(|e| {
527 io::Error::new(
528 io::ErrorKind::InvalidData,
529 format!("Unable to deserialize manifest: {}", e),
530 )
531 })?;
532 }
533 "interface.json" => {
534 interface = serde_json::from_slice(&content).map_err(|e| {
535 io::Error::new(
536 io::ErrorKind::InvalidData,
537 format!("Unable to deserialize interface: {}", e),
538 )
539 })?;
540 }
541 "src/lib.rs" => src_lib_rs = String::from_utf8(content).ok(),
542 _ => {}
543 }
544 }
545
546 Ok(Interface {
547 manifest: manifest.ok_or_else(|| {
548 io::Error::new(io::ErrorKind::InvalidData, "Missing Capability.toml")
549 })?,
550 src_lib_rs: src_lib_rs
551 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing src/lib.rs"))?,
552 interface: interface.ok_or_else(|| {
553 io::Error::new(io::ErrorKind::InvalidData, "Missing interface.json")
554 })?,
555 })
556 }
557
558 async fn from_dir(path: &Path) -> Result<Self, io::Error> {
559 let manifest_string = fs::read(path.join("Capability.toml")).await?;
560 let manifest = toml::from_slice(&manifest_string).map_err(|e| {
561 io::Error::new(
562 io::ErrorKind::InvalidData,
563 format!("Unable to deserialize manifest: {}", e),
564 )
565 })?;
566
567 let interface_string = fs::read(path.join("interface.json")).await?;
568 let interface = toml::from_slice(&interface_string).map_err(|e| {
569 io::Error::new(
570 io::ErrorKind::InvalidData,
571 format!("Unable to deserialize interface: {}", e),
572 )
573 })?;
574
575 Ok(Interface {
576 manifest,
577 src_lib_rs: fs::read_to_string(path.join("src").join("lib.rs")).await?,
578 interface,
579 })
580 }
581}
582
583impl Artifact for ModuleSource {
584 async fn write_to_directory(&self, path: &Path) -> io::Result<()> {
585 fs::create_dir_all(path).await?;
586 let dependencies = serde_json::to_string_pretty(&self.dependencies).map_err(|e| {
587 io::Error::new(
588 io::ErrorKind::InvalidData,
589 format!("Unable to serialize dependencies: {}", e),
590 )
591 })?;
592 fs::write(path.join("source.rs"), &self.source).await?;
593 fs::write(path.join("dependencies.json"), &dependencies).await?;
594 Ok(())
595 }
596
597 fn to_tarball(&self) -> Result<Vec<u8>, io::Error> {
598 let encoder = GzEncoder::new(Vec::new(), Compression::default());
599 let mut tar = Builder::new(encoder);
600 let dependencies = serde_json::to_string_pretty(&self.dependencies).map_err(|e| {
601 io::Error::new(
602 io::ErrorKind::InvalidData,
603 format!("Unable to serialize dependencies: {}", e),
604 )
605 })?;
606 append_file(&mut tar, "source.rs", self.source.as_bytes())?;
607 append_file(&mut tar, "dependencies.json", dependencies.as_bytes())?;
608 tar.into_inner()?.finish()
609 }
610
611 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error> {
612 let tar = GzDecoder::new(bytes);
613 let mut archive = tar::Archive::new(tar);
614 let mut source = None;
615 let mut dependencies = None;
616
617 for file in archive.entries()? {
618 let mut file = file?;
619 let path = file.path()?.to_path_buf();
620 let mut content = Vec::new();
621 file.read_to_end(&mut content)?;
622
623 match path.to_string_lossy().as_ref() {
624 "source.rs" => source = String::from_utf8(content).ok(),
625 "dependencies.json" => {
626 dependencies = serde_json::from_slice(&content).map_err(|e| {
627 io::Error::new(
628 io::ErrorKind::InvalidData,
629 format!("Unable to deserialize dependencies: {}", e),
630 )
631 })?;
632 }
633 _ => {}
634 }
635 }
636
637 Ok(ModuleSource {
638 source: source
639 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Missing source.rs"))?,
640 dependencies: dependencies.ok_or_else(|| {
641 io::Error::new(io::ErrorKind::NotFound, "Missing dependencies.json")
642 })?,
643 })
644 }
645
646 async fn from_dir(path: &Path) -> Result<Self, io::Error> {
647 let dependencies_string = fs::read(path.join("dependencies.json")).await?;
648 let dependencies = serde_json::from_slice(&dependencies_string).map_err(|e| {
649 io::Error::new(
650 io::ErrorKind::InvalidData,
651 format!("Unable to deserialize dependencies: {}", e),
652 )
653 })?;
654 Ok(ModuleSource {
655 source: fs::read_to_string(path.join("source.rs")).await?,
656 dependencies,
657 })
658 }
659}
660
661impl Artifact for ModuleBinary {
662 async fn write_to_directory(&self, path: &Path) -> io::Result<()> {
663 fs::create_dir_all(path).await?;
664 fs::write(path.join("mod.wasm"), &self.wasm).await?;
665 let spec = serde_json::to_string_pretty(&self.spec).map_err(|e| {
666 io::Error::new(
667 io::ErrorKind::InvalidData,
668 format!("Unable to serialize spec: {}", e),
669 )
670 })?;
671 fs::write(path.join("spec.json"), spec).await?;
672 Ok(())
673 }
674
675 fn to_tarball(&self) -> Result<Vec<u8>, io::Error> {
676 let encoder = GzEncoder::new(Vec::new(), Compression::default());
677 let mut tar = Builder::new(encoder);
678 append_file(&mut tar, "mod.wasm", &self.wasm)?;
679 let spec = serde_json::to_string_pretty(&self.spec).map_err(|e| {
680 io::Error::new(
681 io::ErrorKind::InvalidData,
682 format!("Unable to serialize spec: {}", e),
683 )
684 })?;
685 append_file(&mut tar, "spec.json", spec.as_bytes())?;
686 tar.into_inner()?.finish()
687 }
688
689 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error> {
690 let tar = GzDecoder::new(bytes);
691 let mut archive = tar::Archive::new(tar);
692 let mut wasm = None;
693 let mut spec = None;
694
695 for file in archive.entries()? {
696 let mut file = file?;
697 let path = file.path()?.to_path_buf();
698 let mut content = Vec::new();
699 file.read_to_end(&mut content)?;
700
701 match path.to_string_lossy().as_ref() {
702 "mod.wasm" => wasm = Some(content),
703 "spec.json" => {
704 spec = serde_json::from_slice(&content).map_err(|e| {
705 io::Error::new(
706 io::ErrorKind::InvalidData,
707 format!("Unable to deserialize spec: {}", e),
708 )
709 })?;
710 }
711 _ => {}
712 }
713 }
714
715 Ok(ModuleBinary {
716 wasm: wasm
717 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Missing mod.wasm"))?,
718 spec: spec
719 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Missing spec.json"))?,
720 })
721 }
722
723 async fn from_dir(path: &Path) -> Result<Self, io::Error> {
724 let spec_string = fs::read(path.join("spec.json")).await?;
725 let spec = serde_json::from_slice(&spec_string).map_err(|e| {
726 io::Error::new(
727 io::ErrorKind::InvalidData,
728 format!("Unable to deserialize spec: {}", e),
729 )
730 })?;
731 Ok(ModuleBinary {
732 wasm: fs::read(path.join("mod.wasm")).await?,
733 spec,
734 })
735 }
736}
737
738impl Artifact for Module {
739 async fn write_to_directory(&self, path: &Path) -> io::Result<()> {
740 match self {
741 Module::Source(module_source) => module_source.write_to_directory(path).await,
742 Module::Binary(module_binary) => module_binary.write_to_directory(path).await,
743 }
744 }
745
746 fn to_tarball(&self) -> Result<Vec<u8>, io::Error> {
747 match self {
748 Module::Source(module_source) => module_source.to_tarball(),
749 Module::Binary(module_binary) => module_binary.to_tarball(),
750 }
751 }
752
753 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error> {
754 let tar = GzDecoder::new(bytes);
756 let mut archive = tar::Archive::new(tar);
757
758 let mut has_source_rs = false;
759 let mut has_wasm = false;
760
761 for file in archive.entries()? {
762 let file = file?;
763 let path_str = file.path()?.to_string_lossy().into_owned();
764
765 match path_str.as_ref() {
766 "source.rs" => has_source_rs = true,
767 "mod.wasm" => has_wasm = true,
768 _ => {}
769 }
770 }
771
772 if has_source_rs {
773 Ok(Module::Source(ModuleSource::from_tarball(bytes)?))
774 } else if has_wasm {
775 Ok(Module::Binary(ModuleBinary::from_tarball(bytes)?))
776 } else {
777 Err(io::Error::new(
778 io::ErrorKind::InvalidData,
779 "Unknown module format in tarball: missing 'source.rs' or 'mod.wasm'",
780 ))
781 }
782 }
783
784 async fn from_dir(path: &Path) -> Result<Self, io::Error> {
785 if fs::try_exists(path.join("source.rs"))
786 .await
787 .unwrap_or(false)
788 {
789 Ok(Module::Source(ModuleSource::from_dir(path).await?))
790 } else if fs::try_exists(path.join("mod.wasm")).await.unwrap_or(false) {
791 Ok(Module::Binary(ModuleBinary::from_dir(path).await?))
792 } else {
793 Err(io::Error::new(
794 io::ErrorKind::InvalidData,
795 "Unknown module format in directory: missing 'source.rs' or 'mod.wasm'",
796 ))
797 }
798 }
799}
800
801impl Artifact for Artifacts {
802 async fn write_to_directory(&self, path: &Path) -> io::Result<()> {
803 match self {
804 Artifacts::CapabilityBinary(c) => c.write_to_directory(path).await,
805 Artifacts::CapabilitySource(c) => c.write_to_directory(path).await,
806 Artifacts::Interface(i) => i.write_to_directory(path).await,
807 Artifacts::Module(m) => m.write_to_directory(path).await,
808 }
809 }
810
811 fn to_tarball(&self) -> Result<Vec<u8>, io::Error> {
812 match self {
813 Artifacts::CapabilityBinary(c) => c.to_tarball(),
814 Artifacts::CapabilitySource(c) => c.to_tarball(),
815 Artifacts::Interface(i) => i.to_tarball(),
816 Artifacts::Module(m) => m.to_tarball(),
817 }
818 }
819
820 fn from_tarball(bytes: &[u8]) -> Result<Self, io::Error> {
821 let tar = GzDecoder::new(bytes);
823 let mut archive = tar::Archive::new(tar);
824
825 let mut has_source_rs = false;
826 let mut has_wasm = false;
827 let mut has_cap_toml = false;
828 let mut has_lib = false;
829
830 for file in archive.entries()? {
831 let file = file?;
832 let path_str = file.path()?.to_string_lossy().into_owned();
833
834 match path_str.as_ref() {
835 "source.rs" => has_source_rs = true,
836 "mod.wasm" => has_wasm = true,
837 "Capability.toml" => has_cap_toml = true,
838 "lib.dll" | "lib.dylib" | "lib.so" => has_lib = true,
839 _ => {}
840 }
841 }
842
843 if has_source_rs || has_wasm {
844 Ok(Artifacts::Module(Module::from_tarball(bytes)?))
846 } else if has_cap_toml {
847 if has_lib {
848 Ok(Artifacts::CapabilitySource(CapabilitySource::from_tarball(
850 bytes,
851 )?))
852 } else {
853 Ok(Artifacts::Interface(Interface::from_tarball(bytes)?))
854 }
855 } else if has_lib {
856 Ok(Artifacts::CapabilityBinary(CapabilityBinary::from_tarball(
857 bytes,
858 )?))
859 } else {
860 Err(io::Error::new(
861 io::ErrorKind::InvalidData,
862 "Unknown artifact format in tarball",
863 ))
864 }
865 }
866
867 async fn from_dir(path: &Path) -> Result<Self, io::Error> {
868 let has_source_rs = fs::try_exists(path.join("source.rs"))
869 .await
870 .unwrap_or(false);
871 let has_wasm = fs::try_exists(path.join("mod.wasm")).await.unwrap_or(false);
872
873 if has_source_rs || has_wasm {
874 Ok(Artifacts::Module(Module::from_dir(path).await?))
876 } else if fs::try_exists(path.join("Capability.toml"))
877 .await
878 .unwrap_or(false)
879 {
880 let has_dll = fs::try_exists(path.join("lib.dll")).await.unwrap_or(false);
881 let has_dylib = fs::try_exists(path.join("lib.dylib"))
882 .await
883 .unwrap_or(false);
884 let has_so = fs::try_exists(path.join("lib.so")).await.unwrap_or(false);
885
886 if has_dll || has_dylib || has_so {
887 Ok(Artifacts::CapabilitySource(
889 CapabilitySource::from_dir(path).await?,
890 ))
891 } else {
892 Ok(Artifacts::Interface(Interface::from_dir(path).await?))
893 }
894 } else if fs::try_exists(path.join("lib.dll")).await.unwrap_or(false)
895 || fs::try_exists(path.join("lib.dylib"))
896 .await
897 .unwrap_or(false)
898 || fs::try_exists(path.join("lib.so")).await.unwrap_or(false)
899 {
900 Ok(Artifacts::CapabilityBinary(
901 CapabilityBinary::from_dir(path).await?,
902 ))
903 } else {
904 Err(io::Error::new(
905 io::ErrorKind::InvalidData,
906 "Unknown artifact format in directory",
907 ))
908 }
909 }
910}