use super::{
AttemptJournalError, AttemptJournalGuard, BackupLayoutGuard, JournalLock, JournalLockError,
OperationPlanPersistenceError, PersistenceError, create_json_durable, read_json,
read_operation_plan,
};
use crate::{
model::{
artifacts::ArtifactChecksumRecord,
execution_settlement::{ExecutionSettlementRecord, MAX_EXECUTION_SETTLEMENT_BYTES},
},
policy::execution_settlement::{ExecutionSettlementPolicyError, validate},
};
use std::path::Path;
use thiserror::Error;
pub fn create_execution_settlement(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
) -> Result<(), ExecutionSettlementPersistenceError> {
create_with(layout, record, create_json_durable)
}
fn create_with(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
writer: impl FnOnce(&Path, &ExecutionSettlementRecord) -> Result<(), PersistenceError>,
) -> Result<(), ExecutionSettlementPersistenceError> {
layout.check_root()?;
let path = layout.root().join("execution-settlement.json");
let _lock = JournalLock::acquire(&path)?;
check_size(record)?;
validate_retained(layout, record)?;
writer(&path, record)?;
Ok(())
}
pub fn read_execution_settlement(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
expected: &ArtifactChecksumRecord,
) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
layout.check_root()?;
let path = layout.root().join("execution-settlement.json");
let _lock = JournalLock::acquire(&path)?;
let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
check_size(&record)?;
if record.plan_intent() != expected_plan || &record.digest() != expected {
return Err(ExecutionSettlementPersistenceError::DigestMismatch);
}
validate_retained(layout, &record)?;
Ok(record)
}
fn validate_retained(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
) -> Result<(), ExecutionSettlementPersistenceError> {
let plan = read_operation_plan(layout, record.plan_intent())?;
let mut journals = Vec::with_capacity(plan.operations().len());
for authority in plan.attempt_authorities()? {
let guard = AttemptJournalGuard::open(layout, &authority)?;
journals.push(guard.record()?.clone());
}
let references: Vec<_> = journals.iter().collect();
validate(&plan, &references, record)?;
layout.check_root()?;
Ok(())
}
fn check_size(record: &ExecutionSettlementRecord) -> Result<(), PersistenceError> {
if serde_json::to_vec_pretty(record)?.len() as u64 > MAX_EXECUTION_SETTLEMENT_BYTES {
return Err(PersistenceError::RecordTooLarge {
limit: MAX_EXECUTION_SETTLEMENT_BYTES,
});
}
Ok(())
}
#[derive(Debug, Error)]
pub enum ExecutionSettlementPersistenceError {
#[error("execution settlement digest mismatch")]
DigestMismatch,
#[error(transparent)]
Plan(#[from] OperationPlanPersistenceError),
#[error(transparent)]
Authority(#[from] crate::model::operation_plan::OperationPlanError),
#[error(transparent)]
Journal(#[from] AttemptJournalError),
#[error(transparent)]
Policy(#[from] ExecutionSettlementPolicyError),
#[error(transparent)]
Lock(#[from] JournalLockError),
#[error(transparent)]
Persistence(#[from] PersistenceError),
}
#[cfg(all(test, unix))]
mod tests;