rustsec/
error.rs

1//! Error types used by this crate
2
3use std::{
4    fmt::{self, Display},
5    io,
6    str::Utf8Error,
7};
8use thiserror::Error;
9
10/// Create a new error (of a given enum variant) with a formatted message
11macro_rules! format_err {
12    ($kind:path, $msg:expr) => {
13        crate::error::Error::new(
14            $kind,
15            &$msg.to_string()
16        )
17    };
18    ($kind:path, $fmt:expr, $($arg:tt)+) => {
19        format_err!($kind, &format!($fmt, $($arg)+))
20    };
21}
22
23/// Create and return an error with a formatted message
24macro_rules! fail {
25    ($kind:path, $msg:expr) => {
26        return Err(format_err!($kind, $msg).into())
27    };
28    ($kind:path, $fmt:expr, $($arg:tt)+) => {
29        fail!($kind, &format!($fmt, $($arg)+))
30    };
31}
32
33/// Result alias with the `rustsec` crate's `Error` type.
34pub type Result<T> = std::result::Result<T, Error>;
35
36/// Error type
37#[derive(Debug)]
38pub struct Error {
39    /// Kind of error
40    kind: ErrorKind,
41
42    /// Message providing a more specific explanation than `self.kind`.
43    ///
44    /// This may be a complete error by itself, or it may provide context for `self.source`.
45    msg: String,
46
47    /// Cause of this error.
48    ///
49    /// The specific type of this error should not be considered part of the stable interface of
50    /// this crate.
51    source: Option<Box<dyn std::error::Error + Send + Sync>>,
52}
53
54impl Error {
55    /// Creates a new [`Error`](struct@Error) with the given description.
56    ///
57    /// Do not use this for wrapping [`std::error::Error`]; use [`Error::with_source()`] instead.
58    pub fn new<S: ToString>(kind: ErrorKind, description: &S) -> Self {
59        // TODO: In a semver-breaking release, deprecate accepting anything but a `String`,
60        // or maybe `AsRef<str>`. This will discourage putting error types in the `description`
61        // position, which makes it impossible to retrieve their `.source()` info. It will also
62        // avoid an unnecessary clone in the common case where `S` is already a `String`.
63        Self {
64            kind,
65            msg: description.to_string(),
66            source: None,
67        }
68    }
69
70    /// Creates a new [`Error`](struct@Error) whose [`std::error::Error::source()`] is `source`.
71    ///
72    /// `msg` should describe the operation which failed so as to give context for how `source`
73    /// is a meaningful error. For example, if `source` is a [`std::io::Error`] from trying to
74    /// read a file, then `msg` should include the path of the file and why the file is relevant.
75    pub fn with_source<E: std::error::Error + Send + Sync + 'static>(
76        kind: ErrorKind,
77        msg: String,
78        source: E,
79    ) -> Self {
80        Self {
81            kind,
82            msg,
83            source: Some(Box::new(source)),
84        }
85    }
86
87    /// Obtain the inner `ErrorKind` for this error
88    pub fn kind(&self) -> ErrorKind {
89        self.kind
90    }
91}
92
93impl Display for Error {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}: {}", self.kind, self.msg)
96    }
97}
98
99impl std::error::Error for Error {
100    /// The lower-level source of this error, if any.
101    ///
102    /// The specific type of the returned error should not be considered part of the stable
103    /// interface of this crate; prefer to use this only for displaying error information.
104    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
105        match &self.source {
106            Some(boxed_error) => Some(&**boxed_error),
107            None => None,
108        }
109    }
110}
111
112/// Custom error type for this library
113#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
114#[non_exhaustive]
115pub enum ErrorKind {
116    /// Invalid argument or parameter
117    #[error("bad parameter")]
118    BadParam,
119
120    /// An error occurred performing an I/O operation (e.g. network, file)
121    #[error("I/O operation failed")]
122    Io,
123
124    /// Not found
125    #[error("not found")]
126    NotFound,
127
128    /// Unable to acquire filesystem lock
129    #[error("unable to acquire filesystem lock")]
130    LockTimeout,
131
132    /// Couldn't parse response data
133    #[error("parse error")]
134    Parse,
135
136    /// Registry-related error
137    #[error("registry")]
138    Registry,
139
140    /// Git operation failed
141    #[error("git operation failed")]
142    Repo,
143
144    /// Errors related to versions
145    #[error("bad version")]
146    Version,
147}
148
149impl From<Utf8Error> for Error {
150    fn from(other: Utf8Error) -> Self {
151        format_err!(ErrorKind::Parse, &other)
152    }
153}
154
155impl From<cargo_lock::Error> for Error {
156    fn from(other: cargo_lock::Error) -> Self {
157        format_err!(ErrorKind::Io, &other)
158    }
159}
160
161impl From<fmt::Error> for Error {
162    fn from(other: fmt::Error) -> Self {
163        format_err!(ErrorKind::Io, &other)
164    }
165}
166
167impl From<io::Error> for Error {
168    fn from(other: io::Error) -> Self {
169        format_err!(ErrorKind::Io, &other)
170    }
171}
172
173impl Error {
174    /// Converts from [`tame_index::Error`] to our `Error`.
175    ///
176    /// This is a separate function instead of a `From` impl
177    /// because a trait impl would leak into the public API,
178    /// and we need to keep it private because `tame_index` semver
179    /// will be bumped frequently and we don't want to bump `rustsec` semver
180    /// every time it changes.
181    #[cfg(feature = "git")]
182    pub(crate) fn from_tame(err: tame_index::Error) -> Self {
183        // Separate lock timeouts into their own LockTimeout variant.
184        use tame_index::utils::flock::LockError;
185        match err {
186            tame_index::Error::Lock(lock_err) => match &lock_err.source {
187                LockError::TimedOut | LockError::Contested => {
188                    format_err!(ErrorKind::LockTimeout, "{}", lock_err)
189                }
190                _ => format_err!(ErrorKind::Io, "{}", lock_err),
191            },
192            other => format_err!(ErrorKind::Registry, "{}", other),
193        }
194    }
195
196    /// Converts from [`toml::de::Error`] to our `Error`.
197    ///
198    /// This is used so rarely that there is no need to `impl From`,
199    /// and this way we can avoid leaking it into the public API.
200    pub(crate) fn from_toml(other: toml::de::Error) -> Self {
201        format_err!(ErrorKind::Parse, &other)
202    }
203}
204
205impl From<toml::ser::Error> for Error {
206    fn from(other: toml::ser::Error) -> Self {
207        format_err!(ErrorKind::Parse, &other)
208    }
209}