molframe_core/contract/
reexecution.rs1use super::{AnalysisPolicy, ContentDigest, DictionaryVersion, Provenance};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub struct ReexecutionEnvironment<'a> {
11 pub molframe_version: &'a str,
13 pub schema_version: Option<&'a str>,
15 pub component_version: Option<&'a str>,
17}
18
19impl ReexecutionEnvironment<'static> {
20 #[must_use]
22 pub const fn current() -> Self {
23 Self {
24 molframe_version: env!("CARGO_PKG_VERSION"),
25 schema_version: None,
26 component_version: None,
27 }
28 }
29}
30
31#[derive(Clone, Debug)]
33pub struct Reexecution<T> {
34 pub value: T,
36 pub provenance: Provenance,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
42#[non_exhaustive]
43pub enum ReexecutionError {
44 #[error("provenance has no input digest")]
46 MissingInputDigest,
47 #[error("input fingerprint does not match provenance")]
49 InputMismatch,
50 #[error("policy fingerprint does not match the recorded policy")]
52 PolicyMismatch,
53 #[error("molframe version mismatch: expected {expected}, found {observed}")]
55 SoftwareVersionMismatch {
56 expected: Box<str>,
58 observed: Box<str>,
60 },
61 #[error("schema dictionary version mismatch")]
63 SchemaVersionMismatch,
64 #[error("component dictionary version mismatch")]
66 ComponentVersionMismatch,
67}
68
69pub fn reexecute_from_provenance<T>(
81 provenance: &Provenance,
82 input: &[u8],
83 environment: ReexecutionEnvironment<'_>,
84 run: impl FnOnce(&[u8], &AnalysisPolicy) -> T,
85) -> Result<Reexecution<T>, ReexecutionError> {
86 validate(provenance, input, environment)?;
87 Ok(Reexecution {
88 value: run(input, &provenance.policy),
89 provenance: provenance.clone(),
90 })
91}
92
93fn validate(
94 provenance: &Provenance,
95 input: &[u8],
96 environment: ReexecutionEnvironment<'_>,
97) -> Result<(), ReexecutionError> {
98 let Some(expected_input) = provenance.input_digest else {
99 return Err(ReexecutionError::MissingInputDigest);
100 };
101 if expected_input != ContentDigest::of(input) {
102 return Err(ReexecutionError::InputMismatch);
103 }
104 if provenance.policy_fingerprint != provenance.policy.fingerprint() {
105 return Err(ReexecutionError::PolicyMismatch);
106 }
107 if provenance.molframe_version != environment.molframe_version {
108 return Err(ReexecutionError::SoftwareVersionMismatch {
109 expected: provenance.molframe_version.into(),
110 observed: environment.molframe_version.into(),
111 });
112 }
113 check_dictionary(
114 provenance.schema_version.as_ref(),
115 environment.schema_version,
116 ReexecutionError::SchemaVersionMismatch,
117 )?;
118 check_dictionary(
119 provenance.component_version.as_ref(),
120 environment.component_version,
121 ReexecutionError::ComponentVersionMismatch,
122 )
123}
124
125fn check_dictionary(
126 expected: Option<&DictionaryVersion>,
127 observed: Option<&str>,
128 mismatch: ReexecutionError,
129) -> Result<(), ReexecutionError> {
130 if expected.map(DictionaryVersion::as_str) == observed {
131 Ok(())
132 } else {
133 Err(mismatch)
134 }
135}
136
137#[cfg(test)]
138#[path = "reexecution_tests.rs"]
139mod tests;