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    SizeModificationInvalid,
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}