Skip to main content

rustev_surface/
deps.rs

1//! Reading a plan's dependencies: backend descriptors, rules programs (whose
2//! descriptors the host derives, spec 005 3.9) and calibration artifacts.
3
4use rustev_contract::calibration::CalibrationArtifact;
5use rustev_contract::descriptor::BackendDescriptor;
6use rustev_contract::limits::DESCRIPTOR_V1;
7use rustev_contract::{Document, DocumentError};
8
9use crate::host::{Host, IoFail};
10
11/// What the dependency inputs named, labels in order.
12#[derive(Debug, Clone, Copy, Default)]
13pub struct DepArgs<'a> {
14    pub descriptors: &'a [String],
15    pub rules: &'a [String],
16    pub calibrations: &'a [String],
17}
18
19/// The parsed dependencies.
20#[derive(Debug, Default)]
21pub struct Deps {
22    /// The descriptors, then each rules program's derived descriptor.
23    pub descriptors: Vec<BackendDescriptor>,
24    pub calibrations: Vec<CalibrationArtifact>,
25}
26
27/// Why the dependencies could not be read.
28#[derive(Debug)]
29pub enum DepFail {
30    Io(IoFail),
31    /// A malformed descriptor or calibration: a category 1 refusal naming
32    /// `descriptor:<label>` or `calibration:<label>` (spec 006, 3.4.1).
33    Parse {
34        subject: String,
35        detail: String,
36    },
37    /// A rules program that does not parse or validate.
38    Rules {
39        path: String,
40        detail: String,
41    },
42}
43
44/// Derives a rules program's descriptor from its bytes; the host owns the
45/// rules backend (spec 027, 3.3.2).
46pub type RulesHook<'h> = dyn FnMut(&str, &[u8]) -> Result<BackendDescriptor, String> + 'h;
47
48fn parse<T: Document>(host: &dyn Host, kind: &str, label: &str) -> Result<T, DepFail> {
49    let bytes = host
50        .read(label, DESCRIPTOR_V1.max_bytes)
51        .map_err(DepFail::Io)?;
52    T::parse(&bytes).map_err(|e: DocumentError| DepFail::Parse {
53        subject: format!("{kind}:{label}"),
54        detail: e.to_string(),
55    })
56}
57
58/// Read every dependency, in the order descriptors, rules, calibrations.
59pub fn load(
60    host: &dyn Host,
61    args: DepArgs<'_>,
62    rules: &mut RulesHook<'_>,
63) -> Result<Deps, DepFail> {
64    let mut deps = Deps::default();
65    for label in args.descriptors {
66        deps.descriptors.push(parse(host, "descriptor", label)?);
67    }
68    for label in args.rules {
69        let bytes = host
70            .read(label, DESCRIPTOR_V1.max_bytes)
71            .map_err(DepFail::Io)?;
72        let descriptor = rules(label, &bytes).map_err(|detail| DepFail::Rules {
73            path: label.clone(),
74            detail,
75        })?;
76        deps.descriptors.push(descriptor);
77    }
78    for label in args.calibrations {
79        deps.calibrations.push(parse(host, "calibration", label)?);
80    }
81    Ok(deps)
82}
83
84/// A host without a rules backend: any rules program is refused.
85pub fn no_rules(_: &str, _: &[u8]) -> Result<BackendDescriptor, String> {
86    Err("this host runs no rules backend".into())
87}