Skip to main content

haematite_cli/serve/storage/
failure.rs

1use std::sync::Arc;
2
3use haematite::{
4    ApiError, BranchCommitError, BranchError, BranchRefError, DatabaseError, ObserverError,
5};
6use serde::Serialize;
7
8use crate::serve::protocol::{WireOwnedCause, WireOwnerKind, WireRefusal};
9use crate::serve::receipt_fragments::{EncodedReceipt, ReceiptCodecError};
10use crate::serve::receipt_wire::WireCause;
11
12#[derive(Debug, Clone)]
13pub enum FailureCause {
14    Database(Arc<DatabaseError>),
15    #[cfg(test)]
16    Api(Arc<ApiError>),
17    #[cfg(test)]
18    LeafLog(Arc<haematite::LeafLogError>),
19    Observer(Arc<ObserverError>),
20    Branch(Arc<BranchError>),
21    BranchCommit(Arc<BranchCommitError>),
22    BranchRef(Arc<BranchRefError>),
23}
24
25impl FailureCause {
26    fn original(&self) -> &(dyn std::error::Error + 'static) {
27        match self {
28            Self::Database(error) => error.as_ref(),
29            #[cfg(test)]
30            Self::Api(error) => error.as_ref(),
31            #[cfg(test)]
32            Self::LeafLog(error) => error.as_ref(),
33            Self::Observer(error) => error.as_ref(),
34            Self::Branch(error) => error.as_ref(),
35            Self::BranchCommit(error) => error.as_ref(),
36            Self::BranchRef(error) => error.as_ref(),
37        }
38    }
39
40    fn database(&self) -> Option<&DatabaseError> {
41        match self {
42            Self::Database(error) => Some(error),
43            #[cfg(test)]
44            Self::Api(error) => match error.as_ref() {
45                ApiError::Storage(error) => Some(error),
46                _ => None,
47            },
48            #[cfg(test)]
49            Self::LeafLog(error) => match error.as_ref() {
50                haematite::LeafLogError::Storage(error) => Some(error),
51                _ => None,
52            },
53            Self::Observer(error) => match error.as_ref() {
54                ObserverError::Config(error) | ObserverError::Api(ApiError::Storage(error)) => {
55                    Some(error)
56                }
57                _ => None,
58            },
59            Self::Branch(_) | Self::BranchCommit(_) | Self::BranchRef(_) => None,
60        }
61    }
62
63    fn wire(&self) -> Result<WireOwnedCause, ReceiptCodecError> {
64        let kind = match self {
65            Self::Database(_) => WireOwnerKind::Database,
66            #[cfg(test)]
67            Self::Api(_) => WireOwnerKind::Api,
68            #[cfg(test)]
69            Self::LeafLog(_) => WireOwnerKind::LeafLog,
70            Self::Observer(_) => WireOwnerKind::Observer,
71            Self::Branch(_) => WireOwnerKind::Branch,
72            Self::BranchCommit(_) => WireOwnerKind::BranchCommit,
73            Self::BranchRef(_) => WireOwnerKind::BranchRef,
74        };
75        Ok(WireOwnedCause {
76            kind,
77            message: self.original().to_string(),
78            database: self.database().map(WireCause::project).transpose()?,
79        })
80    }
81}
82
83#[derive(Debug, Clone, Serialize)]
84pub struct Failure {
85    pub code: String,
86    pub message: String,
87    pub path: Option<String>,
88    #[serde(skip)]
89    pub(crate) cause: Option<FailureCause>,
90}
91
92impl std::fmt::Display for Failure {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        write!(f, "{}: {}", self.code, self.message)
95    }
96}
97impl std::error::Error for Failure {
98    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
99        self.cause.as_ref().map(FailureCause::original)
100    }
101}
102impl From<DatabaseError> for Failure {
103    fn from(error: DatabaseError) -> Self {
104        Self::database(error)
105    }
106}
107
108macro_rules! typed_constructor {
109    ($name:ident, $variant:ident, $error:ty) => {
110        pub(crate) fn $name(error: $error) -> Self {
111            Self::owned(FailureCause::$variant(Arc::new(error)))
112        }
113    };
114}
115
116impl Failure {
117    pub(crate) fn new(code: &str, message: impl Into<String>) -> Self {
118        Self {
119            code: code.to_owned(),
120            message: message.into(),
121            path: None,
122            cause: None,
123        }
124    }
125    pub(crate) fn storage(error: impl std::fmt::Display) -> Self {
126        Self::new("storage_unavailable", error.to_string())
127    }
128    typed_constructor!(database, Database, DatabaseError);
129    #[cfg(test)]
130    typed_constructor!(api, Api, ApiError);
131    #[cfg(test)]
132    typed_constructor!(leaf_log, LeafLog, haematite::LeafLogError);
133    typed_constructor!(observer, Observer, ObserverError);
134    typed_constructor!(branch, Branch, BranchError);
135    typed_constructor!(branch_commit, BranchCommit, BranchCommitError);
136    typed_constructor!(branch_ref, BranchRef, BranchRefError);
137
138    fn owned(cause: FailureCause) -> Self {
139        let (code, message) =
140            if let Some(DatabaseError::PathWriterRequired { found }) = cause.database() {
141                (
142                    "store_writer_unsupported",
143                    format!("store format stamp {found}; serve writes only bytes-v2 stores"),
144                )
145            } else {
146                ("storage_unavailable", cause.original().to_string())
147            };
148        Self {
149            code: code.to_owned(),
150            message,
151            path: None,
152            cause: Some(cause),
153        }
154    }
155
156    pub(crate) fn seal(&self) -> Result<Option<EncodedReceipt>, ReceiptCodecError> {
157        let Some(cause) = &self.cause else {
158            return Ok(None);
159        };
160        let receipt = match cause.database() {
161            Some(DatabaseError::BatchFailed(failure)) => Some(failure.receipt()),
162            _ => None,
163        };
164        EncodedReceipt::refusal(
165            receipt,
166            WireRefusal {
167                code: self.code.clone(),
168                message: self.message.clone(),
169                path: self.path.clone(),
170                cause: Some(cause.wire()?),
171            },
172        )
173        .map(Some)
174    }
175}