Skip to main content

cargo/util/
errors.rs

1use anyhow::Error;
2use curl::easy::Easy;
3use http::Response;
4use std::fmt::{self, Write};
5use std::path::PathBuf;
6
7use crate::util::network::http_async::ResponsePartsExtensions;
8
9use super::truncate_with_ellipsis;
10
11pub type CargoResult<T> = anyhow::Result<T>;
12
13/// These are headers that are included in error messages to help with
14/// diagnosing issues.
15pub const DEBUG_HEADERS: &[&str] = &[
16    // This is the unique ID that identifies the request in CloudFront which
17    // can be used for looking at the AWS logs.
18    "x-amz-cf-id",
19    // This is the CloudFront POP (Point of Presence) that identifies the
20    // region where the request was routed. This can help identify if an issue
21    // is region-specific.
22    "x-amz-cf-pop",
23    // The unique token used for troubleshooting S3 requests via AWS logs or support.
24    "x-amz-request-id",
25    // Another token used in conjunction with x-amz-request-id.
26    "x-amz-id-2",
27    // Whether or not there was a cache hit or miss (both CloudFront and Fastly).
28    "x-cache",
29    // The cache server that processed the request (Fastly).
30    "x-served-by",
31];
32
33#[derive(Debug)]
34pub struct HttpNotSuccessful {
35    pub code: u32,
36    pub url: String,
37    pub ip: Option<String>,
38    pub body: Vec<u8>,
39    pub headers: Vec<String>,
40}
41
42impl HttpNotSuccessful {
43    pub fn new_from_handle(
44        handle: &mut Easy,
45        initial_url: &str,
46        body: Vec<u8>,
47        headers: Vec<String>,
48    ) -> HttpNotSuccessful {
49        let ip = handle.primary_ip().ok().flatten().map(|s| s.to_string());
50        let url = handle
51            .effective_url()
52            .ok()
53            .flatten()
54            .unwrap_or(initial_url)
55            .to_string();
56        HttpNotSuccessful {
57            code: handle.response_code().unwrap_or(0),
58            url,
59            ip,
60            body,
61            headers,
62        }
63    }
64
65    pub fn new_from_response(response: Response<Vec<u8>>, url: &str) -> HttpNotSuccessful {
66        let ip = response.client_ip().map(str::to_string);
67        let url = response.effective_url().unwrap_or(url).to_string();
68        let (head, body) = response.into_parts();
69        let headers = head
70            .headers
71            .into_iter()
72            .filter_map(|(k, v)| Some(format!("{}: {}", k?.as_str(), v.to_str().ok()?)))
73            .collect();
74        HttpNotSuccessful {
75            code: head.status.as_u16() as u32,
76            url,
77            ip,
78            body,
79            headers,
80        }
81    }
82
83    /// Renders the error in a compact form.
84    pub fn display_short(&self) -> String {
85        self.render(false)
86    }
87
88    fn render(&self, show_headers: bool) -> String {
89        let mut result = String::new();
90        let body = std::str::from_utf8(&self.body)
91            .map(|s| truncate_with_ellipsis(s, 512))
92            .unwrap_or_else(|_| format!("[{} non-utf8 bytes]", self.body.len()));
93
94        write!(
95            result,
96            "failed to get successful HTTP response from `{}`",
97            self.url
98        )
99        .unwrap();
100        if let Some(ip) = &self.ip {
101            write!(result, " ({ip})").unwrap();
102        }
103        write!(result, ", got {}\n", self.code).unwrap();
104        if show_headers {
105            let headers: Vec<_> = self
106                .headers
107                .iter()
108                .filter(|header| {
109                    let Some((name, _)) = header.split_once(":") else {
110                        return false;
111                    };
112                    DEBUG_HEADERS.contains(&name.to_ascii_lowercase().trim())
113                })
114                .collect();
115            if !headers.is_empty() {
116                writeln!(result, "debug headers:").unwrap();
117                for header in headers {
118                    writeln!(result, "{header}").unwrap();
119                }
120            }
121        }
122        write!(result, "body:\n{body}").unwrap();
123        result
124    }
125}
126
127impl fmt::Display for HttpNotSuccessful {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.write_str(&self.render(true))
130    }
131}
132
133impl std::error::Error for HttpNotSuccessful {}
134
135// =============================================================================
136// Verbose error
137
138/// An error wrapper for errors that should only be displayed with `--verbose`.
139///
140/// This should only be used in rare cases. When emitting this error, you
141/// should have a normal error higher up the error-cause chain (like "could
142/// not compile `foo`"), so at least *something* gets printed without
143/// `--verbose`.
144pub struct VerboseError {
145    inner: Error,
146}
147
148impl VerboseError {
149    pub fn new(inner: Error) -> VerboseError {
150        VerboseError { inner }
151    }
152}
153
154impl std::error::Error for VerboseError {
155    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
156        self.inner.source()
157    }
158}
159
160impl fmt::Debug for VerboseError {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        self.inner.fmt(f)
163    }
164}
165
166impl fmt::Display for VerboseError {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        self.inner.fmt(f)
169    }
170}
171
172// =============================================================================
173// Internal error
174
175/// An unexpected, internal error.
176///
177/// This should only be used for unexpected errors. It prints a message asking
178/// the user to file a bug report.
179pub struct InternalError {
180    inner: Error,
181}
182
183impl InternalError {
184    pub fn new(inner: Error) -> InternalError {
185        InternalError { inner }
186    }
187}
188
189impl std::error::Error for InternalError {
190    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
191        self.inner.source()
192    }
193}
194
195impl fmt::Debug for InternalError {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        self.inner.fmt(f)
198    }
199}
200
201impl fmt::Display for InternalError {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        self.inner.fmt(f)
204    }
205}
206
207// =============================================================================
208// Already printed error
209
210/// An error that does not need to be printed because it does not add any new
211/// information to what has already been printed.
212pub struct AlreadyPrintedError {
213    inner: Error,
214}
215
216impl AlreadyPrintedError {
217    pub fn new(inner: Error) -> Self {
218        AlreadyPrintedError { inner }
219    }
220}
221
222impl std::error::Error for AlreadyPrintedError {
223    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
224        self.inner.source()
225    }
226}
227
228impl fmt::Debug for AlreadyPrintedError {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        self.inner.fmt(f)
231    }
232}
233
234impl fmt::Display for AlreadyPrintedError {
235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236        self.inner.fmt(f)
237    }
238}
239
240// =============================================================================
241// Manifest error
242
243/// Error wrapper related to a particular manifest and providing it's path.
244///
245/// This error adds no displayable info of it's own.
246pub struct ManifestError {
247    cause: Error,
248    manifest: PathBuf,
249}
250
251impl ManifestError {
252    pub fn new<E: Into<Error>>(cause: E, manifest: PathBuf) -> Self {
253        Self {
254            cause: cause.into(),
255            manifest,
256        }
257    }
258
259    pub fn manifest_path(&self) -> &PathBuf {
260        &self.manifest
261    }
262
263    /// Returns an iterator over the `ManifestError` chain of causes.
264    ///
265    /// So if this error was not caused by another `ManifestError` this will be empty.
266    pub fn manifest_causes(&self) -> ManifestCauses<'_> {
267        ManifestCauses { current: self }
268    }
269}
270
271impl std::error::Error for ManifestError {
272    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
273        self.cause.source()
274    }
275}
276
277impl fmt::Debug for ManifestError {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        self.cause.fmt(f)
280    }
281}
282
283impl fmt::Display for ManifestError {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        self.cause.fmt(f)
286    }
287}
288
289/// An iterator over the `ManifestError` chain of causes.
290pub struct ManifestCauses<'a> {
291    current: &'a ManifestError,
292}
293
294impl<'a> Iterator for ManifestCauses<'a> {
295    type Item = &'a ManifestError;
296
297    fn next(&mut self) -> Option<Self::Item> {
298        self.current = self.current.cause.downcast_ref()?;
299        Some(self.current)
300    }
301}
302
303impl<'a> ::std::iter::FusedIterator for ManifestCauses<'a> {}
304
305// =============================================================================
306// CLI errors
307
308pub type CliResult = Result<(), CliError>;
309
310#[derive(Debug)]
311/// The CLI error is the error type used at Cargo's CLI-layer.
312///
313/// All errors from the lib side of Cargo will get wrapped with this error.
314/// Other errors (such as command-line argument validation) will create this
315/// directly.
316pub struct CliError {
317    /// The error to display. This can be `None` in rare cases to exit with a
318    /// code without displaying a message. For example `cargo run -q` where
319    /// the resulting process exits with a nonzero code (on Windows), or an
320    /// external subcommand that exits nonzero (we assume it printed its own
321    /// message).
322    pub error: Option<anyhow::Error>,
323    /// The process exit code.
324    pub exit_code: i32,
325}
326
327impl CliError {
328    pub fn new(error: anyhow::Error, code: i32) -> CliError {
329        CliError {
330            error: Some(error),
331            exit_code: code,
332        }
333    }
334
335    pub fn code(code: i32) -> CliError {
336        CliError {
337            error: None,
338            exit_code: code,
339        }
340    }
341}
342
343impl From<anyhow::Error> for CliError {
344    fn from(err: anyhow::Error) -> CliError {
345        CliError::new(err, 101)
346    }
347}
348
349impl From<clap::Error> for CliError {
350    fn from(err: clap::Error) -> CliError {
351        let code = if err.use_stderr() { 1 } else { 0 };
352        CliError::new(err.into(), code)
353    }
354}
355
356impl From<std::io::Error> for CliError {
357    fn from(err: std::io::Error) -> CliError {
358        CliError::new(err.into(), 1)
359    }
360}
361
362// =============================================================================
363// Git CLI errors
364
365pub type GitCliResult = Result<(), GitCliError>;
366
367/// An error that occurred while invoking the `git` command-line tool.
368///
369/// This is used for errors from Git operations performed via CLI.
370/// It wraps a lower-level error and indicates whether the failure
371/// should be considered *spurious*.
372///
373/// Spurious failures might involve retry mechanism.
374#[derive(Debug)]
375pub struct GitCliError {
376    inner: Error,
377    is_spurious: bool,
378}
379
380impl GitCliError {
381    pub fn new(inner: Error, is_spurious: bool) -> GitCliError {
382        GitCliError { inner, is_spurious }
383    }
384
385    pub fn is_spurious(&self) -> bool {
386        self.is_spurious
387    }
388}
389
390impl std::error::Error for GitCliError {
391    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
392        self.inner.source()
393    }
394}
395
396impl fmt::Display for GitCliError {
397    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398        self.inner.fmt(f)
399    }
400}
401
402// =============================================================================
403// Construction helpers
404
405pub fn internal<S: fmt::Display>(error: S) -> anyhow::Error {
406    InternalError::new(anyhow::format_err!("{}", error)).into()
407}