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 hmac::crypto_mac::MacError;
8use serde::ser::SerializeStruct;
9use serde::{Serialize, Serializer};
10use tracing::error;
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 log_and_ignore(self) -> Option<T>;
170    fn map_unexpected(self) -> LbResult<T>;
171}
172
173impl<T, E: std::fmt::Debug> Unexpected<T> for Result<T, E> {
174    #[track_caller]
175    fn map_unexpected(self) -> LbResult<T> {
176        let location = Location::caller();
177        self.map_err(|err| {
178            LbErrKind::Unexpected(format!(
179                "unexpected error at {}:{} {err:?}",
180                location.file(),
181                location.line(),
182            ))
183            .into()
184        })
185    }
186
187    #[track_caller]
188    fn log_and_ignore(self) -> Option<T> {
189        let location = Location::caller();
190        if let Err(e) = &self {
191            error!("error ignored at {}:{} {e:?}", location.file(), location.line());
192        }
193
194        self.ok()
195    }
196}
197
198#[derive(Debug)]
199pub struct UnexpectedError {
200    pub msg: String,
201    pub backtrace: Option<Backtrace>,
202}
203
204impl UnexpectedError {
205    pub fn new(s: impl ToString) -> Self {
206        Self { msg: s.to_string(), backtrace: Some(Backtrace::force_capture()) }
207    }
208}
209
210impl fmt::Display for UnexpectedError {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
212        write!(f, "unexpected error: {}", self.msg)
213    }
214}
215
216impl From<LbErr> for UnexpectedError {
217    fn from(err: LbErr) -> Self {
218        Self { msg: format!("{:?}", err.kind), backtrace: err.backtrace }
219    }
220}
221
222impl<T> From<PoisonError<T>> for UnexpectedError {
223    fn from(err: PoisonError<T>) -> Self {
224        Self::new(format!("{err:#?}"))
225    }
226}
227
228impl From<crossbeam::channel::RecvError> for UnexpectedError {
229    fn from(err: crossbeam::channel::RecvError) -> Self {
230        Self::new(format!("{err:#?}"))
231    }
232}
233
234impl From<crossbeam::channel::RecvTimeoutError> for UnexpectedError {
235    fn from(err: crossbeam::channel::RecvTimeoutError) -> Self {
236        Self::new(format!("{err:#?}"))
237    }
238}
239
240impl<T> From<crossbeam::channel::SendError<T>> for UnexpectedError {
241    fn from(err: crossbeam::channel::SendError<T>) -> Self {
242        Self::new(format!("{err:#?}"))
243    }
244}
245
246impl Serialize for UnexpectedError {
247    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
248    where
249        S: Serializer,
250    {
251        let mut state = serializer.serialize_struct("UnexpectedError", 2)?;
252        state.serialize_field("tag", "Unexpected")?;
253        state.serialize_field("content", &self.msg)?;
254        state.end()
255    }
256}
257
258#[macro_export]
259macro_rules! unexpected_only {
260    ($base:literal $(, $args:tt )*) => {{
261        debug!($base $(, $args )*);
262        debug!("{:?}", std::backtrace::Backtrace::force_capture());
263        debug!($base $(, $args )*);
264        UnexpectedError::new(format!($base $(, $args )*))
265    }};
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
269pub enum LbErrKind {
270    AccountExists,
271    AccountNonexistent,
272    AccountStringCorrupted,
273    AlreadyCanceled,
274    AlreadyPremium,
275    AppStoreAccountAlreadyLinked,
276    AlreadySyncing,
277    // todo: group billing
278    CannotCancelSubscriptionForAppStore,
279    CardDecline,
280    CardExpired,
281    CardInsufficientFunds,
282    CardInvalidCvc,
283    CardInvalidExpMonth,
284    CardInvalidExpYear,
285    CardInvalidNumber,
286    CardNotSupported,
287    ClientUpdateRequired,
288    CurrentUsageIsMoreThanNewTier,
289    DiskPathInvalid,
290    DiskPathTaken,
291    DrawingInvalid,
292    ExistingRequestPending,
293    // todo: Group
294    FileNameContainsSlash,
295    // todo: #[deprecated]
296    FileNameTooLong,
297    FileNameEmpty,
298    FileNonexistent,
299    FileNotDocument,
300    FileParentNonexistent,
301    InsufficientPermission,
302    InvalidPurchaseToken,
303    InvalidAuthDetails,
304    KeyPhraseInvalid,
305    NotPremium,
306    UsageIsOverDataCap,
307    UsageIsOverFreeTierDataCap,
308    OldCardDoesNotExist,
309    PathContainsEmptyFileName,
310    RootModificationInvalid,
311    RootNonexistent,
312    ServerDisabled,
313    ServerUnreachable,
314    ShareAlreadyExists,
315    ShareNonexistent,
316    TryAgain,
317    // todo: group username errors
318    UsernameInvalid,
319    UsernameNotFound,
320    UsernamePublicKeyMismatch,
321    UsernameTaken,
322    ReReadRequired,
323    Diff(DiffError),
324
325    /// Errors that describe invalid modifications to trees. See [ValidationFailure] for more info
326    Validation(ValidationFailure),
327    Sign(SignError),
328    Crypto(CryptoError),
329
330    /// If no programmer in any part of the stack (including tests) expects
331    /// to see a particular error, we debug format the underlying error to
332    /// keep the number of error types in check. Commonly used for errors
333    /// originating in other crates.
334    Unexpected(String),
335}
336
337#[derive(Debug, Clone, PartialEq, Eq)]
338pub enum DiffError {
339    OldVersionIncorrect,
340    OldFileNotFound,
341    OldVersionRequired,
342    DiffMalformed,
343    HmacModificationInvalid,
344}
345
346#[derive(Debug, Clone, PartialEq, Eq)]
347pub enum SignError {
348    SignatureInvalid,
349    // todo: candidate for unexpected
350    SignatureParseError(libsecp256k1::Error),
351    WrongPublicKey,
352    SignatureInTheFuture(u64),
353    SignatureExpired(u64),
354}
355
356#[derive(Debug, Clone, PartialEq, Eq)]
357pub enum CryptoError {
358    Decryption(aead::Error),
359    HmacVerification(MacError),
360}
361
362impl From<bincode::Error> for LbErr {
363    fn from(err: bincode::Error) -> Self {
364        core_err_unexpected(err).into()
365    }
366}
367
368pub fn core_err_unexpected<T: fmt::Debug>(err: T) -> LbErrKind {
369    LbErrKind::Unexpected(format!("{err:?}"))
370}
371
372// todo call location becomes useless here, and we want that
373pub fn unexpected<T: fmt::Debug>(err: T) -> LbErr {
374    LbErrKind::Unexpected(format!("{err:?}")).into()
375}
376
377impl From<db_rs::DbError> for LbErr {
378    fn from(err: db_rs::DbError) -> Self {
379        core_err_unexpected(err).into()
380    }
381}
382
383impl<G> From<PoisonError<G>> for LbErr {
384    fn from(err: PoisonError<G>) -> Self {
385        core_err_unexpected(err).into()
386    }
387}
388
389impl From<io::Error> for LbErr {
390    fn from(e: io::Error) -> Self {
391        match e.kind() {
392            io::ErrorKind::NotFound
393            | io::ErrorKind::PermissionDenied
394            | io::ErrorKind::InvalidInput => LbErrKind::DiskPathInvalid,
395            io::ErrorKind::AlreadyExists => LbErrKind::DiskPathTaken,
396            _ => core_err_unexpected(e),
397        }
398        .into()
399    }
400}
401
402impl From<serde_json::Error> for LbErr {
403    fn from(err: serde_json::Error) -> Self {
404        LbErrKind::Unexpected(format!("{err}")).into()
405    }
406}
407
408impl From<ApiError<api::NewAccountError>> for LbErr {
409    fn from(err: ApiError<api::NewAccountError>) -> Self {
410        match err {
411            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
412            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
413            ApiError::Endpoint(api::NewAccountError::UsernameTaken) => LbErrKind::UsernameTaken,
414            ApiError::Endpoint(api::NewAccountError::InvalidUsername) => LbErrKind::UsernameInvalid,
415            ApiError::Endpoint(api::NewAccountError::Disabled) => LbErrKind::ServerDisabled,
416            e => core_err_unexpected(e),
417        }
418        .into()
419    }
420}
421
422impl From<ApiError<api::GetPublicKeyError>> for LbErr {
423    fn from(err: ApiError<api::GetPublicKeyError>) -> Self {
424        match err {
425            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
426            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
427            ApiError::Endpoint(api::GetPublicKeyError::UserNotFound) => {
428                LbErrKind::AccountNonexistent
429            }
430            e => core_err_unexpected(e),
431        }
432        .into()
433    }
434}
435
436impl From<ApiError<api::GetUsernameError>> for LbErr {
437    fn from(err: ApiError<api::GetUsernameError>) -> Self {
438        match err {
439            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
440            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
441            ApiError::Endpoint(api::GetUsernameError::UserNotFound) => {
442                LbErrKind::AccountNonexistent
443            }
444            e => core_err_unexpected(e),
445        }
446        .into()
447    }
448}
449
450impl From<ApiError<api::GetFileIdsError>> for LbErr {
451    fn from(e: ApiError<api::GetFileIdsError>) -> 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::GetUpdatesError>> for LbErr {
462    fn from(e: ApiError<api::GetUpdatesError>) -> 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::GetDocumentError>> for LbErr {
473    fn from(e: ApiError<api::GetDocumentError>) -> Self {
474        match e {
475            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
476            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
477            e => core_err_unexpected(e),
478        }
479        .into()
480    }
481}
482
483impl From<ApiError<api::UpsertError>> for LbErr {
484    fn from(e: ApiError<api::UpsertError>) -> Self {
485        match e {
486            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
487            ApiError::Endpoint(api::UpsertError::UsageIsOverDataCap) => {
488                LbErrKind::UsageIsOverDataCap
489            }
490            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
491            e => core_err_unexpected(e),
492        }
493        .into()
494    }
495}
496
497impl From<ApiError<api::ChangeDocError>> for LbErr {
498    fn from(e: ApiError<api::ChangeDocError>) -> Self {
499        match e {
500            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
501            ApiError::Endpoint(api::ChangeDocError::UsageIsOverDataCap) => {
502                LbErrKind::UsageIsOverDataCap
503            }
504            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
505            e => core_err_unexpected(e),
506        }
507        .into()
508    }
509}
510
511impl From<ApiError<api::GetUsageError>> for LbErr {
512    fn from(e: ApiError<api::GetUsageError>) -> Self {
513        match e {
514            ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
515            ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
516            e => core_err_unexpected(e),
517        }
518        .into()
519    }
520}
521
522#[derive(Debug, Clone, Serialize)]
523pub enum Warning {
524    EmptyFile(Uuid),
525    InvalidUTF8(Uuid),
526}
527
528impl fmt::Display for Warning {
529    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530        match self {
531            Self::EmptyFile(id) => write!(f, "empty file: {id}"),
532            Self::InvalidUTF8(id) => write!(f, "invalid utf8 in file: {id}"),
533        }
534    }
535}