use super::IcSnapshotUploadRequest;
use crate::model::{
attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
operation_plan::{OperationPlanError, OperationPlanRecord},
};
use thiserror::Error;
#[derive(Debug)]
pub struct IcSnapshotUploadAttempt<'request, 'source> {
plan: &'request OperationPlanRecord,
payload: &'request IcSnapshotUploadRequest<'source>,
authority: AttemptAuthorityRecord,
mutation_attempt: u32,
}
impl<'request, 'source> IcSnapshotUploadAttempt<'request, 'source> {
pub fn new(
plan: &'request OperationPlanRecord,
operation_sequence: u64,
journal: &AttemptJournalRecord,
payload: &'request IcSnapshotUploadRequest<'source>,
) -> Result<Self, IcSnapshotUploadAttemptError> {
let authority = original_authority(plan, operation_sequence, journal, payload)?;
let mutation_attempt = journal
.view()
.pending_mutation
.ok_or(IcSnapshotUploadAttemptError::NoPendingMutation)?;
let attempt = Self {
plan,
payload,
authority,
mutation_attempt,
};
attempt.validate_journal(journal)?;
Ok(attempt)
}
#[must_use]
pub const fn plan(&self) -> &'request OperationPlanRecord {
self.plan
}
#[must_use]
pub const fn payload(&self) -> &'request IcSnapshotUploadRequest<'source> {
self.payload
}
#[must_use]
pub const fn authority(&self) -> &AttemptAuthorityRecord {
&self.authority
}
#[must_use]
pub const fn mutation_attempt(&self) -> u32 {
self.mutation_attempt
}
pub fn validate_journal(
&self,
journal: &AttemptJournalRecord,
) -> Result<(), IcSnapshotUploadAttemptError> {
if journal.authority() != &self.authority {
return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
}
let view = journal.view();
if view.pending_mutation != Some(self.mutation_attempt) {
return Err(IcSnapshotUploadAttemptError::MutationMismatch);
}
if view.pending_observation.is_some() {
return Err(IcSnapshotUploadAttemptError::ObservationPending);
}
Ok(())
}
}
pub(crate) fn original_authority(
plan: &OperationPlanRecord,
operation_sequence: u64,
journal: &AttemptJournalRecord,
payload: &IcSnapshotUploadRequest<'_>,
) -> Result<AttemptAuthorityRecord, IcSnapshotUploadAttemptError> {
let authority = plan.attempt_authority(operation_sequence)?;
if journal.authority() != &authority {
return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
}
if payload.target() != authority.binding().target()
|| payload.binding_digest().hash() != authority.binding().request()
{
return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
}
if plan.context().network() != payload.source_plan().context().network()
|| plan.context().release() != payload.source_plan().context().release()
{
return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
}
Ok(authority)
}
#[derive(Debug, Error)]
pub enum IcSnapshotUploadAttemptError {
#[error("snapshot upload source network or release differs")]
SourceContextMismatch,
#[error("snapshot upload original authority mismatch")]
AuthorityMismatch,
#[error("snapshot upload original payload mismatch")]
PayloadMismatch,
#[error("snapshot upload requires an original pending mutation")]
NoPendingMutation,
#[error("snapshot upload original mutation differs")]
MutationMismatch,
#[error("snapshot upload recovery observation is pending")]
ObservationPending,
#[error(transparent)]
Plan(#[from] OperationPlanError),
}