use super::{AnalysisPolicy, ContentDigest, DictionaryVersion, Provenance};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReexecutionEnvironment<'a> {
pub molframe_version: &'a str,
pub schema_version: Option<&'a str>,
pub component_version: Option<&'a str>,
}
impl ReexecutionEnvironment<'static> {
#[must_use]
pub const fn current() -> Self {
Self {
molframe_version: env!("CARGO_PKG_VERSION"),
schema_version: None,
component_version: None,
}
}
}
#[derive(Clone, Debug)]
pub struct Reexecution<T> {
pub value: T,
pub provenance: Provenance,
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum ReexecutionError {
#[error("provenance has no input digest")]
MissingInputDigest,
#[error("input fingerprint does not match provenance")]
InputMismatch,
#[error("policy fingerprint does not match the recorded policy")]
PolicyMismatch,
#[error("molframe version mismatch: expected {expected}, found {observed}")]
SoftwareVersionMismatch {
expected: Box<str>,
observed: Box<str>,
},
#[error("schema dictionary version mismatch")]
SchemaVersionMismatch,
#[error("component dictionary version mismatch")]
ComponentVersionMismatch,
}
pub fn reexecute_from_provenance<T>(
provenance: &Provenance,
input: &[u8],
environment: ReexecutionEnvironment<'_>,
run: impl FnOnce(&[u8], &AnalysisPolicy) -> T,
) -> Result<Reexecution<T>, ReexecutionError> {
validate(provenance, input, environment)?;
Ok(Reexecution {
value: run(input, &provenance.policy),
provenance: provenance.clone(),
})
}
fn validate(
provenance: &Provenance,
input: &[u8],
environment: ReexecutionEnvironment<'_>,
) -> Result<(), ReexecutionError> {
let Some(expected_input) = provenance.input_digest else {
return Err(ReexecutionError::MissingInputDigest);
};
if expected_input != ContentDigest::of(input) {
return Err(ReexecutionError::InputMismatch);
}
if provenance.policy_fingerprint != provenance.policy.fingerprint() {
return Err(ReexecutionError::PolicyMismatch);
}
if provenance.molframe_version != environment.molframe_version {
return Err(ReexecutionError::SoftwareVersionMismatch {
expected: provenance.molframe_version.into(),
observed: environment.molframe_version.into(),
});
}
check_dictionary(
provenance.schema_version.as_ref(),
environment.schema_version,
ReexecutionError::SchemaVersionMismatch,
)?;
check_dictionary(
provenance.component_version.as_ref(),
environment.component_version,
ReexecutionError::ComponentVersionMismatch,
)
}
fn check_dictionary(
expected: Option<&DictionaryVersion>,
observed: Option<&str>,
mismatch: ReexecutionError,
) -> Result<(), ReexecutionError> {
if expected.map(DictionaryVersion::as_str) == observed {
Ok(())
} else {
Err(mismatch)
}
}
#[cfg(test)]
#[path = "reexecution_tests.rs"]
mod tests;