Skip to main content

spar/
error.rs

1//! One error type. Every failure that a user could plausibly cause carries a
2//! sentence explaining what to do about it, because a failure whose reason is
3//! missing from the message costs more than the failure itself.
4
5use std::fmt;
6
7/// Why a call failed, where the answer changes what to do about it.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum ErrorKind {
10    #[default]
11    Other,
12    /// The call ran past its deadline and was killed. Worth its own kind
13    /// because asking again means waiting exactly as long again, which is the
14    /// one failure where a retry costs more than it can possibly win.
15    TimedOut,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct SparError {
20    message: String,
21    kind: ErrorKind,
22}
23
24impl SparError {
25    pub fn new(message: impl Into<String>) -> Self {
26        Self {
27            message: message.into(),
28            kind: ErrorKind::Other,
29        }
30    }
31
32    pub fn timed_out(message: impl Into<String>) -> Self {
33        Self {
34            message: message.into(),
35            kind: ErrorKind::TimedOut,
36        }
37    }
38
39    pub fn kind(&self) -> ErrorKind {
40        self.kind
41    }
42
43    /// Whether asking the same thing again could plausibly go better.
44    pub fn worth_retrying(&self) -> bool {
45        self.kind != ErrorKind::TimedOut
46    }
47
48    pub fn message(&self) -> &str {
49        &self.message
50    }
51
52    /// The last line of a multi-line failure. Useful when a nested command's
53    /// own error is the interesting part and the preamble is not.
54    pub fn last_line(&self) -> &str {
55        self.message.lines().next_back().unwrap_or(&self.message)
56    }
57
58    /// The first line, for one-line status output.
59    pub fn first_line(&self) -> &str {
60        self.message.lines().next().unwrap_or(&self.message)
61    }
62}
63
64impl fmt::Display for SparError {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.write_str(&self.message)
67    }
68}
69
70impl std::error::Error for SparError {}
71
72impl From<std::io::Error> for SparError {
73    fn from(e: std::io::Error) -> Self {
74        SparError::new(e.to_string())
75    }
76}
77
78impl From<serde_json::Error> for SparError {
79    fn from(e: serde_json::Error) -> Self {
80        SparError::new(format!("invalid JSON: {e}"))
81    }
82}
83
84impl From<toml::de::Error> for SparError {
85    fn from(e: toml::de::Error) -> Self {
86        SparError::new(format!("invalid TOML: {e}"))
87    }
88}
89
90pub type Result<T> = std::result::Result<T, SparError>;
91
92/// Build a `SparError` with `format!` syntax.
93#[macro_export]
94macro_rules! spar_err {
95    ($($arg:tt)*) => { $crate::error::SparError::new(format!($($arg)*)) };
96}
97
98/// Return early with a `SparError`.
99#[macro_export]
100macro_rules! bail {
101    ($($arg:tt)*) => { return Err($crate::spar_err!($($arg)*)) };
102}