Skip to main content

rustev_eval/
bundle.rs

1//! A case's replay bundle as the host supplies it (spec 015, 3.1).
2//!
3//! The library reads no storage: the host either parsed a bundle or
4//! observed why the bytes it holds are unusable, and says which. A case
5//! with no entry at all is `bundle-missing`, a different statement: the
6//! host holds nothing for it.
7
8use rustev_contract::replay::ReplayBundle;
9
10/// The most bytes of an operator note a load failure keeps.
11pub const NOTE_MAX_BYTES: usize = 256;
12
13/// Why a bundle the host holds could not be used.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
15pub enum BundleLoad {
16    /// The host could not read bytes it holds: an I/O error, a refusal, a
17    /// file that is not a regular file.
18    Inaccessible,
19    /// The bytes exceed `REPLAY_V1`'s document limit.
20    Oversized,
21    /// The bytes are not a valid `rustev.replay/1` document.
22    Corrupt,
23}
24
25impl BundleLoad {
26    /// The case's incomparable reason (spec 015, 3.2.1).
27    pub fn code(self) -> &'static str {
28        match self {
29            BundleLoad::Inaccessible => "bundle-inaccessible",
30            BundleLoad::Oversized => "bundle-oversized",
31            BundleLoad::Corrupt => "bundle-corrupt",
32        }
33    }
34}
35
36/// A load failure the host observed, with a bounded note for the operator.
37/// The note is not authoritative and never enters a report.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct LoadFailure {
40    pub kind: BundleLoad,
41    note: String,
42}
43
44impl LoadFailure {
45    /// Keeps at most [`NOTE_MAX_BYTES`] of `note`, cut at a character
46    /// boundary.
47    pub fn new(kind: BundleLoad, note: &str) -> LoadFailure {
48        let mut end = note.len().min(NOTE_MAX_BYTES);
49        while !note.is_char_boundary(end) {
50            end -= 1;
51        }
52        LoadFailure {
53            kind,
54            note: note[..end].to_string(),
55        }
56    }
57
58    pub fn note(&self) -> &str {
59        &self.note
60    }
61}
62
63/// One case's entry in the evaluation inputs.
64#[derive(Debug, Clone)]
65pub enum BundleInput {
66    Loaded(Box<ReplayBundle>),
67    Failed(LoadFailure),
68}
69
70impl From<ReplayBundle> for BundleInput {
71    fn from(b: ReplayBundle) -> BundleInput {
72        BundleInput::Loaded(Box::new(b))
73    }
74}
75
76impl From<LoadFailure> for BundleInput {
77    fn from(f: LoadFailure) -> BundleInput {
78        BundleInput::Failed(f)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn a_note_is_cut_at_a_character_boundary() {
88        let long = "é".repeat(200);
89        let f = LoadFailure::new(BundleLoad::Corrupt, &long);
90        assert_eq!(f.note().len(), NOTE_MAX_BYTES);
91        let odd = format!("x{long}");
92        let f = LoadFailure::new(BundleLoad::Corrupt, &odd);
93        assert_eq!(f.note().len(), NOTE_MAX_BYTES - 1);
94        assert_eq!(
95            LoadFailure::new(BundleLoad::Oversized, "short").note(),
96            "short"
97        );
98    }
99}