haematite_cli/serve/storage/
failure.rs1use 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}