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