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