Skip to main content

paralock_cli/
error.rs

1//! Errors and exit codes.
2
3use core::fmt;
4
5use paralock::{ParalockErrorCode, Swap};
6
7/// The process exit codes.
8///
9/// | Code | Meaning |
10/// |---|---|
11/// | 0 | Success: the command did what it was asked (a swap completed, a refund landed, every doctor check passed). |
12/// | 1 | Failure: nothing irreversible happened, or the command could not run (unreachable node, refused quote…). |
13/// | 2 | Usage or configuration error (bad flags, missing or invalid `paralock.toml`, missing key). |
14/// | 3 | A swap failed after its ZEC was locked. It was refunded, or it is saved and refundable with `paralock refund <id>`. |
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Exit {
17    /// 0.
18    Ok,
19    /// 1.
20    Failed,
21    /// 2.
22    Usage,
23    /// 3.
24    SwapFailed,
25}
26
27impl Exit {
28    /// The numeric code.
29    pub const fn code(self) -> u8 {
30        match self {
31            Self::Ok => 0,
32            Self::Failed => 1,
33            Self::Usage => 2,
34            Self::SwapFailed => 3,
35        }
36    }
37}
38
39/// An error the CLI reports to the user: a message, an optional hint, the protocol code (if any) and the swap it
40/// concerns (if any).
41#[derive(Debug)]
42pub struct CliError {
43    /// How the process exits.
44    pub exit: Exit,
45    /// What went wrong.
46    pub message: String,
47    /// What to do about it.
48    pub hint: Option<String>,
49    /// The Paralock error code, when the error came from the SDK or a maker.
50    pub code: Option<ParalockErrorCode>,
51    /// The swap, when it failed after its ZEC was locked.
52    pub swap: Option<Box<Swap>>,
53}
54
55/// `Result` with a [`CliError`].
56pub type CliResult<T> = Result<T, CliError>;
57
58impl CliError {
59    /// A runtime failure (exit 1).
60    pub fn failed(message: impl Into<String>) -> Self {
61        Self { exit: Exit::Failed, message: message.into(), hint: None, code: None, swap: None }
62    }
63
64    /// A usage or configuration error (exit 2).
65    pub fn usage(message: impl Into<String>) -> Self {
66        Self { exit: Exit::Usage, ..Self::failed(message) }
67    }
68
69    /// Adds a hint.
70    #[must_use]
71    pub fn hint(mut self, hint: impl Into<String>) -> Self {
72        self.hint = Some(hint.into());
73        self
74    }
75
76    /// Prefixes the message with what was being done.
77    #[must_use]
78    pub fn context(mut self, what: impl fmt::Display) -> Self {
79        self.message = format!("{what}: {}", self.message);
80        self
81    }
82}
83
84impl fmt::Display for CliError {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str(&self.message)
87    }
88}
89
90impl std::error::Error for CliError {}
91
92impl From<paralock::Error> for CliError {
93    fn from(e: paralock::Error) -> Self {
94        let code = e.code();
95        let swap = e.failed_swap().cloned().map(Box::new);
96        let exit = if swap.is_some() { Exit::SwapFailed } else { Exit::Failed };
97        let hint = match code {
98            ParalockErrorCode::Unavailable => {
99                Some("a node, the price feed or the maker is unreachable: `paralock doctor` checks each of them")
100            }
101            ParalockErrorCode::Busy => Some("the maker is busy or rate limiting you; try again in a moment"),
102            ParalockErrorCode::QuoteExpired => Some("ask for a new quote"),
103            ParalockErrorCode::InsufficientLiquidity => {
104                Some("the maker lacks the stablecoins for this amount; try a smaller one")
105            }
106            _ => None,
107        };
108        Self { exit, message: e.to_string(), hint: hint.map(str::to_owned), code: Some(code), swap }
109    }
110}
111
112impl From<paralock::StoreError> for CliError {
113    fn from(e: paralock::StoreError) -> Self {
114        let locked = matches!(e, paralock::StoreError::Locked(_));
115        let error = Self::failed(format!("swap store: {e}"));
116        if locked { error.hint("another paralock process uses this store; wait for it to finish") } else { error }
117    }
118}
119
120impl From<std::io::Error> for CliError {
121    fn from(e: std::io::Error) -> Self {
122        Self::failed(e.to_string())
123    }
124}