Skip to main content

coven_sync/
refusal.rs

1//! Object-check verdicts shared by immediate errors and recorded reports (ยง19.1).
2
3use coven_crypto::CryptoError;
4use coven_database::DbError;
5use coven_merge::MergeError;
6use std::{error::Error, fmt, sync::Arc};
7
8/// One vocabulary for refused objects. Native causes survive immediate calls;
9/// persisted records retain only the variant. Equality ignores causes.
10#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
11pub enum Refusal {
12    /// Opening the object or its authenticated path failed.
13    Decryption {
14        /// The native check failure, absent on a recorded report.
15        #[serde(skip)]
16        cause: Option<Arc<CryptoError>>,
17    },
18    /// The author's signature did not verify.
19    Signature {
20        /// The native signature failure, absent on a recorded report.
21        #[serde(skip)]
22        cause: Option<Arc<CryptoError>>,
23    },
24    /// The bytes violate their format.
25    Parse {
26        /// The native decoder failure, absent on a recorded report.
27        #[serde(skip)]
28        cause: Option<Arc<dyn Error + Send + Sync>>,
29    },
30    /// The authenticated write failed merge or application-schema checks.
31    InvalidWrite {
32        /// The native validation failure, absent on a recorded report.
33        #[serde(skip)]
34        cause: Option<Arc<DbError>>,
35    },
36    /// The author's recorded view does not authorize this object.
37    NotAuthorized,
38    /// The timestamp or recorded past violates causality.
39    InvalidCausality {
40        /// The native merge failure, if this check produced one.
41        #[serde(skip)]
42        cause: Option<Arc<MergeError>>,
43    },
44    /// The object's identity disagrees with its path, author or key material.
45    WrongIdentity {
46        /// The native format, merge or key-material failure, if one was produced.
47        #[serde(skip)]
48        cause: Option<Arc<dyn Error + Send + Sync>>,
49    },
50    /// Authenticated file plaintext disagrees with the row's content hash.
51    ContentHash,
52}
53
54impl PartialEq for Refusal {
55    fn eq(&self, other: &Self) -> bool {
56        std::mem::discriminant(self) == std::mem::discriminant(other)
57    }
58}
59impl Eq for Refusal {}
60
61impl Error for Refusal {
62    fn source(&self) -> Option<&(dyn Error + 'static)> {
63        match self {
64            Self::Decryption { cause } | Self::Signature { cause } => {
65                cause.as_deref().map(|e| e as &dyn Error)
66            }
67            Self::Parse { cause } | Self::WrongIdentity { cause } => {
68                cause.as_deref().map(|e| e as &dyn Error)
69            }
70            Self::InvalidWrite { cause } => cause.as_deref().map(|e| e as &dyn Error),
71            Self::InvalidCausality { cause } => cause.as_deref().map(|e| e as &dyn Error),
72            Self::NotAuthorized | Self::ContentHash => None,
73        }
74    }
75}
76
77impl fmt::Display for Refusal {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.write_str(match self {
80            Self::Decryption { .. } => "decryption failed",
81            Self::Signature { .. } => "signature failed",
82            Self::Parse { .. } => "parse failed",
83            Self::InvalidWrite { .. } => "invalid write",
84            Self::NotAuthorized => "object is not authorized",
85            Self::InvalidCausality { .. } => "invalid causality",
86            Self::WrongIdentity { .. } => "wrong object identity",
87            Self::ContentHash => "file content hash differs",
88        })?;
89        if let Some(cause) = self.source() {
90            write!(f, ": {cause}")?;
91        }
92        Ok(())
93    }
94}
95
96impl From<coven_format::Error> for Refusal {
97    fn from(error: coven_format::Error) -> Self {
98        match error {
99            coven_format::Error::Merge(
100                error @ (MergeError::CausalTimestamp(_)
101                | MergeError::CausalClosure(_)
102                | MergeError::DuplicateTimestamp(_, _)),
103            ) => Self::InvalidCausality { cause: Some(Arc::new(error)) },
104            coven_format::Error::Merge(error @ MergeError::TimestampDevice(_)) => {
105                Self::WrongIdentity { cause: Some(Arc::new(error)) }
106            }
107            error @ coven_format::Error::Invalid {
108                rule: coven_format::error::Rule::TimestampDevice,
109                ..
110            } => Self::WrongIdentity { cause: Some(Arc::new(error)) },
111            error => Self::Parse { cause: Some(Arc::new(error)) },
112        }
113    }
114}
115
116#[cfg(test)]
117#[path = "refusal_tests.rs"]
118mod tests;