lb_rs/model/
errors.rs

1use std::backtrace::Backtrace;
2use std::fmt::Display;
3use std::fmt::{self, Formatter};
4use std::io;
5use std::panic::Location;
6use std::sync::PoisonError;
7
8use hmac::crypto_mac::MacError;
9use serde::ser::SerializeStruct;
10use serde::{Serialize, Serializer};
11use uuid::Uuid;
12
13use crate::io::network::ApiError;
14use crate::model::ValidationFailure;
15
16use super::api;
17
18pub type LbResult<T> = Result<T, LbErr>;
19
20#[derive(Debug)]
21pub struct LbErr {
22    pub kind: LbErrKind,
23    pub backtrace: Option<Backtrace>,
24}
25
26/// Using this within core has limited meaning as the unexpected / expected error
27/// calculation that happens in lib.rs won't have taken place. So in some sense
28/// printing this out anywhere within core is going to be _unexpected_
29impl Display for LbErr {
30    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
31        write!(f, "{}", self.kind)
32    }
33}
34
35/// The purpose of this Display implementation is to provide uniformity for the
36/// description of errors that a customer may see. And to provide a productivity
37/// boost for the UI developer processing (and ultimately showing) these errors.
38/// If an error is not expected to be propegated outside of this crate the
39/// the language associated with the error will reflect that (and may use an
40/// uglier debug impl for details).
41impl Display for LbErrKind {
42    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43        match self {
44            LbErrKind::AccountExists => write!(f, "An account already exists"),
45            LbErrKind::AccountNonexistent => write!(f, "You need an account to do that"),
46            LbErrKind::AccountStringCorrupted => write!(f, "That account key is invalid"),
47            LbErrKind::AlreadyCanceled => write!(f, "Your subscription has already been cancelled"),
48            LbErrKind::AlreadyPremium => write!(f, "Your account is already premium"),
49            LbErrKind::AppStoreAccountAlreadyLinked => {
50                write!(f, "Your account is already linked to the App Store")
51            }
52            LbErrKind::CannotCancelSubscriptionForAppStore => {
53                write!(f, "You cannot cancel an app store subscription from here")
54            }
55            LbErrKind::CardDecline => write!(f, "Your card was declined"),
56            LbErrKind::CardExpired => write!(f, "Your card is expired"),
57            LbErrKind::CardInsufficientFunds => write!(f, "This card has insufficient funds"),
58            LbErrKind::CardInvalidCvc => write!(f, "Your CVC is invalid"),
59            LbErrKind::CardInvalidExpMonth => write!(f, "Your expiration month is invalid"),
60            LbErrKind::CardInvalidExpYear => write!(f, "Your expiration year is invalid"),
61            LbErrKind::CardInvalidNumber => write!(f, "Your card number is invalid"),
62            LbErrKind::CardNotSupported => write!(f, "That card is not supported by stripe"),
63            LbErrKind::ClientUpdateRequired => {
64                write!(f, "You must update your Lockbook to do that")
65            }
66            LbErrKind::CurrentUsageIsMoreThanNewTier => {
67                write!(f, "You need to delete some files before downgrading your usage")
68            }
69            LbErrKind::DiskPathInvalid => write!(f, "That disk path is invalid"),
70            LbErrKind::DiskPathTaken => write!(f, "That disk path is not available"),
71            LbErrKind::DrawingInvalid => write!(f, "That drawing is invalid"),
72            LbErrKind::ExistingRequestPending => {
73                write!(f, "Existing billing request in progress, please wait and try again")
74            }
75            LbErrKind::FileNameContainsSlash => write!(f, "A file name cannot contain slashes"),
76            LbErrKind::FileNameTooLong => write!(f, "That file name is too long"),
77            LbErrKind::FileNameEmpty => write!(f, "A file name cannot be empty"),
78            LbErrKind::FileNonexistent => write!(f, "That file does not exist"),
79            LbErrKind::FileNotDocument => write!(f, "That file is not a document"),
80            LbErrKind::FileParentNonexistent => write!(f, "Could not find that file parent"),
81            LbErrKind::InsufficientPermission => {
82                write!(f, "You don't have the permission to do that")
83            }
84            LbErrKind::InvalidPurchaseToken => write!(f, "That purchase token is invalid"),
85            LbErrKind::InvalidAuthDetails => {
86                write!(f, "Our server failed to authenticate your request, please try again")
87            }
88            LbErrKind::KeyPhraseInvalid => {
89                write!(f, "Your private key phrase is wrong")
90            }
91            LbErrKind::NotPremium => write!(f, "You do not have a premium subscription"),
92            LbErrKind::UsageIsOverDataCap => {
93                write!(f, "You're out of space")
94            }
95            LbErrKind::UsageIsOverFreeTierDataCap => {
96                write!(f, "You're out of space, you can purchase additional space")
97            }
98            LbErrKind::OldCardDoesNotExist => write!(f, "No existing card found"),
99            LbErrKind::PathContainsEmptyFileName => {
100                write!(f, "That path contains an empty file name")
101            }
102            LbErrKind::RootModificationInvalid => write!(f, "You cannot modify your root"),
103            LbErrKind::RootNonexistent => write!(f, "Could not find your root file"),
104            LbErrKind::ServerDisabled => write!(
105                f,
106                "The server is not accepting this action at the moment, please try again later"
107            ),
108            LbErrKind::ServerUnreachable => write!(f, "Could not reach server"),
109            LbErrKind::ShareAlreadyExists => write!(f, "That share already exists"),
110            LbErrKind::ShareNonexistent => write!(f, "That share does not exist"),
111            LbErrKind::TryAgain => write!(f, "Please try again"),
112            LbErrKind::UsernameInvalid => write!(f, "That username is invalid"),
113            LbErrKind::UsernameNotFound => write!(f, "That username is not found"),
114            LbErrKind::UsernamePublicKeyMismatch => {
115                write!(f, "That username doesn't match that public key")
116            }
117            LbErrKind::UsernameTaken => write!(f, "That username is not available"),
118            LbErrKind::Unexpected(msg) => write!(f, "Unexpected error: {msg}"),
119            LbErrKind::AlreadySyncing => {
120                write!(f, "A sync is already in progress, cannot begin another sync at this time!")
121            }
122            LbErrKind::ReReadRequired => {
123                write!(f, "This document changed since you last read it, please re-read it!")
124            }
125            LbErrKind::Validation(validation_failure) => match validation_failure {
126                ValidationFailure::Cycle(_) => write!(f, "Cannot move a folder into itself"),
127                ValidationFailure::NonFolderWithChildren(_) => {
128                    write!(f, "A document or a link was treated as a folder.")
129                }
130                ValidationFailure::PathConflict(_) => {
131                    write!(f, "A file already exists at that path.")
132                }
133                ValidationFailure::DeletedFileUpdated(id) => {
134                    write!(f, "this file has been deleted {id}")
135                }
136                ValidationFailure::FileNameTooLong(_) => write!(f, "this filename is too long!"),
137                ValidationFailure::OwnedLink(_) => {
138                    write!(f, "you cannot have a link to a file you own")
139                }
140                ValidationFailure::BrokenLink(_) => write!(f, "that link target does not exist!"),
141                ValidationFailure::DuplicateLink { .. } => {
142                    write!(f, "you already have a link to that file")
143                }
144                ValidationFailure::SharedLink { .. } => {
145                    write!(f, "you cannot place a link inside a shared folder!")
146                }
147                _ => write!(f, "unexpected validation failure: {validation_failure:?}"),
148            },
149            LbErrKind::Diff(diff_error) => {
150                write!(f, "unexpected diff error: {diff_error:?}")
151            }
152            LbErrKind::Sign(sign_error) => {
153                write!(f, "unexpected signing error: {sign_error:?}")
154            }
155            LbErrKind::Crypto(crypto_error) => {
156                write!(f, "unexpected crypto error: {crypto_error:?}")
157            }
158        }
159    }
160}
161
162impl From<LbErrKind> for LbErr {
163    fn from(kind: LbErrKind) -> Self {
164        Self { kind, backtrace: Some(Backtrace::force_capture()) }
165    }
166}
167
168pub trait Unexpected<T> {
169    fn map_unexpected(self) -> LbResult<T>;
170}
171
172impl<T, E: std::fmt::Debug> Unexpected<T> for Result<T, E> {
173    #[track_caller]
174    fn map_unexpected(self) -> LbResult<T> {
175        let location = Location::caller();
176        self.map_err(|err| {
177            LbErrKind::Unexpected(format!(
178                "unexpected error at {}:{} {err:?}",
179                location.file(),
180                location.line(),
181            ))
182            .into()
183        })
184    }
185}
186
187#[derive(Debug)]
188pub struct UnexpectedError {
189    pub msg: String,
190    pub backtrace: Option<Backtrace>,
191}
192
193impl UnexpectedError {
194    pub fn new(s: impl ToString) -> Self {
195        Self { msg: s.to_string(), backtrace: Some(Backtrace::force_capture()) }
196    }
197}
198
199impl fmt::Display for UnexpectedError {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
201        write!(f, "unexpected error: {}", self.msg)
202    }
203}
204
205impl From<LbErr> for UnexpectedError {
206    fn from(err: LbErr) -> Self {
207        Self { msg: format!("{:?}", err.kind), backtrace: err.backtrace }
208    }
209}
210
211impl<T> From<PoisonError<T>> for UnexpectedError {
212    fn from(err: PoisonError<T>) -> Self {
213        Self::new(format!("{:#?}", err))
214    }
215}
216
217impl From<crossbeam::channel::RecvError> for UnexpectedError {
218    fn from(err: crossbeam::channel::RecvError) -> Self {
219        Self::new(format!("{:#?}", err))
220    }
221}
222
223impl From<crossbeam::channel::RecvTimeoutError> for UnexpectedError {
224    fn from(err: crossbeam::channel::RecvTimeoutError) -> Self {
225        Self::new(format!("{:#?}", err))
226    }
227}
228
229impl<T> From<crossbeam::channel::SendError<T>> for UnexpectedError {
230    fn from(err: crossbeam::channel::SendError<T>) -> Self {
231        Self::new(format!("{:#?}", err))
232    }
233}
234
235impl Serialize for UnexpectedError {
236    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
237    where
238        S: Serializer,
239    {
240        let mut state = serializer.serialize_struct("UnexpectedError", 2)?;
241        state.serialize_field("tag", "Unexpected")?;
242        state.serialize_field("content", &self.msg)?;
243        state.end()
244    }
245}
246
247#[macro_export]
248macro_rules! unexpected_only {
249    ($base:literal $(, $args:tt )*) => {{
250        debug!($base $(, $args )*);
251        debug!("{:?}", std::backtrace::Backtrace::force_capture());
252        debug!($base $(, $args )*);
253        UnexpectedError::new(format!($base $(, $args )*))
254    }};
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum LbErrKind {
259    AccountExists,
260    AccountNonexistent,
261    AccountStringCorrupted,
262    AlreadyCanceled,
263    AlreadyPremium,
264    AppStoreAccountAlreadyLinked,
265    AlreadySyncing,
266    // todo: group billing
267    CannotCancelSubscriptionForAppStore,
268    CardDecline,
269    CardExpired,
270    CardInsufficientFunds,
271    CardInvalidCvc,
272    CardInvalidExpMonth,
273    CardInvalidExpYear,
274    CardInvalidNumber,
275    CardNotSupported,
276    ClientUpdateRequired,
277    CurrentUsageIsMoreThanNewTier,
278    DiskPathInvalid,
279    DiskPathTaken,
280    DrawingInvalid,
281    ExistingRequestPending,
282    // todo: Group
283    FileNameContainsSlash,
284    // todo: #[deprecated]
285    FileNameTooLong,
286    FileNameEmpty,
287    FileNonexistent,
288    FileNotDocument,
289    FileParentNonexistent,
290    InsufficientPermission,
291    InvalidPurchaseToken,
292    InvalidAuthDetails,
293    KeyPhraseInvalid,
294    NotPremium,
295    UsageIsOverDataCap,
296    UsageIsOverFreeTierDataCap,
297    OldCardDoesNotExist,
298    PathContainsEmptyFileName,
299    RootModificationInvalid,
300    RootNonexistent,
301    ServerDisabled,
302    ServerUnreachable,
303    ShareAlreadyExists,
304    ShareNonexistent,
305    TryAgain,
306    // todo: group username errors
307    UsernameInvalid,
308    UsernameNotFound,
309    UsernamePublicKeyMismatch,
310    UsernameTaken,
311    ReReadRequired,
312    Diff(DiffError),
313
314    /// Errors that describe invalid modifications to trees. See [ValidationFailure] for more info
315    Validation(ValidationFailure),
316    Sign(SignError),
317    Crypto(CryptoError),
318
319    /// If no programmer in any part of the stack (including tests) expects
320    /// to see a particular error, we debug format the underlying error to
321    /// keep the number of error types in check. Commonly used for errors
322    /// originating in other crates.
323    Unexpected(String),
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327pub enum DiffError {
328    OldVersionIncorrect,
329    OldFileNotFound,
330    OldVersionRequired,
331    DiffMalformed,
332    HmacModificationInvalid,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
336pub enum SignError {
337    SignatureInvalid,
338    // todo: candidate for unexpected
339    SignatureParseError(libsecp256k1::Error),
340    WrongPublicKey,
341    SignatureInTheFuture(u64),
342    SignatureExpired(u64),
343}
344
345#[derive(Debug, Clone, PartialEq, Eq)]
346pub enum CryptoError {
347    Decryption(aead::Error),
348    HmacVerification(MacError),
349}
350
351impl From<bincode::Error> for LbErr {
352    fn from(err: bincode::Error) -> Self {
353        core_err_unexpected(err).into()
354    }
355}
356
357pub fn core_err_unexpected<T: fmt::Debug>(err: T) -> LbErrKind {
358    LbErrKind::Unexpected(format!("{:?}", err))
359}
360
361// todo call location becomes useless here, and we want that
362pub fn unexpected<T: fmt::Debug>(err: T) -> LbErr {
363    LbErrKind::Unexpected(format!("{:?}", err)).into()
364}
365
366impl From<db_rs::DbError> for LbErr {
367    fn from(err: db_rs::DbError) -> Self {
368        core_err_unexpected(err).into()
369    }
370}
371
372impl<G> From<PoisonError<G>> for LbErr {
373    fn from(err: PoisonError<G>) -> Self {
374        core_err_unexpected(err).into()
375    }
376}
377
378impl From<io::Error> for LbErr {
379    fn from(e: io::Error) -> Self {
380        match e.kind() {
381            io::ErrorKind::NotFound
382            | io::ErrorKind::PermissionDenied
383            | io::ErrorKind::InvalidInput => LbErrKind::DiskPathInvalid,
384            io::ErrorKind::AlreadyExists => LbErrKind::DiskPathTaken,
385            _ => core_err_unexpected(e),
386        }
387        .into()
388    }
389}
390
391impl From<serde_json::Error> for LbErr {
392    fn from(err: serde_json::Error) -> Self {
393        LbErrKind::Unexpected(format!("{err}")).into()
394    }
395}
396
397impl From<ApiError<api::NewAccountError>> for LbErr {
398    fn from(err: ApiError<api::NewAccountError>) -> Self {
399        match err {
400            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
401            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
402            ApiError::Endpoint(api::NewAccountError::UsernameTaken) => LbErrKind::UsernameTaken,
403            ApiError::Endpoint(api::NewAccountError::InvalidUsername) => LbErrKind::UsernameInvalid,
404            ApiError::Endpoint(api::NewAccountError::Disabled) => LbErrKind::ServerDisabled,
405            e => core_err_unexpected(e),
406        }
407        .into()
408    }
409}
410
411impl From<ApiError<api::GetPublicKeyError>> for LbErr {
412    fn from(err: ApiError<api::GetPublicKeyError>) -> Self {
413        match err {
414            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
415            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
416            ApiError::Endpoint(api::GetPublicKeyError::UserNotFound) => {
417                LbErrKind::AccountNonexistent
418            }
419            e => core_err_unexpected(e),
420        }
421        .into()
422    }
423}
424
425impl From<ApiError<api::GetUsernameError>> for LbErr {
426    fn from(err: ApiError<api::GetUsernameError>) -> Self {
427        match err {
428            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
429            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
430            ApiError::Endpoint(api::GetUsernameError::UserNotFound) => {
431                LbErrKind::AccountNonexistent
432            }
433            e => core_err_unexpected(e),
434        }
435        .into()
436    }
437}
438
439impl From<ApiError<api::GetFileIdsError>> for LbErr {
440    fn from(e: ApiError<api::GetFileIdsError>) -> Self {
441        match e {
442            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
443            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
444            e => core_err_unexpected(e),
445        }
446        .into()
447    }
448}
449
450impl From<ApiError<api::GetUpdatesError>> for LbErr {
451    fn from(e: ApiError<api::GetUpdatesError>) -> Self {
452        match e {
453            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
454            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
455            e => core_err_unexpected(e),
456        }
457        .into()
458    }
459}
460
461impl From<ApiError<api::GetDocumentError>> for LbErr {
462    fn from(e: ApiError<api::GetDocumentError>) -> Self {
463        match e {
464            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
465            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
466            e => core_err_unexpected(e),
467        }
468        .into()
469    }
470}
471
472impl From<ApiError<api::UpsertError>> for LbErr {
473    fn from(e: ApiError<api::UpsertError>) -> Self {
474        match e {
475            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
476            ApiError::Endpoint(api::UpsertError::UsageIsOverDataCap) => {
477                LbErrKind::UsageIsOverDataCap
478            }
479            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
480            e => core_err_unexpected(e),
481        }
482        .into()
483    }
484}
485
486impl From<ApiError<api::ChangeDocError>> for LbErr {
487    fn from(e: ApiError<api::ChangeDocError>) -> Self {
488        match e {
489            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
490            ApiError::Endpoint(api::ChangeDocError::UsageIsOverDataCap) => {
491                LbErrKind::UsageIsOverDataCap
492            }
493            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
494            e => core_err_unexpected(e),
495        }
496        .into()
497    }
498}
499
500impl From<ApiError<api::GetUsageError>> for LbErr {
501    fn from(e: ApiError<api::GetUsageError>) -> Self {
502        match e {
503            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
504            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
505            e => core_err_unexpected(e),
506        }
507        .into()
508    }
509}
510
511#[derive(Debug, Clone, Serialize)]
512pub enum Warning {
513    EmptyFile(Uuid),
514    InvalidUTF8(Uuid),
515}
516
517impl fmt::Display for Warning {
518    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519        match self {
520            Self::EmptyFile(id) => write!(f, "empty file: {}", id),
521            Self::InvalidUTF8(id) => write!(f, "invalid utf8 in file: {}", id),
522        }
523    }
524}