| 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, | |
| workaround: Option<&'static str>, | |
| } | |
| impl GitCliError { | |
| pub fn new(inner: Error) -> GitCliError { | |
| GitCliError { | |
| inner, | |
| is_spurious: false, | |
| workaround: None, | |
| } | |
| } | |
| pub fn workaround(mut self, workaround: &'static str) -> Self { | |
| self.workaround = Some(workaround); | |
| self | |
| } | |
| pub fn spurious(mut self, yes: bool) -> Self { | |
| self.is_spurious = yes; | |
| self | |
| } | |
| 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)?; | |
| if let Some(workaround) = self.workaround { | |
| writeln!(f)?; | |
| writeln!(f)?; | |
| write!(f, "{workaround}")?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| // ============================================================================= | |
| // Construction helpers | |
| pub fn internal<S: fmt::Display>(error: S) -> anyhow::Error { | |
| InternalError::new(anyhow::format_err!("{}", error)).into() | |
| } |