Skip to main content

coven/
bootstrap_error.rs

1//! Why opening a new installation failed, and how each cause it keeps
2//! becomes one of its variants (§13, E10, E15).
3
4use super::*;
5use coven_foundation::files::BootstrapDirectoryError;
6
7/// Opening a new installation failed. A join's unpublished work, and a
8/// recorded restore's, remain for an explicit retry unless the call was
9/// cancelled or the invite was settled; a restore before its record removes
10/// its own.
11#[derive(Debug, thiserror::Error)]
12pub enum BootstrapError {
13    /// Cancellation removes unpublished local data and leaves final custody alone.
14    #[error("opening cancelled")]
15    Cancelled,
16    /// Cancelling a join after its request may have been sent. The join stays
17    /// unfinished and resumable; it ends only by approval, decline or expiry
18    /// (§13.2).
19    #[error("the join request may have been sent; the join stays unfinished")]
20    JoinRequestSent,
21    /// No join is under way for this store on this device: none was started
22    /// here, or it has ended.
23    #[error("no join is under way for store {0}")]
24    NoUnfinishedJoin(StoreId),
25    /// Cancelling a restore once its device may be registered. The restore
26    /// stays unfinished and listed, and only finishes (§13.1).
27    #[error("the device may be registered; the restore stays unfinished")]
28    RestoreRegistered,
29    /// No restore is under way for this store on this device: none recorded
30    /// itself here, or it has ended.
31    #[error("no restore is under way for store {0}")]
32    NoUnfinishedRestore(StoreId),
33    /// No replacement of this device's installation of the store is under
34    /// way past its record: none recorded itself, or it has finished.
35    #[error("no replacement is under way for store {0}")]
36    NoUnfinishedReplacement(StoreId),
37    /// The code cannot be used for this operation.
38    #[error(transparent)]
39    Code(#[from] CodeError),
40    /// Preparing, publishing or removing the unpublished directory failed.
41    #[error(transparent)]
42    Directory(BootstrapDirectoryError),
43    /// Opening the database failed.
44    #[error(transparent)]
45    Store(CovenError),
46    /// The device is locked, and the protection class the app chose keeps
47    /// the installation's files closed until it unlocks (api.md E1). A join's
48    /// unfinished installation stays for the retry, and a recorded
49    /// restore's; a restore before its record removes its own. The app waits
50    /// for the unlock and calls again.
51    #[error("{}: the device is locked, and the store's files are protected until it unlocks", path.display())]
52    DeviceLocked {
53        /// The first protected file the lock refused.
54        path: std::path::PathBuf,
55    },
56    /// Provider admission, signed membership or snapshot loading failed.
57    #[error(transparent)]
58    Sync(SyncError),
59    /// Provider sign-in failed.
60    #[error(transparent)]
61    OAuth(coven_storage::providers::OAuthError),
62    /// Final custody could not be read or written.
63    #[error(transparent)]
64    SecureStorage(KeyError),
65    /// Cleanup failed too; retry can inspect both failures.
66    #[error("{operation}; cleanup failed: {cleanup}")]
67    Cleanup {
68        /// The initial failure.
69        operation: Box<BootstrapError>,
70        /// Its cleanup failure.
71        #[source]
72        cleanup: Box<BootstrapError>,
73    },
74    /// The loaded store is published; confirming durability or final cleanup
75    /// failed. Custody remains committed and the handle retains session-only keys.
76    #[error("store is published; final durability or cleanup failed: {source}")]
77    Published {
78        /// The open store, including its session-only custody.
79        handle: CovenHandle,
80        /// Failure after publication.
81        #[source]
82        source: Box<BootstrapError>,
83    },
84    /// The call's runtime is shutting down and dropped its keychain work
85    /// before it started, so nothing changed (E15).
86    #[error(transparent)]
87    Shutdown(#[from] crate::task::Shutdown),
88    /// The call's work panicked: a bug, which the caller reports (E15).
89    #[error(transparent)]
90    Bug(#[from] crate::task::Bug),
91}
92
93impl From<BootstrapDirectoryError> for BootstrapError {
94    fn from(error: BootstrapDirectoryError) -> Self {
95        if let BootstrapDirectoryError::Bug(bug) = error {
96            return Self::Bug(bug);
97        }
98        match error.device_locked() {
99            Some(path) => Self::DeviceLocked { path: path.to_owned() },
100            None => Self::Directory(error),
101        }
102    }
103}
104
105/// A code sync refuses is refused as the call's own code: a join's, as a
106/// restore's.
107impl From<SyncError> for BootstrapError {
108    fn from(error: SyncError) -> Self {
109        match error {
110            SyncError::Code(error) => Self::Code(error),
111            SyncError::Bug(bug) => Self::Bug(bug),
112            error => Self::Sync(error),
113        }
114    }
115}
116
117impl From<CovenError> for BootstrapError {
118    fn from(error: CovenError) -> Self {
119        match error {
120            CovenError::DeviceLocked { path } => Self::DeviceLocked { path },
121            CovenError::Bug(bug) => Self::Bug(bug),
122            error => Self::Store(error),
123        }
124    }
125}
126
127/// A database call's or opening's failure is the store's.
128impl From<DbError> for BootstrapError {
129    fn from(error: DbError) -> Self {
130        CovenError::from(error).into()
131    }
132}
133
134impl From<coven_database::OpenError> for BootstrapError {
135    fn from(error: coven_database::OpenError) -> Self {
136        CovenError::from(error).into()
137    }
138}
139
140impl From<coven_storage::providers::OAuthError> for BootstrapError {
141    fn from(error: coven_storage::providers::OAuthError) -> Self {
142        match error {
143            coven_storage::providers::OAuthError::Storage(StorageError::Bug(bug)) => Self::Bug(bug),
144            error => Self::OAuth(error),
145        }
146    }
147}
148
149impl From<StoreLockError> for BootstrapError {
150    fn from(error: StoreLockError) -> Self {
151        BootstrapDirectoryError::from(error).into()
152    }
153}
154
155impl From<coven_foundation::files::FileError> for BootstrapError {
156    fn from(error: coven_foundation::files::FileError) -> Self {
157        BootstrapDirectoryError::from(error).into()
158    }
159}
160
161impl From<KeyError> for BootstrapError {
162    fn from(error: KeyError) -> Self {
163        match error {
164            KeyError::Bug(bug) => Self::Bug(bug),
165            error => Self::SecureStorage(error),
166        }
167    }
168}
169
170impl crate::task::CarriesBug for BootstrapError {
171    fn bug(&self) -> Option<&crate::task::Bug> {
172        match self {
173            Self::Bug(bug) => Some(bug),
174            Self::Directory(error) => error.bug(),
175            Self::Store(error) => error.bug(),
176            Self::Sync(error) => error.bug(),
177            Self::OAuth(error) => error.bug(),
178            Self::SecureStorage(error) => error.bug(),
179            Self::Cleanup { operation, cleanup } => operation.bug().or_else(|| cleanup.bug()),
180            Self::Published { source, .. } => source.bug(),
181            _ => None,
182        }
183    }
184}
185
186impl From<crate::task::TaskFailed> for BootstrapError {
187    fn from(failed: crate::task::TaskFailed) -> Self {
188        failed.into_error()
189    }
190}