1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! Error types used by this crate

use std::{
    fmt::{self, Display},
    io,
    str::Utf8Error,
};
use thiserror::Error;

#[cfg(feature = "git")]
use tame_index::external::gix;

/// Create a new error (of a given enum variant) with a formatted message
macro_rules! format_err {
    ($kind:path, $msg:expr) => {
        crate::error::Error::new(
            $kind,
            &$msg.to_string()
        )
    };
    ($kind:path, $fmt:expr, $($arg:tt)+) => {
        format_err!($kind, &format!($fmt, $($arg)+))
    };
}

/// Create and return an error with a formatted message
macro_rules! fail {
    ($kind:path, $msg:expr) => {
        return Err(format_err!($kind, $msg).into())
    };
    ($kind:path, $fmt:expr, $($arg:tt)+) => {
        fail!($kind, &format!($fmt, $($arg)+))
    };
}

/// Result alias with the `rustsec` crate's `Error` type.
pub type Result<T> = std::result::Result<T, Error>;

/// Error type
#[derive(Debug)]
pub struct Error {
    /// Kind of error
    kind: ErrorKind,

    /// Message providing additional information
    msg: String,
}

impl Error {
    /// Create a new error with the given description
    pub fn new<S: ToString>(kind: ErrorKind, description: &S) -> Self {
        Self {
            kind,
            msg: description.to_string(),
        }
    }

    /// Obtain the inner `ErrorKind` for this error
    pub fn kind(&self) -> ErrorKind {
        self.kind
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", &self.kind, &self.msg)
    }
}

impl std::error::Error for Error {}

/// Custom error type for this library
#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
#[non_exhaustive]
pub enum ErrorKind {
    /// Invalid argument or parameter
    #[error("bad parameter")]
    BadParam,

    /// Error performing an automatic fix
    #[cfg(feature = "fix")]
    #[cfg_attr(docsrs, doc(cfg(feature = "fix")))]
    #[error("fix failed")]
    Fix,

    /// An error occurred performing an I/O operation (e.g. network, file)
    #[error("I/O operation failed")]
    Io,

    /// Not found
    #[error("not found")]
    NotFound,

    /// Unable to acquire filesystem lock
    #[error("unable to acquire filesystem lock")]
    LockTimeout,

    /// Couldn't parse response data
    #[error("parse error")]
    Parse,

    /// Registry-related error
    #[error("registry")]
    Registry,

    /// Git operation failed
    #[error("git operation failed")]
    Repo,

    /// Errors related to versions
    #[error("bad version")]
    Version,
}

impl From<Utf8Error> for Error {
    fn from(other: Utf8Error) -> Self {
        format_err!(ErrorKind::Parse, &other)
    }
}

#[cfg(feature = "fix")]
#[cfg_attr(docsrs, doc(cfg(feature = "fix")))]
impl From<cargo_edit::Error> for Error {
    fn from(other: cargo_edit::Error) -> Self {
        format_err!(ErrorKind::Fix, &other)
    }
}

impl From<cargo_lock::Error> for Error {
    fn from(other: cargo_lock::Error) -> Self {
        format_err!(ErrorKind::Io, &other)
    }
}

impl From<fmt::Error> for Error {
    fn from(other: fmt::Error) -> Self {
        format_err!(ErrorKind::Io, &other)
    }
}

impl From<io::Error> for Error {
    fn from(other: io::Error) -> Self {
        format_err!(ErrorKind::Io, &other)
    }
}

impl Error {
    /// Converts from [`tame_index::Error`] to our `Error`.
    ///
    /// This is a separate function instead of a `From` impl
    /// because a trait impl would leak into the public API,
    /// and we need to keep it private because `tame_index` semver
    /// will be bumped frequently and we don't want to bump `rustsec` semver
    /// every time it changes.
    #[cfg(feature = "git")]
    pub(crate) fn from_tame(err: tame_index::Error) -> Self {
        // Separate lock timeouts into their own LockTimeout variant.
        match err {
            tame_index::Error::Git(git_err) => match git_err {
                tame_index::error::GitError::Lock(lock_err) => Self::from_gix_lock(lock_err),
                other => format_err!(ErrorKind::Registry, "{}", other),
            },
            other => format_err!(ErrorKind::Registry, "{}", other),
        }
    }

    /// Converts from [`gix::lock::acquire::Error`] to our `Error`.
    ///
    /// This is a separate function instead of a `From` impl
    /// because a trait impl would leak into the public API,
    /// and we need to keep it private because `gix` semver
    /// will be bumped frequently and we don't want to bump `rustsec` semver
    /// every time it changes.
    #[cfg(feature = "git")]
    pub(crate) fn from_gix_lock(other: gix::lock::acquire::Error) -> Self {
        match other {
            gix::lock::acquire::Error::Io(e) => {
                format_err!(ErrorKind::Repo, "failed to aquire directory lock: {}", e)
            }
            gix::lock::acquire::Error::PermanentlyLocked {
                // rustc doesn't recognize inline printing as uses of variables,
                // so we have to explicitly discard them here even though they are used
                resource_path: _,
                mode: _,
                attempts: _,
            } => format_err!(
                ErrorKind::LockTimeout,
                "directory \"{resource_path:?}\" still locked after {attempts} attempts"
            ),
        }
    }

    /// Converts from [`toml::de::Error`] to our `Error`.
    ///
    /// This is used so rarely that there is no need to `impl From`,
    /// and this way we can avoid leaking it into the public API.
    pub(crate) fn from_toml(other: toml::de::Error) -> Self {
        format_err!(crate::ErrorKind::Parse, &other)
    }
}