Include line number prefix in DisplaySourceError message

This commit is contained in:
Fabrice Reix 2024-06-20 21:31:57 +02:00 committed by hurl-bot
parent 57f465e3b1
commit 9054a3434e
No known key found for this signature in database
GPG Key ID: 1283A2B4A0DCAF8D
6 changed files with 92 additions and 40 deletions

View File

@ -78,7 +78,9 @@ impl DisplaySourceError for OutputError {
let mut text = StyledString::new();
hurl_core::error::add_source_line(&mut text, content, self.source_info().start.line);
text.append(self.fixme(content));
text
let error_line = self.source_info().start.line;
hurl_core::error::add_line_info_prefix(&text, content, error_line)
}
}

View File

@ -294,7 +294,9 @@ impl DisplaySourceError for RunnerError {
let mut text = StyledString::new();
hurl_core::error::add_source_line(&mut text, content, self.source_info().start.line);
text.append(self.fixme(content));
text
let error_line = self.source_info().start.line;
hurl_core::error::add_line_info_prefix(&text, content, error_line)
}
}
@ -334,7 +336,7 @@ mod tests {
error
.message(&split_lines(content))
.to_string(Format::Plain),
" GET http://unknown\n ^^^^^^^^^^^^^^ (6) Could not resolve host: unknown"
"\n 1 | GET http://unknown\n | ^^^^^^^^^^^^^^ (6) Could not resolve host: unknown\n |"
);
assert_eq!(
error_string(filename, content, &error, Some(entry_source_info), false),
@ -364,12 +366,12 @@ HTTP/1.0 200
error
.message(&split_lines(content))
.to_string(Format::Plain),
" HTTP/1.0 200\n ^^^ actual value is <404>"
"\n 2 | HTTP/1.0 200\n | ^^^ actual value is <404>\n |"
);
colored::control::set_override(true);
assert_eq!(
error.message(&split_lines(content)).to_string(Format::Ansi),
" HTTP/1.0 200\n\u{1b}[1;31m ^^^ actual value is <404>\u{1b}[0m",
"\n\u{1b}[1;34m 2 |\u{1b}[0m HTTP/1.0 200\n\u{1b}[1;34m |\u{1b}[0m\u{1b}[1;31m ^^^ actual value is <404>\u{1b}[0m\n\u{1b}[1;34m |\u{1b}[0m"
);
assert_eq!(
@ -401,7 +403,7 @@ xpath "strong(//head/title)" == "Hello"
);
assert_eq!(
&error.message( &split_lines(content)).to_string(Format::Plain),
" xpath \"strong(//head/title)\" == \"Hello\"\n ^^^^^^^^^^^^^^^^^^^^^^ the XPath expression is not valid"
"\n 4 | xpath \"strong(//head/title)\" == \"Hello\"\n | ^^^^^^^^^^^^^^^^^^^^^^ the XPath expression is not valid\n |"
);
assert_eq!(
error_string(filename, content, &error, Some(entry_source_info), false),
@ -440,9 +442,11 @@ jsonpath "$.count" >= 5
error
.message(&split_lines(content))
.to_string(Format::Plain),
r#" jsonpath "$.count" >= 5
actual: int <2>
expected: greater than int <5>"#
r#"
4 | jsonpath "$.count" >= 5
| actual: int <2>
| expected: greater than int <5>
|"#
);
assert_eq!(
@ -479,7 +483,7 @@ HTTP/1.0 200
error
.message(&split_lines(content))
.to_string(Format::Plain),
" ```<p>Hello</p>\n ^ actual value is <<p>Hello</p>\n\n >"
"\n 3 | ```<p>Hello</p>\n | ^ actual value is <<p>Hello</p>\n |\n | >\n |"
);
assert_eq!(
error_string(filename, content, &error, Some(entry_source_info), false),

View File

@ -16,7 +16,7 @@
*
*/
use crate::ast::SourceInfo;
use crate::text::{Format, StyledString};
use crate::text::{Format, Style, StyledString};
use colored::Colorize;
use std::cmp::max;
@ -121,13 +121,6 @@ pub fn error_string<E: DisplaySourceError>(
prefix.to_string()
};
let prefix_with_number = format!("{error_line:>loc_max_width$} {separator}");
let prefix_with_number = if colored {
prefix_with_number.blue().bold().to_string()
} else {
prefix_with_number.to_string()
};
// 1. First line is the description, ex. `Assert status code`.
let description = if colored {
error.description().bold().to_string()
@ -180,7 +173,6 @@ pub fn error_string<E: DisplaySourceError>(
}
// 5. Appends the error message (one or more lines)
// with the line number '|' prefix
let message = error.message(&lines);
let message = if colored {
message.to_string(Format::Ansi)
@ -188,21 +180,57 @@ pub fn error_string<E: DisplaySourceError>(
message.to_string(Format::Plain)
};
for (i, line) in split_lines(&message).iter().enumerate() {
text.push('\n');
text.push_str(if i == 0 { &prefix_with_number } else { &prefix });
text.push_str(line);
}
// 6. Appends additional empty line
if !message.ends_with('\n') {
text.push('\n');
text.push_str(&prefix);
}
text.push_str(&message);
text
}
pub fn add_line_info_prefix(
text: &StyledString,
content: &[&str],
error_line: usize,
) -> StyledString {
let text = text.clone();
//dd_source_line(&mut text, content, error_line);
// eprintln!("text={:#?}", text);
let separator = "|";
let loc_max_width = max(content.len().to_string().len(), 2);
let spaces = " ".repeat(loc_max_width);
let mut prefix = StyledString::new();
prefix.push_with(
format!("{spaces} {separator}").as_str(),
Style::new().blue().bold(),
);
let mut prefix_with_number = StyledString::new();
prefix_with_number.push_with(
format!("{error_line:>loc_max_width$} {separator}").as_str(),
Style::new().blue().bold(),
);
let mut text2 = StyledString::new();
for (i, line) in text.split('\n').iter().enumerate() {
text2.push("\n");
text2.append(if i == 0 {
prefix_with_number.clone()
} else {
prefix.clone()
});
text2.append(line.clone());
}
//eprintln!(">>> text2 {:#?}", text2);
// Appends additional empty line
if !text2.ends_with("|") {
text2.push("\n");
text2.append(prefix.clone());
}
// eprintln!(">>> text2 {:#?}", text2);
text2
}
/// Splits this `text` to a list of LF/CRLF separated lines.
pub fn split_lines(text: &str) -> Vec<&str> {
regex::Regex::new(r"\n|\r\n").unwrap().split(text).collect()
@ -313,7 +341,8 @@ HTTP 200
diff
}
fn message(&self, lines: &[&str]) -> StyledString {
self.fixme(lines)
let s = self.fixme(lines);
add_line_info_prefix(&s, &[], 4)
}
}
let error = E;
@ -323,16 +352,17 @@ HTTP 200
error
.message(&split_lines(content))
.to_string(Format::Plain),
r#" {
"name": "John",
- "age": 27
+ "age": 28
}
"#
r#"
4 | {
| "name": "John",
|- "age": 27
|+ "age": 28
| }
|"#
);
assert_eq!(
error.message(&split_lines(content)).to_string(Format::Ansi),
" {\n \"name\": \"John\",\n\u{1b}[31m- \"age\": 27\u{1b}[0m\n\u{1b}[32m+ \"age\": 28\u{1b}[0m\n }\n"
"\n\u{1b}[1;34m 4 |\u{1b}[0m {\n\u{1b}[1;34m |\u{1b}[0m \"name\": \"John\",\n\u{1b}[1;34m |\u{1b}[0m\u{1b}[31m- \"age\": 27\u{1b}[0m\n\u{1b}[1;34m |\u{1b}[0m\u{1b}[32m+ \"age\": 28\u{1b}[0m\n\u{1b}[1;34m |\u{1b}[0m }\n\u{1b}[1;34m |\u{1b}[0m"
);
assert_eq!(

View File

@ -255,7 +255,9 @@ impl DisplaySourceError for ParseError {
let mut text = StyledString::new();
crate::error::add_source_line(&mut text, content, self.source_info().start.line);
text.append(self.fixme(content));
text
let error_line = self.source_info().start.line;
crate::error::add_line_info_prefix(&text, content, error_line)
}
}

View File

@ -100,6 +100,10 @@ impl StyledString {
items.push(item);
items
}
pub fn ends_with(&self, value: &str) -> bool {
self.to_string(Format::Plain).ends_with(value)
}
}
impl Token {
@ -276,6 +280,14 @@ mod tests {
assert_eq!(line.split(','), vec![item0, item1, item2, item3]);
}
#[test]
fn test_ends_with() {
let mut line = StyledString::new();
line.push("Hello,Hi,");
assert!(line.ends_with(","));
assert!(!line.ends_with("\n"));
}
#[test]
fn compare_with_crate_colored() {
// These tests are used to check regression against the [colored crate](https://crates.io/crates/colored).

View File

@ -53,6 +53,8 @@ impl DisplaySourceError for linter::Error {
let mut text = StyledString::new();
hurl_core::error::add_source_line(&mut text, content, self.source_info().start.line);
text.append(self.fixme(content));
text
let error_line = self.source_info().start.line;
hurl_core::error::add_line_info_prefix(&text, content, error_line)
}
}