Skip to main content

phoxal_bundle/
artifact.rs

1//! Indexed executable artifact records.
2
3use std::fmt;
4use std::io::{Seek, SeekFrom};
5use std::path::{Path, PathBuf};
6
7use phoxal_runtime_contract::identity::ParticipantArtifactId;
8use phoxal_runtime_contract::metadata::ParticipantContract;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12    BIN_DIR, BundleError, BundlePath, DocumentError, Sha256Digest, open_executable_source,
13};
14
15/// One opened executable source. The writer hashes and copies the same open
16/// handle, so the digest it records and the bytes it stages come from one file.
17pub struct BinarySource {
18    file: std::fs::File,
19    path: PathBuf,
20}
21
22impl fmt::Debug for BinarySource {
23    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24        formatter
25            .debug_struct("BinarySource")
26            .field("path", &self.path)
27            .finish_non_exhaustive()
28    }
29}
30
31impl BinarySource {
32    /// Open one executable source file.
33    pub fn open(path: impl AsRef<Path>) -> Result<Self, BundleError> {
34        let path = path.as_ref().to_path_buf();
35        let file = open_executable_source(&path)?;
36        Ok(Self { file, path })
37    }
38
39    /// The source pathname retained only for diagnostics.
40    #[must_use]
41    pub fn path(&self) -> &Path {
42        &self.path
43    }
44
45    pub(crate) fn reader(&self) -> Result<std::fs::File, BundleError> {
46        let mut file = self
47            .file
48            .try_clone()
49            .map_err(|source| BundleError::ReadFile {
50                path: self.path.clone(),
51                source,
52            })?;
53        file.seek(SeekFrom::Start(0))
54            .map_err(|source| BundleError::ReadFile {
55                path: self.path.clone(),
56                source,
57            })?;
58        Ok(file)
59    }
60}
61
62/// A staged reusable executable and its canonical artifact contract.
63#[derive(Clone, Debug, Deserialize, Serialize)]
64#[serde(deny_unknown_fields)]
65pub struct BinaryReference {
66    pub(crate) path: BundlePath,
67    pub(crate) digest: Sha256Digest,
68    pub(crate) size_bytes: u64,
69    pub(crate) contract: ParticipantContract,
70}
71
72impl BinaryReference {
73    /// Construct a reference for an already-built executable source.
74    pub fn from_source(
75        path: BundlePath,
76        contract: ParticipantContract,
77        source: &BinarySource,
78    ) -> Result<Self, BundleError> {
79        let file = source.reader()?;
80        let size_bytes = file
81            .metadata()
82            .map_err(|source_error| BundleError::ReadFile {
83                path: source.path.clone(),
84                source: source_error,
85            })?
86            .len();
87        Ok(Self {
88            path,
89            digest: Sha256Digest::from_reader(file).map_err(|source_error| {
90                BundleError::ReadFile {
91                    path: source.path.clone(),
92                    source: source_error,
93                }
94            })?,
95            size_bytes,
96            contract,
97        })
98    }
99
100    #[must_use]
101    pub const fn path(&self) -> &BundlePath {
102        &self.path
103    }
104    #[must_use]
105    pub const fn digest(&self) -> Sha256Digest {
106        self.digest
107    }
108    #[must_use]
109    pub const fn size_bytes(&self) -> u64 {
110        self.size_bytes
111    }
112    #[must_use]
113    pub const fn contract(&self) -> &ParticipantContract {
114        &self.contract
115    }
116
117    pub(crate) fn validate(&self, id: &ParticipantArtifactId) -> Result<(), DocumentError> {
118        if self.contract.id != *id {
119            return Err(DocumentError::ArtifactContractMismatch {
120                artifact: id.clone(),
121                contract: self.contract.id.clone(),
122            });
123        }
124        if !self.path.starts_with_directory(BIN_DIR) {
125            return Err(DocumentError::ArtifactOutsideBin {
126                artifact: id.clone(),
127                path: self.path.clone(),
128            });
129        }
130        Ok(())
131    }
132}