Skip to main content

lb_rs/model/
errors.rs

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