| use anyhow::Error; |
| use curl::easy::Easy; |
| use http::Response; |
| use std::fmt::{self, Write}; |
| use std::path::PathBuf; |
| |
| use crate::util::network::http_async::ResponsePartsExtensions; |
| |
| use super::truncate_with_ellipsis; |
| |
| pub type CargoResult<T> = anyhow::Result<T>; |
| |
| /// These are headers that are included in error messages to help with |
| /// diagnosing issues. |
| pub const DEBUG_HEADERS: &[&str] = &[ |
| // This is the unique ID that identifies the request in CloudFront which |
| // can be used for looking at the AWS logs. |
| "x-amz-cf-id", |
| // This is the CloudFront POP (Point of Presence) that identifies the |
| // region where the request was routed. This can help identify if an issue |
| // is region-specific. |
| "x-amz-cf-pop", |
| // The unique token used for troubleshooting S3 requests via AWS logs or support. |
| "x-amz-request-id", |
| // Another token used in conjunction with x-amz-request-id. |
| "x-amz-id-2", |
| // Whether or not there was a cache hit or miss (both CloudFront and Fastly). |
| "x-cache", |
| // The cache server that processed the request (Fastly). |
| "x-served-by", |
| ]; |
| |
| #[derive(Debug)] |
| pub struct HttpNotSuccessful { |
| pub code: u32, |
| pub url: String, |
| pub ip: Option<String>, |
| pub body: Vec<u8>, |
| pub headers: Vec<String>, |
| } |
| |
| impl HttpNotSuccessful { |
| pub fn new_from_handle( |
| handle: &mut Easy, |
| initial_url: &str, |
| body: Vec<u8>, |
| headers: Vec<String>, |
| ) -> HttpNotSuccessful { |
| let ip = handle.primary_ip().ok().flatten().map(|s| s.to_string()); |
| let url = handle |
| .effective_url() |
| .ok() |
| .flatten() |
| .unwrap_or(initial_url) |
| .to_string(); |
| HttpNotSuccessful { |
| code: handle.response_code().unwrap_or(0), |
| url, |
| ip, |
| body, |
| headers, |
| } |
| } |
| |
| pub fn new_from_response(response: Response<Vec<u8>>, url: &str) -> HttpNotSuccessful { |
| let ip = response.client_ip().map(str::to_string); |
| let url = response.effective_url().unwrap_or(url).to_string(); |
| let (head, body) = response.into_parts(); |
| let headers = head |
| .headers |
| .into_iter() |
| .filter_map(|(k, v)| Some(format!("{}: {}", k?.as_str(), v.to_str().ok()?))) |
| .collect(); |
| HttpNotSuccessful { |
| code: head.status.as_u16() as u32, |
| url, |
| ip, |
| body, |
| headers, |
| } |
| } |
| |
| /// Renders the error in a compact form. |
| pub fn display_short(&self) -> String { |
| self.render(false) |
| } |
| |
| fn render(&self, show_headers: bool) -> String { |
| let mut result = String::new(); |
| let body = std::str::from_utf8(&self.body) |
| .map(|s| truncate_with_ellipsis(s, 512)) |
| .unwrap_or_else(|_| format!("[{} non-utf8 bytes]", self.body.len())); |
| |
| write!( |
| result, |
| "failed to get successful HTTP response from `{}`", |
| self.url |
| ) |
| .unwrap(); |
| if let Some(ip) = &self.ip { |
| write!(result, " ({ip})").unwrap(); |
| } |
| write!(result, ", got {}\n", self.code).unwrap(); |
| if show_headers { |
| let headers: Vec<_> = self |
| .headers |
| .iter() |
| .filter(|header| { |
| let Some((name, _)) = header.split_once(":") else { |
| return false; |
| }; |
| DEBUG_HEADERS.contains(&name.to_ascii_lowercase().trim()) |
| }) |
| .collect(); |
| if !headers.is_empty() { |
| writeln!(result, "debug headers:").unwrap(); |
| for header in headers { |
| writeln!(result, "{header}").unwrap(); |
| } |
| } |
| } |
| write!(result, "body:\n{body}").unwrap(); |
| result |
| } |
| } |
| |
| impl fmt::Display for HttpNotSuccessful { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.write_str(&self.render(true)) |
| } |
| } |
| |
| impl std::error::Error for HttpNotSuccessful {} |
| |
| // ============================================================================= |
| // Verbose error |
| |
| /// An error wrapper for errors that should only be displayed with `--verbose`. |
| /// |
| /// This should only be used in rare cases. When emitting this error, you |
| /// should have a normal error higher up the error-cause chain (like "could |
| /// not compile `foo`"), so at least *something* gets printed without |
| /// `--verbose`. |
| pub struct VerboseError { |
| inner: Error, |
| } |
| |
| impl VerboseError { |
| pub fn new(inner: Error) -> VerboseError { |
| VerboseError { inner } |
| } |
| } |
| |
| impl std::error::Error for VerboseError { |
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| self.inner.source() |
| } |
| } |
| |
| impl fmt::Debug for VerboseError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.inner.fmt(f) |
| } |
| } |
| |
| impl fmt::Display for VerboseError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.inner.fmt(f) |
| } |
| } |
| |
| // ============================================================================= |
| // Internal error |
| |
| /// An unexpected, internal error. |
| /// |
| /// This should only be used for unexpected errors. It prints a message asking |
| /// the user to file a bug report. |
| pub struct InternalError { |
| inner: Error, |
| } |
| |
| impl InternalError { |
| pub fn new(inner: Error) -> InternalError { |
| InternalError { inner } |
| } |
| } |
| |
| impl std::error::Error for InternalError { |
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| self.inner.source() |
| } |
| } |
| |
| impl fmt::Debug for InternalError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.inner.fmt(f) |
| } |
| } |
| |
| impl fmt::Display for InternalError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.inner.fmt(f) |
| } |
| } |
| |
| // ============================================================================= |
| // Already printed error |
| |
| /// An error that does not need to be printed because it does not add any new |
| /// information to what has already been printed. |
| pub struct AlreadyPrintedError { |
| inner: Error, |
| } |
| |
| impl AlreadyPrintedError { |
| pub fn new(inner: Error) -> Self { |
| AlreadyPrintedError { inner } |
| } |
| } |
| |
| impl std::error::Error for AlreadyPrintedError { |
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| self.inner.source() |
| } |
| } |
| |
| impl fmt::Debug for AlreadyPrintedError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.inner.fmt(f) |
| } |
| } |
| |
| impl fmt::Display for AlreadyPrintedError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.inner.fmt(f) |
| } |
| } |
| |
| // ============================================================================= |
| // Manifest error |
| |
| /// Error wrapper related to a particular manifest and providing it's path. |
| /// |
| /// This error adds no displayable info of it's own. |
| pub struct ManifestError { |
| cause: Error, |
| manifest: PathBuf, |
| } |
| |
| impl ManifestError { |
| pub fn new<E: Into<Error>>(cause: E, manifest: PathBuf) -> Self { |
| Self { |
| cause: cause.into(), |
| manifest, |
| } |
| } |
| |
| pub fn manifest_path(&self) -> &PathBuf { |
| &self.manifest |
| } |
| |
| /// Returns an iterator over the `ManifestError` chain of causes. |
| /// |
| /// So if this error was not caused by another `ManifestError` this will be empty. |
| pub fn manifest_causes(&self) -> ManifestCauses<'_> { |
| ManifestCauses { current: self } |
| } |
| } |
| |
| impl std::error::Error for ManifestError { |
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| self.cause.source() |
| } |
| } |
| |
| impl fmt::Debug for ManifestError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.cause.fmt(f) |
| } |
| } |
| |
| impl fmt::Display for ManifestError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.cause.fmt(f) |
| } |
| } |
| |
| /// An iterator over the `ManifestError` chain of causes. |
| pub struct ManifestCauses<'a> { |
| current: &'a ManifestError, |
| } |
| |
| impl<'a> Iterator for ManifestCauses<'a> { |
| type Item = &'a ManifestError; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| self.current = self.current.cause.downcast_ref()?; |
| Some(self.current) |
| } |
| } |
| |
| impl<'a> ::std::iter::FusedIterator for ManifestCauses<'a> {} |
| |
| // ============================================================================= |
| // CLI errors |
| |
| pub type CliResult = Result<(), CliError>; |
| |
| #[derive(Debug)] |
| /// The CLI error is the error type used at Cargo's CLI-layer. |
| /// |
| /// All errors from the lib side of Cargo will get wrapped with this error. |
| /// Other errors (such as command-line argument validation) will create this |
| /// directly. |
| pub struct CliError { |
| /// The error to display. This can be `None` in rare cases to exit with a |
| /// code without displaying a message. For example `cargo run -q` where |
| /// the resulting process exits with a nonzero code (on Windows), or an |
| /// external subcommand that exits nonzero (we assume it printed its own |
| /// message). |
| pub error: Option<anyhow::Error>, |
| /// The process exit code. |
| pub exit_code: i32, |
| } |
| |
| impl CliError { |
| pub fn new(error: anyhow::Error, code: i32) -> CliError { |
| CliError { |
| error: Some(error), |
| exit_code: code, |
| } |
| } |
| |
| pub fn code(code: i32) -> CliError { |
| CliError { |
| error: None, |
| exit_code: code, |
| } |
| } |
| } |
| |
| impl From<anyhow::Error> for CliError { |
| fn from(err: anyhow::Error) -> CliError { |
| CliError::new(err, 101) |
| } |
| } |
| |
| impl From<clap::Error> for CliError { |
| fn from(err: clap::Error) -> CliError { |
| let code = if err.use_stderr() { 1 } else { 0 }; |
| CliError::new(err.into(), code) |
| } |
| } |
| |
| impl From<std::io::Error> for CliError { |
| fn from(err: std::io::Error) -> CliError { |
| CliError::new(err.into(), 1) |
| } |
| } |
| |
| // ============================================================================= |
| // Git CLI errors |
| |
| pub type GitCliResult = Result<(), GitCliError>; |
| |
| /// An error that occurred while invoking the `git` command-line tool. |
| /// |
| /// This is used for errors from Git operations performed via CLI. |
| /// It wraps a lower-level error and indicates whether the failure |
| /// should be considered *spurious*. |
| /// |
| /// Spurious failures might involve retry mechanism. |
| #[derive(Debug)] |
| pub struct GitCliError { |
| inner: Error, |
| is_spurious: bool, |
| } |
| |
| impl GitCliError { |
| pub fn new(inner: Error, is_spurious: bool) -> GitCliError { |
| GitCliError { inner, is_spurious } |
| } |
| |
| pub fn is_spurious(&self) -> bool { |
| self.is_spurious |
| } |
| } |
| |
| impl std::error::Error for GitCliError { |
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| self.inner.source() |
| } |
| } |
| |
| impl fmt::Display for GitCliError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.inner.fmt(f) |
| } |
| } |
| |
| // ============================================================================= |
| // Construction helpers |
| |
| pub fn internal<S: fmt::Display>(error: S) -> anyhow::Error { |
| InternalError::new(anyhow::format_err!("{}", error)).into() |
| } |