diff --git a/Cargo.lock b/Cargo.lock index a3c673041..e2911b7ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6959,6 +6959,9 @@ name = "uv-warnings" version = "0.0.7" dependencies = [ "anstream", + "anyhow", + "indoc", + "insta", "owo-colors", "rustc-hash", ] diff --git a/crates/uv-warnings/Cargo.toml b/crates/uv-warnings/Cargo.toml index dfd5425c7..3d8bf95f2 100644 --- a/crates/uv-warnings/Cargo.toml +++ b/crates/uv-warnings/Cargo.toml @@ -19,3 +19,8 @@ workspace = true anstream = { workspace = true } owo-colors = { workspace = true } rustc-hash = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +indoc = { workspace = true } +insta = { workspace = true } diff --git a/crates/uv-warnings/src/lib.rs b/crates/uv-warnings/src/lib.rs index a076682ef..deb1df9e6 100644 --- a/crates/uv-warnings/src/lib.rs +++ b/crates/uv-warnings/src/lib.rs @@ -74,6 +74,15 @@ macro_rules! warn_user_once { /// warning: Failed to create registry entry for Python 3.12 /// Caused By: Security policy forbids chaining registry entries /// ``` +/// +/// ```text +/// error: Failed to download Python 3.12 +/// Caused by: Failed to fetch https://example.com/upload/python3.13.tar.zst +/// Server says: This endpoint only support POST requests. +/// +/// For downloads, please refer to https://example.com/download/python3.13.tar.zst +/// Caused by: Caused By: HTTP Error 400 +/// ``` pub fn write_error_chain( err: &dyn Error, mut stream: impl std::fmt::Write, @@ -88,12 +97,62 @@ pub fn write_error_chain( err.to_string().trim() )?; for source in iter::successors(err.source(), |&err| err.source()) { - writeln!( - &mut stream, - " {}: {}", - "Caused by".color(color).bold(), - source.to_string().trim() - )?; + let msg = source.to_string(); + let mut lines = msg.lines(); + if let Some(first) = lines.next() { + let padding = " "; + let cause = "Caused by"; + let child_padding = " ".repeat(padding.len() + cause.len() + 2); + writeln!( + &mut stream, + "{}{}: {}", + padding, + cause.color(color).bold(), + first.trim() + )?; + for line in lines { + let line = line.trim_end(); + if line.is_empty() { + // Avoid showing indents on empty lines + writeln!(&mut stream)?; + } else { + writeln!(&mut stream, "{}{}", child_padding, line.trim_end())?; + } + } + } } Ok(()) } + +#[cfg(test)] +mod tests { + use crate::write_error_chain; + use anyhow::anyhow; + use indoc::indoc; + use insta::assert_snapshot; + use owo_colors::AnsiColors; + + #[test] + fn format_multiline_message() { + let err_middle = indoc! {"Failed to fetch https://example.com/upload/python3.13.tar.zst + Server says: This endpoint only support POST requests. + + For downloads, please refer to https://example.com/download/python3.13.tar.zst"}; + let err = anyhow!("Caused By: HTTP Error 400") + .context(err_middle) + .context("Failed to download Python 3.12"); + + let mut rendered = String::new(); + write_error_chain(err.as_ref(), &mut rendered, "error", AnsiColors::Red).unwrap(); + let rendered = anstream::adapter::strip_str(&rendered); + + assert_snapshot!(rendered, @r" + error: Failed to download Python 3.12 + Caused by: Failed to fetch https://example.com/upload/python3.13.tar.zst + Server says: This endpoint only support POST requests. + + For downloads, please refer to https://example.com/download/python3.13.tar.zst + Caused by: Caused By: HTTP Error 400 + "); + } +}