Skip to main content

coven/
coven_error.rs

1//! The app's error for opening, reading and writing a store: the database's
2//! failures, a sync step's, and the facade's own opening and custody work.
3
4use crate::task::{Bug, CarriesBug, Shutdown, TaskFailed};
5use crate::{
6    CovenMigrationError, DbError, FileError, KeyError, MigrationError, NetworkWatchError,
7    SettingsError, SqliteError, StoreId, StoreLockError, SyncError, WriteFailure,
8};
9use coven_database::OpenError;
10
11/// A store call's result, retaining its typed cause.
12pub type CovenResult<T> = Result<T, CovenError>;
13
14/// Failures of opening, reading and writing a store (§4, E1, E3).
15#[derive(Debug, thiserror::Error)]
16pub enum CovenError {
17    /// Reading device identity or an app callback's custody operation failed.
18    #[error(transparent)]
19    Key(KeyError),
20    /// An app callback failed and SQLite also failed to roll it back. Later
21    /// calls fail with `DbError::RollbackFailed` until the store is reopened (§4).
22    #[error("{operation}; rollback failed: {rollback}")]
23    Rollback {
24        /// The callback or database failure.
25        operation: Box<CovenError>,
26        /// SQLite's rollback failure.
27        #[source]
28        rollback: SqliteError,
29    },
30    /// File cleanup failed after the callback failed, retaining both.
31    #[error("file cleanup failed: {failures:?}; write failed: {write}")]
32    FileCleanup {
33        /// The callback's original error.
34        write: Box<CovenError>,
35        /// Each failed byte removal or pending-record update.
36        failures: Vec<DbError>,
37    },
38    /// The database or a write's validation failed.
39    #[error(transparent)]
40    Database(#[from] DbError),
41    /// A sync step failed inside an app's write, which did not commit, or
42    /// while the store opened, keeping sync's own error (E1, E3).
43    #[error(transparent)]
44    Sync(SyncError),
45    /// An app migration failed or cannot run on this schema.
46    #[error(transparent)]
47    Migration(#[from] MigrationError),
48    /// Coven's internal schema is unsupported or its migration cannot run.
49    #[error(transparent)]
50    CovenMigration(#[from] CovenMigrationError),
51    /// The directory's settings could not be read.
52    #[error(transparent)]
53    Settings(SettingsError),
54    /// A file operation in the store's directory failed.
55    #[error(transparent)]
56    File(FileError),
57    /// The writer's lock could not be acquired.
58    #[error(transparent)]
59    Lock(StoreLockError),
60    /// A required builder choice was not supplied (E1).
61    #[error("opening requires {field}")]
62    MissingConfiguration {
63        /// The missing choice.
64        field: &'static str,
65    },
66    /// The keychain holds no entry naming this directory's device; opening
67    /// changed nothing (§19.3).
68    #[error("store {store}: this device's keychain entry is missing")]
69    DeviceKeyMissing {
70        /// The store whose device entry is missing.
71        store: StoreId,
72    },
73    /// A connected store's identity custody holds no member keys either,
74    /// beside the missing device entry; only restoring with the restore code
75    /// continues. Opening changed nothing (§19.3, §13).
76    #[error("store {store}: this device's entry and member keys are missing")]
77    MemberKeysMissing {
78        /// The store whose member keys are missing.
79        store: StoreId,
80    },
81    /// The device is locked, and the protection class the app chose keeps
82    /// the store's files closed until it unlocks; opening changed nothing
83    /// the lock refused (api.md E1). The app waits for the unlock and opens
84    /// again.
85    #[error("{}: the device is locked, and the store's files are protected until it unlocks", path.display())]
86    DeviceLocked {
87        /// The first protected file the open was refused.
88        path: std::path::PathBuf,
89    },
90    /// The call's runtime is shutting down and dropped coven's work for it
91    /// on the blocking pool unstarted, such as opening's custody work, so
92    /// that work changed nothing (E15). A database call dropped this way is
93    /// `DbError::Shutdown`.
94    #[error(transparent)]
95    Shutdown(#[from] Shutdown),
96    /// Coven's work for the call panicked on the blocking pool, such as
97    /// opening's custody work or a sync step: a bug, which the caller
98    /// reports (E15). A database call that panicked is `DbError::Bug`.
99    #[error(transparent)]
100    Bug(#[from] Bug),
101    /// The platform refused to let coven watch the device's network, such
102    /// as an Android app without coven's Android library (api.md E1, E5).
103    #[error(transparent)]
104    NetworkWatch(#[from] NetworkWatchError),
105}
106
107impl CarriesBug for CovenError {
108    fn bug(&self) -> Option<&Bug> {
109        match self {
110            Self::Bug(bug) => Some(bug),
111            Self::Database(error) => error.bug(),
112            Self::Sync(error) => error.bug(),
113            Self::Key(error) => error.bug(),
114            Self::Lock(error) => error.bug(),
115            Self::File(error) => error.bug(),
116            Self::Settings(error) => error.bug(),
117            Self::Rollback { operation, .. } => operation.bug(),
118            Self::FileCleanup { write, failures } => {
119                write.bug().or_else(|| failures.iter().find_map(DbError::bug))
120            }
121            Self::Migration(MigrationError::Failed { source, .. })
122            | Self::CovenMigration(CovenMigrationError::Failed { source }) => source.bug(),
123            _ => None,
124        }
125    }
126}
127
128impl From<KeyError> for CovenError {
129    fn from(error: KeyError) -> Self {
130        match error {
131            KeyError::Bug(bug) => Self::Bug(bug),
132            error => Self::Key(error),
133        }
134    }
135}
136
137impl From<SettingsError> for CovenError {
138    fn from(error: SettingsError) -> Self {
139        match error {
140            SettingsError::Bug(bug) => Self::Bug(bug),
141            error => Self::Settings(error),
142        }
143    }
144}
145
146impl From<FileError> for CovenError {
147    fn from(error: FileError) -> Self {
148        match error {
149            FileError::Bug(bug) => Self::Bug(bug),
150            error => Self::File(error),
151        }
152    }
153}
154
155impl From<StoreLockError> for CovenError {
156    fn from(error: StoreLockError) -> Self {
157        match error {
158            StoreLockError::Bug(bug) => Self::Bug(bug),
159            error => Self::Lock(error),
160        }
161    }
162}
163
164impl From<TaskFailed> for CovenError {
165    fn from(failed: TaskFailed) -> Self {
166        failed.into_error()
167    }
168}
169
170impl From<SqliteError> for CovenError {
171    fn from(error: SqliteError) -> Self {
172        Self::Database(error.into())
173    }
174}
175
176/// Opening the database fails as the app's open call reports it, each
177/// cause under its own name.
178impl From<OpenError> for CovenError {
179    fn from(error: OpenError) -> Self {
180        match error {
181            OpenError::Database(error) => Self::Database(error),
182            OpenError::Migration(error) => Self::Migration(error),
183            OpenError::CovenMigration(error) => Self::CovenMigration(error),
184            OpenError::Settings(error) => Self::from(error),
185            OpenError::Lock(error) => Self::Lock(error),
186            OpenError::MissingConfiguration { field } => Self::MissingConfiguration { field },
187            OpenError::Bug(bug) => Self::Bug(bug),
188        }
189    }
190}
191
192/// A sync step's failure inside an app's write or while the store opens: a
193/// database failure as itself, a bug or shutdown as the call's own, and any
194/// other as the step's own error (§4, E15).
195impl From<SyncError> for CovenError {
196    fn from(error: SyncError) -> Self {
197        match error {
198            SyncError::Database(error) => Self::Database(error),
199            SyncError::Shutdown(shutdown) => Self::Shutdown(shutdown),
200            SyncError::Bug(bug) => Self::Bug(bug),
201            error => Self::Sync(error),
202        }
203    }
204}
205
206/// An app's write failing with `CovenError` keeps a failed rollback or byte
207/// removal in the error it wraps: the database's and a sync step's in their
208/// own, any other beside it (Appendix C.3).
209impl WriteFailure for CovenError {
210    fn with_rollback(self, rollback: SqliteError) -> Self {
211        match self {
212            Self::Database(error) => Self::Database(error.with_rollback(rollback)),
213            Self::Sync(error) => Self::Sync(error.with_rollback(rollback)),
214            error => Self::Rollback { operation: Box::new(error), rollback },
215        }
216    }
217    fn with_cleanup(write: Self, failures: Vec<DbError>) -> Self {
218        match write {
219            Self::Database(error) => Self::Database(DbError::with_cleanup(error, failures)),
220            Self::Sync(error) => Self::Sync(SyncError::with_cleanup(error, failures)),
221            error => Self::FileCleanup { write: Box::new(error), failures },
222        }
223    }
224}
225
226#[cfg(test)]
227#[path = "coven_error_tests.rs"]
228mod tests;