diff --git a/assets/screencast.svg b/assets/screencast.svg
index bfb4a1a96d..a0b79fde99 100644
--- a/assets/screencast.svg
+++ b/assets/screencast.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/lychee-bin/src/commands/check.rs b/lychee-bin/src/commands/check.rs
index 944f86683d..3551c4c9c8 100644
--- a/lychee-bin/src/commands/check.rs
+++ b/lychee-bin/src/commands/check.rs
@@ -362,7 +362,7 @@ fn show_progress(
fn get_failed_urls(stats: &mut ResponseStats) -> Vec<(InputSource, Url)> {
stats
- .fail_map
+ .error_map
.iter()
.flat_map(|(source, set)| {
set.iter()
diff --git a/lychee-bin/src/formatters/stats/compact.rs b/lychee-bin/src/formatters/stats/compact.rs
index 087acbc3a6..066750d423 100644
--- a/lychee-bin/src/formatters/stats/compact.rs
+++ b/lychee-bin/src/formatters/stats/compact.rs
@@ -20,8 +20,8 @@ impl Display for CompactResponseStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let stats = &self.stats;
- if !stats.fail_map.is_empty() {
- let input = if stats.fail_map.len() == 1 {
+ if !stats.error_map.is_empty() {
+ let input = if stats.error_map.len() == 1 {
"input"
} else {
"inputs"
@@ -31,13 +31,13 @@ impl Display for CompactResponseStats {
f,
BOLD_PINK,
"Issues found in {} {input}. Find details below.\n\n",
- stats.fail_map.len()
+ stats.error_map.len()
)?;
}
let response_formatter = get_response_formatter(&self.mode);
- for (source, responses) in &stats.fail_map {
+ for (source, responses) in &stats.error_map {
color!(f, BOLD_YELLOW, "[{}]:\n", source)?;
for response in responses {
writeln!(
@@ -145,9 +145,9 @@ mod tests {
status: Status::Ok(StatusCode::INTERNAL_SERVER_ERROR),
};
- let mut fail_map: HashMap> = HashMap::new();
+ let mut error_map: HashMap> = HashMap::new();
let source = InputSource::RemoteUrl(Box::new(Url::parse("https://example.com").unwrap()));
- fail_map.insert(source, HashSet::from_iter(vec![err1, err2]));
+ error_map.insert(source, HashSet::from_iter(vec![err1, err2]));
let stats = ResponseStats {
total: 1,
@@ -157,7 +157,7 @@ mod tests {
excludes: 0,
timeouts: 0,
duration_secs: 0,
- fail_map,
+ error_map,
suggestion_map: HashMap::default(),
unsupported: 0,
redirects: 0,
diff --git a/lychee-bin/src/formatters/stats/detailed.rs b/lychee-bin/src/formatters/stats/detailed.rs
index ae30d160b5..732d2d0898 100644
--- a/lychee-bin/src/formatters/stats/detailed.rs
+++ b/lychee-bin/src/formatters/stats/detailed.rs
@@ -49,7 +49,7 @@ impl Display for DetailedResponseStats {
let response_formatter = get_response_formatter(&self.mode);
- for (source, responses) in &stats.fail_map {
+ for (source, responses) in &stats.error_map {
// Using leading newlines over trailing ones (e.g. `writeln!`)
// lets us avoid extra newlines without any additional logic.
write!(f, "\n\nErrors in {source}")?;
@@ -115,9 +115,9 @@ mod tests {
status: Status::Ok(StatusCode::INTERNAL_SERVER_ERROR),
};
- let mut fail_map: HashMap> = HashMap::new();
+ let mut error_map: HashMap> = HashMap::new();
let source = InputSource::RemoteUrl(Box::new(Url::parse("https://example.com").unwrap()));
- fail_map.insert(source, HashSet::from_iter(vec![err1, err2]));
+ error_map.insert(source, HashSet::from_iter(vec![err1, err2]));
let stats = ResponseStats {
total: 2,
@@ -132,7 +132,7 @@ mod tests {
cached: 0,
suggestion_map: HashMap::default(),
success_map: HashMap::default(),
- fail_map,
+ error_map,
excluded_map: HashMap::default(),
detailed_stats: true,
};
diff --git a/lychee-bin/src/formatters/stats/markdown.rs b/lychee-bin/src/formatters/stats/markdown.rs
index e4cf11666b..1ab507f1e7 100644
--- a/lychee-bin/src/formatters/stats/markdown.rs
+++ b/lychee-bin/src/formatters/stats/markdown.rs
@@ -100,7 +100,7 @@ impl Display for MarkdownResponseStats {
writeln!(f)?;
writeln!(f, "{}", stats_table(&self.0))?;
- write_stats_per_input(f, "Errors", &stats.fail_map, |response| {
+ write_stats_per_input(f, "Errors", &stats.error_map, |response| {
markdown_response(response).map_err(|_e| fmt::Error)
})?;
diff --git a/lychee-bin/src/main.rs b/lychee-bin/src/main.rs
index cd96c27f75..bb6ac4aac9 100644
--- a/lychee-bin/src/main.rs
+++ b/lychee-bin/src/main.rs
@@ -344,7 +344,7 @@ async fn run(opts: &LycheeOptions) -> Result {
let (stats, cache, exit_code) = commands::check(params).await?;
let github_issues = stats
- .fail_map
+ .error_map
.values()
.flatten()
.any(|body| body.uri.domain() == Some("github.com"));
diff --git a/lychee-bin/src/stats.rs b/lychee-bin/src/stats.rs
index 2be7dd1162..fc5839902d 100644
--- a/lychee-bin/src/stats.rs
+++ b/lychee-bin/src/stats.rs
@@ -11,7 +11,7 @@ use serde::Serialize;
///
/// This struct contains various counters for the responses received during a
/// run. It also contains maps to store the responses for each status (success,
-/// fail, excluded, etc.) and the sources of the responses.
+/// error, excluded, etc.) and the sources of the responses.
///
/// The `detailed_stats` field is used to enable or disable the storage of the
/// responses in the maps for successful and excluded responses. If it's set to
@@ -39,7 +39,7 @@ pub(crate) struct ResponseStats {
/// Map to store successful responses (if `detailed_stats` is enabled)
pub(crate) success_map: HashMap>,
/// Map to store failed responses (if `detailed_stats` is enabled)
- pub(crate) fail_map: HashMap>,
+ pub(crate) error_map: HashMap>,
/// Replacement suggestions for failed responses (if `--suggest` is enabled)
pub(crate) suggestion_map: HashMap>,
/// Map to store excluded responses (if `detailed_stats` is enabled)
@@ -91,7 +91,7 @@ impl ResponseStats {
let status = response.status();
let source = response.source().clone();
let status_map_entry = match status {
- _ if status.is_error() => self.fail_map.entry(source).or_default(),
+ _ if status.is_error() => self.error_map.entry(source).or_default(),
Status::Ok(_) if self.detailed_stats => self.success_map.entry(source).or_default(),
Status::Excluded if self.detailed_stats => self.excluded_map.entry(source).or_default(),
_ => return,
@@ -174,9 +174,9 @@ mod tests {
stats.add(dummy_ok());
let response = dummy_error();
- let expected_fail_map: HashMap> =
+ let expected_error_map: HashMap> =
HashMap::from_iter([(response.source().clone(), HashSet::from_iter([response.1]))]);
- assert_eq!(stats.fail_map, expected_fail_map);
+ assert_eq!(stats.error_map, expected_error_map);
assert!(stats.success_map.is_empty());
}
@@ -185,20 +185,20 @@ mod tests {
async fn test_detailed_stats() {
let mut stats = ResponseStats::extended();
assert!(stats.success_map.is_empty());
- assert!(stats.fail_map.is_empty());
+ assert!(stats.error_map.is_empty());
assert!(stats.excluded_map.is_empty());
stats.add(dummy_error());
stats.add(dummy_excluded());
stats.add(dummy_ok());
- let mut expected_fail_map: HashMap> = HashMap::new();
+ let mut expected_error_map: HashMap> = HashMap::new();
let response = dummy_error();
- let entry = expected_fail_map
+ let entry = expected_error_map
.entry(response.source().clone())
.or_default();
entry.insert(response.1);
- assert_eq!(stats.fail_map, expected_fail_map);
+ assert_eq!(stats.error_map, expected_error_map);
let mut expected_success_map: HashMap> = HashMap::new();
let response = dummy_ok();
diff --git a/lychee-bin/tests/cli.rs b/lychee-bin/tests/cli.rs
index b72f9e9f40..e4fed44c44 100644
--- a/lychee-bin/tests/cli.rs
+++ b/lychee-bin/tests/cli.rs
@@ -87,7 +87,7 @@ mod cli {
errors: usize,
cached: usize,
success_map: HashMap>,
- fail_map: HashMap>,
+ error_map: HashMap>,
suggestion_map: HashMap>,
excluded_map: HashMap>,
}
@@ -156,6 +156,61 @@ mod cli {
Ok(())
}
+ /// Test JSON output format
+ #[tokio::test]
+ async fn test_json_output() -> Result<()> {
+ // Server that returns a bunch of 200 OK responses
+ let mock_server_ok = mock_server!(StatusCode::OK);
+ let mut cmd = main_command();
+ cmd.arg("--format")
+ .arg("json")
+ .arg("-vv")
+ .arg("--no-progress")
+ .arg("-")
+ .write_stdin(mock_server_ok.uri())
+ .assert()
+ .success();
+ let output = cmd.output().unwrap();
+ let output_json = serde_json::from_slice::(&output.stdout)?;
+
+ // Check that the output is valid JSON
+ assert!(output_json.is_object());
+ // Check that the output contains the expected keys
+ assert!(output_json.get("detailed_stats").is_some());
+ assert!(output_json.get("success_map").is_some());
+ assert!(output_json.get("error_map").is_some());
+ assert!(output_json.get("excluded_map").is_some());
+
+ // Check the success map
+ let success_map = output_json["success_map"].as_object().unwrap();
+ assert_eq!(success_map.len(), 1);
+
+ // Get the actual URL from the mock server for comparison
+ let mock_url = mock_server_ok.uri();
+
+ // Create the expected success map structure
+ let expected_success_map = serde_json::json!({
+ "stdin": [
+ {
+ "status": {
+ "code": 200,
+ "text": "200 OK"
+ },
+ "url": format!("{mock_url}/"),
+ }
+ ]
+ });
+
+ // Compare the actual success map with the expected one
+ assert_eq!(
+ success_map,
+ expected_success_map.as_object().unwrap(),
+ "Success map doesn't match expected structure"
+ );
+
+ Ok(())
+ }
+
/// JSON-formatted output should always be valid JSON.
/// Additional hints and error messages should be printed to `stderr`.
/// See https://github.com/lycheeverse/lychee/issues/1355
@@ -195,9 +250,10 @@ mod cli {
// Check that the output is valid JSON
assert!(serde_json::from_slice::(&output.stdout).is_ok());
- // Parse site error status from the fail_map
+ // Parse site error status from the error_map
let output_json = serde_json::from_slice::(&output.stdout).unwrap();
- let site_error_status = &output_json["fail_map"][&test_path.to_str().unwrap()][0]["status"];
+ let site_error_status =
+ &output_json["error_map"][&test_path.to_str().unwrap()][0]["status"];
assert_eq!(
"error sending request for url (https://expired.badssl.com/) Maybe a certificate error?",
diff --git a/lychee-lib/src/types/response.rs b/lychee-lib/src/types/response.rs
index 33ab6f3fd3..7609e84b97 100644
--- a/lychee-lib/src/types/response.rs
+++ b/lychee-lib/src/types/response.rs
@@ -7,7 +7,7 @@ use crate::{InputSource, Status, Uri};
/// Response type returned by lychee after checking a URI
//
-// Body is public to allow inserting into stats maps (fail_map, success_map,
+// Body is public to allow inserting into stats maps (error_map, success_map,
// etc.) without `Clone`, because the inner `ErrorKind` in `response.status` is
// not `Clone`. Use `body()` to access the body in the rest of the code.
//