clapi/
error.rs

1use crate::error::Inner::{Custom, Simple};
2use std::fmt::{Debug, Display, Formatter};
3
4/// A convenient `Result` type.
5pub type Result<T> = std::result::Result<T, Error>;
6
7type AnyError = Box<dyn std::error::Error + Sync + Send>;
8
9/// An error in a command-line operation.
10pub struct Error {
11    inner: Inner,
12}
13
14enum Inner {
15    Simple(ErrorKind),
16    Custom(CustomError),
17}
18
19impl Error {
20    /// Constructs a new `Error` with the specified `ErrorKind` and extended error information.
21    ///
22    /// # Example
23    /// ```rust
24    /// use clapi::{Error, ErrorKind};
25    ///
26    /// let error = Error::new(ErrorKind::InvalidArgumentCount, "expect 1 or more arguments");
27    /// assert!(matches!(error.kind(), ErrorKind::InvalidArgumentCount));
28    /// ```
29    pub fn new<E: Into<AnyError>>(kind: ErrorKind, error: E) -> Self {
30        Error {
31            inner: Custom(
32                CustomError::new(
33                    kind,
34                    error.into(),
35                    None
36                )
37            ),
38        }
39    }
40
41    /// Returns the `ErrorKind` of this error.
42    pub fn kind(&self) -> &ErrorKind {
43        match &self.inner {
44            Inner::Simple(kind) => kind,
45            Inner::Custom(custom) => &custom.kind,
46        }
47    }
48
49    /// Returns this error with the given message.
50    ///
51    /// # Example
52    /// ```
53    /// use clapi::{Error, ErrorKind};
54    ///
55    /// let error = Error::from(ErrorKind::InvalidArgument("xyz".to_string()));
56    /// let new_error = error.with_message("expected a number");
57    /// assert_eq!(new_error.to_string(), "invalid value for argument 'xyz': expected a number".to_string())
58    /// ```
59    pub fn with_message<S: Into<AnyError>>(&self, msg: S) -> Self {
60        match &self.inner {
61            Simple(kind) => {
62                Error::new(kind.clone(), msg.into())
63            }
64            Custom(custom) => {
65                Error {
66                    inner: Custom(CustomError::new(
67                        custom.kind.clone(),
68                        custom.error.to_string().into(),
69                        Some(msg.into().to_string())
70                    ))
71                }
72            }
73        }
74    }
75
76    /// Prints this error in the `stderr` and exit this process with status 0.
77    pub fn exit(self) -> ! {
78        if matches!(self.kind(), ErrorKind::DisplayHelp(_) | ErrorKind::DisplayVersion(_)) {
79            println!("{}", self);
80        } else {
81            // FIXME: Error already contains a newline
82            eprintln!("Error: {}", self);
83        }
84
85        std::process::exit(0)
86    }
87}
88
89impl std::error::Error for Error {
90    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
91        match self.inner {
92            Simple(_) => None,
93            Custom(ref custom) => Some(custom.error.as_ref()),
94        }
95    }
96}
97
98impl Display for Error {
99    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100        match &self.inner {
101            Inner::Simple(kind) => Display::fmt(kind, f),
102            Inner::Custom(custom) =>  Display::fmt(custom, f)
103        }
104    }
105}
106
107impl Debug for Error {
108    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109        Display::fmt(self, f)
110    }
111}
112
113impl From<ErrorKind> for Error {
114    fn from(kind: ErrorKind) -> Self {
115        Error {
116            inner: Simple(kind),
117        }
118    }
119}
120
121/// Types of errors.
122#[derive(Clone, Eq, PartialEq)]
123pub enum ErrorKind {
124    /// The value passed to the argument is invalid.
125    InvalidArgument(String),
126    /// Invalid number of arguments being passed.
127    InvalidArgumentCount,
128    /// The expression is invalid.
129    InvalidExpression,
130    /// The option wasn't expected in the current context
131    UnexpectedOption(String),
132    /// The command wasn't expected in the current context
133    UnexpectedCommand(String),
134    /// The option is required.
135    MissingOption(String),
136    /// An error no listed.
137    Other,
138
139    /// *Not an actual error used for convenience*.
140    ///
141    /// Display a help message.
142    DisplayHelp(String),
143
144    /// *Not an actual error used for convenience*.
145    ///
146    /// Display a version message.
147    DisplayVersion(String),
148
149    /// Indicates to the caller to show a help message. This should not be used as an `Error`.
150    #[doc(hidden)]
151    FallthroughHelp
152}
153
154impl Display for ErrorKind {
155    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
156        match self {
157            ErrorKind::InvalidArgument(s) => write!(f, "invalid value for argument '{}'", s),
158            ErrorKind::InvalidArgumentCount => write!(f, "invalid argument count"),
159            ErrorKind::InvalidExpression => write!(f, "invalid expression"),
160            ErrorKind::UnexpectedOption(s) => write!(f, "unexpected option: '{}'", s),
161            ErrorKind::UnexpectedCommand(s) => write!(f, "unexpected command: '{}'", s),
162            ErrorKind::MissingOption(s) => write!(f, "'{}' is required", s),
163            ErrorKind::Other => write!(f, "unexpected error"),
164            ErrorKind::DisplayHelp(s) => write!(f, "{}", s),
165            ErrorKind::DisplayVersion(s) => write!(f, "{}", s),
166            ErrorKind::FallthroughHelp => panic!("`ErrorKind::FallthroughHelp` should not be used as an error")
167        }
168    }
169}
170
171impl Debug for ErrorKind {
172    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
173        Display::fmt(self, f)
174    }
175}
176
177struct CustomError {
178    kind: ErrorKind,
179    error: AnyError,
180    info: Option<String>
181}
182
183impl CustomError {
184    pub fn new(kind: ErrorKind, error: AnyError, info: Option<String>) -> Self {
185        CustomError {
186            kind,
187            error,
188            info
189        }
190    }
191}
192
193impl Display for CustomError {
194    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
195        if let Some(info) = &self.info {
196            write!(f, "{}: {}\n{}", self.kind, self.error, info)
197        } else {
198            write!(f, "{}: {}", self.kind, self.error)
199        }
200    }
201}