xmtp_db/
errors.rs

1use diesel::result::DatabaseErrorKind;
2use thiserror::Error;
3
4use crate::group_intent::GroupIntentError;
5
6use super::{
7    refresh_state::EntityKind,
8    sql_key_store::{self, SqlKeyStoreError},
9};
10use xmtp_common::{BoxDynError, RetryableError, retryable};
11use xmtp_proto::types::{Cursor, InstallationId};
12
13pub struct Mls;
14
15#[derive(Debug, Error)]
16pub enum StorageError {
17    #[error(transparent)]
18    DieselConnect(#[from] diesel::ConnectionError),
19    #[error(transparent)]
20    DieselResult(#[from] diesel::result::Error),
21    #[error("Error migrating database {0}")]
22    MigrationError(#[from] BoxDynError),
23    #[error(transparent)]
24    NotFound(#[from] NotFound),
25    #[error(transparent)]
26    Duplicate(DuplicateItem),
27    #[error(transparent)]
28    OpenMlsStorage(#[from] SqlKeyStoreError),
29    #[error("Transaction was intentionally rolled back")]
30    IntentionalRollback,
31    #[error("failed to deserialize from db")]
32    DbDeserialize,
33    #[error("failed to serialize for db")]
34    DbSerialize,
35    #[error("required fields missing from stored db type {0}")]
36    Builder(#[from] derive_builder::UninitializedFieldError),
37    #[error(transparent)]
38    Platform(#[from] crate::database::PlatformStorageError),
39    #[error("decoding from database failed {}", _0)]
40    Prost(#[from] prost::DecodeError),
41    #[error(transparent)]
42    Conversion(#[from] xmtp_proto::ConversionError),
43    #[error(transparent)]
44    Connection(#[from] crate::ConnectionError),
45    #[error("HMAC key must be 42 bytes")]
46    InvalidHmacLength,
47    #[error(transparent)]
48    GroupIntent(#[from] GroupIntentError),
49}
50
51impl From<std::convert::Infallible> for StorageError {
52    fn from(_: std::convert::Infallible) -> StorageError {
53        // infallible can never fail/occur
54        unreachable!("Infallible conversion should never fail.")
55    }
56}
57
58impl StorageError {
59    // release conn is a noop in wasm
60    #[cfg(target_arch = "wasm32")]
61    pub fn db_needs_connection(&self) -> bool {
62        false
63    }
64
65    #[cfg(not(target_arch = "wasm32"))]
66    pub fn db_needs_connection(&self) -> bool {
67        use StorageError::*;
68        matches!(
69            self,
70            Platform(crate::PlatformStorageError::PoolNeedsConnection)
71                | Connection(crate::ConnectionError::Platform(
72                    crate::PlatformStorageError::PoolNeedsConnection,
73                ))
74        )
75    }
76}
77
78#[derive(Error, Debug)]
79// Monolithic enum for all things lost
80pub enum NotFound {
81    #[error("group with welcome id {0} not found")]
82    GroupByWelcome(Cursor),
83    #[error("group with id {id} not found", id = hex::encode(_0))]
84    GroupById(Vec<u8>),
85    #[error("installation time for group {id}", id = hex::encode(_0))]
86    InstallationTimeForGroup(Vec<u8>),
87    #[error("inbox id for address {0} not found")]
88    InboxIdForAddress(String),
89    #[error("message id {id} not found", id = hex::encode(_0))]
90    MessageById(Vec<u8>),
91    #[error("dm by dm_target_inbox_id {0} not found")]
92    DmByInbox(String),
93    #[error("intent with id {0} for state Publish from ToPublish not found")]
94    IntentForToPublish(i32),
95    #[error("intent with id {0} for state ToPublish from Published not found")]
96    IntentForPublish(i32),
97    #[error("intent with id {0} for state Committed from Published not found")]
98    IntentForCommitted(i32),
99    #[error("Intent with id {0} not found")]
100    IntentById(i32),
101    #[error("refresh state with id {id} of kind {1} originating from node {2} not found", id = hex::encode(_0))]
102    RefreshStateByIdKindAndOriginator(Vec<u8>, EntityKind, i32),
103    #[error("Cipher salt for db at [`{0}`] not found")]
104    CipherSalt(String),
105    #[error("Sync Group for installation {0} not found")]
106    SyncGroup(InstallationId),
107    #[error("Key Package Reference {handle} not found", handle = hex::encode(_0))]
108    KeyPackageReference(Vec<u8>),
109    #[error("MLS Group Not Found")]
110    MlsGroup,
111    #[error("Post Quantum Key Pair not found")]
112    PostQuantumPrivateKey,
113    #[error("Key Package {kp} not found", kp = hex::encode(_0))]
114    KeyPackage(Vec<u8>),
115}
116
117#[derive(Error, Debug)]
118pub enum DuplicateItem {
119    #[error("the welcome id {0:?} already exists")]
120    WelcomeId(Option<Cursor>),
121    #[error("the commit log public key for group id {id} already exists", id = hex::encode(_0))]
122    CommitLogPublicKey(Vec<u8>),
123}
124
125impl RetryableError for DuplicateItem {
126    fn is_retryable(&self) -> bool {
127        use DuplicateItem::*;
128        match self {
129            WelcomeId(_) => false,
130            CommitLogPublicKey(_) => false,
131        }
132    }
133}
134
135impl RetryableError<Mls> for diesel::result::Error {
136    fn is_retryable(&self) -> bool {
137        use DatabaseErrorKind::*;
138        use diesel::result::Error::*;
139
140        match self {
141            DatabaseError(UniqueViolation, _) => false,
142            DatabaseError(CheckViolation, _) => false,
143            DatabaseError(NotNullViolation, _) => false,
144            // TODO: Figure out the full list of non-retryable errors.
145            // The diesel code has a comment that "this type is not meant to be exhaustively matched"
146            // so best is probably to return true here and map known errors to something else
147            // that is not retryable.
148            _ => true,
149        }
150    }
151}
152
153impl RetryableError for StorageError {
154    fn is_retryable(&self) -> bool {
155        match self {
156            Self::DieselConnect(_) => true,
157            Self::DieselResult(result) => retryable!(result),
158            Self::Duplicate(d) => retryable!(d),
159            Self::OpenMlsStorage(storage) => retryable!(storage),
160            Self::Platform(p) => retryable!(p),
161            Self::Connection(e) => retryable!(e),
162            Self::GroupIntent(e) => retryable!(e),
163            Self::MigrationError(_)
164            | Self::Conversion(_)
165            | Self::NotFound(_)
166            | Self::IntentionalRollback
167            | Self::DbDeserialize
168            | Self::DbSerialize
169            | Self::Builder(_)
170            | Self::InvalidHmacLength
171            | Self::Prost(_) => false,
172        }
173    }
174}
175
176impl RetryableError for NotFound {
177    fn is_retryable(&self) -> bool {
178        true
179    }
180}
181
182// OpenMLS KeyStore errors
183impl RetryableError<Mls> for openmls::group::AddMembersError<sql_key_store::SqlKeyStoreError> {
184    fn is_retryable(&self) -> bool {
185        match self {
186            Self::CreateCommitError(commit) => retryable!(commit),
187            Self::CommitBuilderStageError(commit_builder_stage) => retryable!(commit_builder_stage),
188            Self::StorageError(storage) => retryable!(storage),
189            Self::GroupStateError(group_state) => retryable!(group_state),
190            _ => false,
191        }
192    }
193}
194
195impl RetryableError<Mls> for openmls::group::CreateCommitError {
196    fn is_retryable(&self) -> bool {
197        false
198    }
199}
200
201impl RetryableError<Mls>
202    for openmls::treesync::LeafNodeUpdateError<sql_key_store::SqlKeyStoreError>
203{
204    fn is_retryable(&self) -> bool {
205        match self {
206            Self::Storage(storage) => retryable!(storage),
207            _ => false,
208        }
209    }
210}
211
212impl RetryableError<Mls> for openmls::key_packages::errors::KeyPackageNewError {
213    fn is_retryable(&self) -> bool {
214        matches!(self, Self::StorageError)
215    }
216}
217
218impl RetryableError<Mls> for openmls::group::RemoveMembersError<sql_key_store::SqlKeyStoreError> {
219    fn is_retryable(&self) -> bool {
220        match self {
221            Self::CreateCommitError(commit) => retryable!(commit),
222            Self::CommitBuilderStageError(commit_builder_stage) => retryable!(commit_builder_stage),
223            Self::GroupStateError(group_state) => retryable!(group_state),
224            Self::StorageError(storage) => retryable!(storage),
225            _ => false,
226        }
227    }
228}
229
230impl RetryableError<Mls> for openmls::group::NewGroupError<sql_key_store::SqlKeyStoreError> {
231    fn is_retryable(&self) -> bool {
232        match self {
233            Self::StorageError(storage) => retryable!(storage),
234            _ => false,
235        }
236    }
237}
238
239impl RetryableError<Mls>
240    for openmls::group::UpdateGroupMembershipError<sql_key_store::SqlKeyStoreError>
241{
242    fn is_retryable(&self) -> bool {
243        match self {
244            Self::CreateCommitError(create_commit) => retryable!(create_commit),
245            Self::GroupStateError(group_state) => retryable!(group_state),
246            Self::StorageError(storage) => retryable!(storage),
247            Self::CommitBuilderError(commit_builder) => retryable!(commit_builder),
248        }
249    }
250}
251
252impl RetryableError<Mls> for openmls::prelude::MlsGroupStateError {
253    fn is_retryable(&self) -> bool {
254        false
255    }
256}
257
258impl RetryableError<Mls>
259    for openmls::prelude::CreateGroupContextExtProposalError<sql_key_store::SqlKeyStoreError>
260{
261    fn is_retryable(&self) -> bool {
262        match self {
263            Self::CreateCommitError(create_commit) => retryable!(create_commit),
264            Self::StorageError(storage) => retryable!(storage),
265            _ => false,
266        }
267    }
268}
269
270impl RetryableError<Mls> for openmls::group::SelfUpdateError<sql_key_store::SqlKeyStoreError> {
271    fn is_retryable(&self) -> bool {
272        match self {
273            Self::CreateCommitError(commit) => retryable!(commit),
274            Self::CommitBuilderStageError(commit_builder_stage) => retryable!(commit_builder_stage),
275            Self::GroupStateError(group_state) => retryable!(group_state),
276            Self::StorageError(storage) => retryable!(storage),
277            _ => false,
278        }
279    }
280}
281
282impl RetryableError<Mls>
283    for openmls::group::CommitBuilderStageError<sql_key_store::SqlKeyStoreError>
284{
285    fn is_retryable(&self) -> bool {
286        match self {
287            Self::KeyStoreError(storage) => retryable!(storage),
288            Self::LibraryError(_) => false,
289        }
290    }
291}
292
293impl RetryableError<Mls>
294    for openmls::prelude::CreationFromExternalError<sql_key_store::SqlKeyStoreError>
295{
296    fn is_retryable(&self) -> bool {
297        match self {
298            Self::WriteToStorageError(storage) => retryable!(storage),
299            _ => false,
300        }
301    }
302}
303
304impl RetryableError<Mls> for openmls::prelude::WelcomeError<sql_key_store::SqlKeyStoreError> {
305    fn is_retryable(&self) -> bool {
306        match self {
307            Self::PublicGroupError(creation_err) => retryable!(creation_err),
308            Self::StorageError(storage) => retryable!(storage),
309            _ => false,
310        }
311    }
312}
313
314impl RetryableError<Mls> for openmls::group::MergeCommitError<sql_key_store::SqlKeyStoreError> {
315    fn is_retryable(&self) -> bool {
316        match self {
317            Self::StorageError(storage) => retryable!(storage),
318            Self::LibraryError(_) => false,
319        }
320    }
321}
322
323impl RetryableError<Mls>
324    for openmls::group::MergePendingCommitError<sql_key_store::SqlKeyStoreError>
325{
326    fn is_retryable(&self) -> bool {
327        match self {
328            Self::MlsGroupStateError(err) => retryable!(err),
329            Self::MergeCommitError(err) => retryable!(err),
330        }
331    }
332}
333
334impl RetryableError<Mls>
335    for openmls::prelude::ProcessMessageError<sql_key_store::SqlKeyStoreError>
336{
337    fn is_retryable(&self) -> bool {
338        match self {
339            Self::GroupStateError(err) => retryable!(err),
340            _ => false,
341        }
342    }
343}