Skip to content

Commit a2b72ec

Browse files
committed
Update redirect_map
1 parent a273181 commit a2b72ec

8 files changed

Lines changed: 116 additions & 82 deletions

File tree

lychee-bin/src/formatters/stats/detailed.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ https://original.dev/ --> https://suggestion.dev/
153153
154154
155155
Redirects in https://example.com/
156-
https://redirected.dev/ (at 1:1) | 200 OK | Followed 2 redirects. Redirects: https://1.dev/ --[308]--> https://2.dev/ --[308]--> http://redirected.dev/
156+
https://1.dev/ --[308]--> https://2.dev/ --[308]--> http://redirected.dev/
157157
158158
159159
📊 Per-host Statistics

lychee-bin/src/formatters/stats/json.rs

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -84,32 +84,17 @@ mod tests {
8484
"redirect_map": {
8585
"https://example.com/": [
8686
{
87-
"url": "https://redirected.dev/",
88-
"status": {
89-
"text": "200 OK",
90-
"code": 200,
91-
"redirects": {
92-
"origin": "https://1.dev/",
93-
"redirects": [
94-
{
95-
"url": "https://2.dev/",
96-
"code": 308
97-
},
98-
{
99-
"url": "http://redirected.dev/",
100-
"code": 308
101-
}
102-
]
87+
"origin": "https://1.dev/",
88+
"redirects": [
89+
{
90+
"url": "https://2.dev/",
91+
"code": 308
92+
},
93+
{
94+
"url": "http://redirected.dev/",
95+
"code": 308
10396
}
104-
},
105-
"span": {
106-
"line": 1,
107-
"column": 1
108-
},
109-
"duration": {
110-
"secs": 1,
111-
"nanos": 0
112-
}
97+
]
11398
}
11499
]
115100
},

lychee-bin/src/formatters/stats/junit.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,8 @@ fn junit_testcases(stats: ResponseStats) -> Vec<TestCase> {
5050
let skipped = junit_testcases_group(stats.excluded_map, TestCaseStatus::skipped(), "Excluded");
5151
let successes =
5252
junit_testcases_group(stats.success_map, TestCaseStatus::success(), "Successful");
53-
let redirected =
54-
junit_testcases_group(stats.redirect_map, TestCaseStatus::success(), "Redirected");
5553

56-
[failures, skipped, successes, redirected].concat()
54+
[failures, skipped, successes].concat()
5755
}
5856

5957
fn junit_testcases_group(
@@ -110,8 +108,8 @@ mod tests {
110108
assert_eq!(
111109
result,
112110
r#"<?xml version="1.0" encoding="UTF-8"?>
113-
<testsuites name="lychee link check results" tests="4" failures="1" errors="0">
114-
<testsuite name="lychee link check results" tests="4" disabled="1" errors="0" failures="1">
111+
<testsuites name="lychee link check results" tests="3" failures="1" errors="0">
112+
<testsuite name="lychee link check results" tests="3" disabled="1" errors="0" failures="1">
115113
<testcase name="Failed https://github.com/mre/idiomatic-rust-doesnt-exist-man" time="1.000" file="https://example.com/" line="1">
116114
<failure message="https://github.com/mre/idiomatic-rust-doesnt-exist-man (at 1:1) | 404 Not Found"/>
117115
<system-out>https://github.com/mre/idiomatic-rust-doesnt-exist-man (at 1:1) | 404 Not Found</system-out>
@@ -123,9 +121,6 @@ mod tests {
123121
<testcase name="Successful https://success.org/" time="1.000" file="https://example.com/">
124122
<system-out>https://success.org/</system-out>
125123
</testcase>
126-
<testcase name="Redirected https://redirected.dev/" time="1.000" file="https://example.com/" line="1">
127-
<system-out>https://redirected.dev/ (at 1:1) | 200 OK | Followed 2 redirects. Redirects: https://1.dev/ --[308]--&gt; https://2.dev/ --[308]--&gt; http://redirected.dev/</system-out>
128-
</testcase>
129124
</testsuite>
130125
</testsuites>
131126
"#

lychee-bin/src/formatters/stats/markdown.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,12 @@ impl Display for MarkdownResponseStats {
116116
markdown_response(response).map_err(|_| fmt::Error)
117117
})?;
118118

119-
write_stats_per_input(f, "Redirects", &stats.redirect_map, |response| {
120-
markdown_response(response).map_err(|_| fmt::Error)
119+
write_stats_per_input(f, "Redirects", &stats.redirect_map, |redirects| {
120+
Ok(format!("* {redirects}"))
121121
})?;
122122

123123
write_stats_per_input(f, "Suggestions", &stats.suggestion_map, |suggestion| {
124-
Ok(format!(
125-
"* {} --> {}",
126-
suggestion.original, suggestion.suggestion
127-
))
124+
Ok(format!("* {suggestion}"))
128125
})?;
129126

130127
Ok(())
@@ -284,7 +281,7 @@ mod tests {
284281
285282
### Redirects in https://example.com/
286283
287-
* [200] <https://redirected.dev/> (at 1:1) | 200 OK | Followed 2 redirects. Redirects: https://1.dev/ --[308]--> https://2.dev/ --[308]--> http://redirected.dev/
284+
* https://1.dev/ --[308]--> https://2.dev/ --[308]--> http://redirected.dev/
288285
289286
## Suggestions per input
290287

lychee-bin/src/formatters/stats/mod.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,7 @@ fn get_dummy_stats() -> OutputStats {
161161
code: StatusCode::PERMANENT_REDIRECT,
162162
});
163163

164-
let redirect_map = HashMap::from([(
165-
source.clone(),
166-
HashSet::from([ResponseBody {
167-
uri: "https://redirected.dev".try_into().unwrap(),
168-
status: Status::Redirected(Box::new(Status::Ok(StatusCode::OK)), redirects),
169-
span: SPAN,
170-
duration: DURATION,
171-
}]),
172-
)]);
164+
let redirect_map = HashMap::from([(source.clone(), HashSet::from([redirects]))]);
173165

174166
let response_stats = ResponseStats {
175167
total: 2,

lychee-bin/src/formatters/stats/response.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
time::Duration,
77
};
88

9-
use lychee_lib::{CacheStatus, InputSource, Response, ResponseBody, Status};
9+
use lychee_lib::{CacheStatus, InputSource, Redirects, Response, ResponseBody, Status};
1010
use serde::Serialize;
1111

1212
use crate::formatters::suggestion::Suggestion;
@@ -48,10 +48,10 @@ pub(crate) struct ResponseStats {
4848
pub(crate) error_map: HashMap<InputSource, HashSet<ResponseBody>>,
4949
/// Timed out responses
5050
pub(crate) timeout_map: HashMap<InputSource, HashSet<ResponseBody>>,
51-
/// Replacement suggestions for failed responses (if `--suggest` is enabled)
51+
/// [`Suggestion`]s for failed responses (if `--suggest` is enabled)
5252
pub(crate) suggestion_map: HashMap<InputSource, HashSet<Suggestion>>,
53-
/// Redirected responses (if `detailed_stats` is enabled)
54-
pub(crate) redirect_map: HashMap<InputSource, HashSet<ResponseBody>>,
53+
/// All [`Redirects`] independent of the response status (if `detailed_stats` is enabled)
54+
pub(crate) redirect_map: HashMap<InputSource, HashSet<Redirects>>,
5555
/// Excluded responses (if `detailed_stats` is enabled)
5656
pub(crate) excluded_map: HashMap<InputSource, HashSet<ResponseBody>>,
5757
/// The time it took to perform the full run
@@ -107,16 +107,28 @@ impl ResponseStats {
107107
fn add_response_status(&mut self, response: Response) {
108108
let status = response.status();
109109
let source: InputSource = response.source().clone();
110-
let status_map_entry = match status {
111-
_ if status.is_timeout() => self.timeout_map.entry(source).or_default(),
112-
_ if status.is_error() => self.error_map.entry(source).or_default(),
113-
Status::Ok(_) if self.detailed_stats => self.success_map.entry(source).or_default(),
114-
Status::Excluded if self.detailed_stats => self.excluded_map.entry(source).or_default(),
115-
Status::Redirected(_, _) if self.detailed_stats => {
116-
self.redirect_map.entry(source).or_default()
117-
}
118-
_ => return,
110+
111+
if self.detailed_stats
112+
&& let Some(redirects) = status.redirects()
113+
{
114+
self.redirect_map
115+
.entry(source.clone())
116+
.or_default()
117+
.insert(redirects.clone());
118+
}
119+
120+
let status_map_entry = if status.is_timeout() {
121+
self.timeout_map.entry(source).or_default()
122+
} else if status.is_error() {
123+
self.error_map.entry(source).or_default()
124+
} else if status.is_excluded() {
125+
self.excluded_map.entry(source).or_default()
126+
} else if status.is_success() && self.detailed_stats {
127+
self.success_map.entry(source).or_default()
128+
} else {
129+
return;
119130
};
131+
120132
status_map_entry.insert(response.into_body());
121133
}
122134

lychee-bin/tests/cli.rs

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ mod cli {
8080

8181
/// Parse lychee's json output to [`Value`].
8282
/// Additionally remove the non-deterministic duration to simplify subsequent assertions
83-
fn stdout_to_json(stdout: &[u8]) -> Result<Value> {
84-
let mut output_json = serde_json::from_slice::<Value>(stdout)?;
83+
fn stdout_to_json(stdout: &[u8]) -> Value {
84+
let mut output_json =
85+
serde_json::from_slice::<Value>(stdout).expect("stdout is not valid JSON");
8586
remove_nondeterministic_duration(&mut output_json["success_map"]);
86-
remove_nondeterministic_duration(&mut output_json["redirect_map"]);
87-
return Ok(output_json);
87+
remove_nondeterministic_duration(&mut output_json["excluded_map"]);
88+
return output_json;
8889

8990
fn remove_nondeterministic_duration(value: &mut Value) {
9091
let map = value.as_object_mut().expect("Expected object");
@@ -197,7 +198,7 @@ mod cli {
197198
.assert()
198199
.success();
199200
let output = cmd.output().unwrap();
200-
let output_json = stdout_to_json(&output.stdout)?;
201+
let output_json = stdout_to_json(&output.stdout);
201202

202203
// Check that the output is valid JSON
203204
assert!(output_json.is_object());
@@ -257,7 +258,7 @@ mod cli {
257258
let output = cmd.output()?;
258259

259260
// Check that the output is valid JSON
260-
stdout_to_json(&output.stdout)?;
261+
stdout_to_json(&output.stdout);
261262
Ok(())
262263
}
263264

@@ -276,7 +277,7 @@ mod cli {
276277
let output = cmd.output()?;
277278

278279
// Parse site error status from the error_map
279-
let output_json = stdout_to_json(&output.stdout)?;
280+
let output_json = stdout_to_json(&output.stdout);
280281
let site_error_status =
281282
&output_json["error_map"][&test_path.to_str().unwrap()][0]["status"];
282283

@@ -901,24 +902,20 @@ mod cli {
901902

902903
/// Test excludes
903904
#[test]
904-
fn test_exclude_wildcard() -> Result<()> {
905+
fn test_exclude_wildcard() {
905906
let test_path = fixtures_path!().join("TEST.md");
906-
907907
cargo_bin_cmd!()
908908
.arg(test_path)
909909
.arg("--exclude")
910910
.arg(".*")
911911
.assert()
912912
.success()
913913
.stdout(contains("12 Excluded"));
914-
915-
Ok(())
916914
}
917915

918916
#[test]
919-
fn test_exclude_multiple_urls() -> Result<()> {
917+
fn test_exclude_multiple_urls() {
920918
let test_path = fixtures_path!().join("TEST.md");
921-
922919
cargo_bin_cmd!()
923920
.arg(test_path)
924921
.arg("--exclude")
@@ -928,8 +925,6 @@ mod cli {
928925
.assert()
929926
.success()
930927
.stdout(contains("4 Excluded"));
931-
932-
Ok(())
933928
}
934929

935930
#[tokio::test]
@@ -1913,7 +1908,7 @@ The config file should contain every possible key for documentation purposes."
19131908
}
19141909

19151910
#[test]
1916-
fn test_erroneous_remap_with_redirect_real_world() {
1911+
fn test_erroneous_remap_with_redirect() {
19171912
cargo_bin_cmd!()
19181913
.arg("-vv")
19191914
.arg("--remap")
@@ -1948,6 +1943,42 @@ The config file should contain every possible key for documentation purposes."
19481943
.stdout(contains("The given URI is invalid, check URI syntax: https://example.com/"));
19491944
}
19501945

1946+
#[test]
1947+
fn test_remap_to_excluded() {
1948+
let stdout = cargo_bin_cmd!()
1949+
.arg("--remap=aaa bbb")
1950+
.arg("--exclude=bbb")
1951+
.arg("--format=json")
1952+
.arg("-")
1953+
.write_stdin("https://aaa.com")
1954+
.assert()
1955+
.success()
1956+
.get_output()
1957+
.stdout
1958+
.clone();
1959+
1960+
let json = stdout_to_json(&stdout);
1961+
assert_eq!(json["remaps"], 1);
1962+
assert_eq!(json["excludes"], 1);
1963+
assert_eq!(
1964+
json["excluded_map"],
1965+
json!({
1966+
"stdin": [
1967+
{
1968+
"url": "https://bbb.com/",
1969+
"status": {
1970+
"text": "Excluded",
1971+
"details": "This is due to your 'exclude' values | Remapped: https://aaa.com/ --> https://bbb.com/"
1972+
},
1973+
"span": {
1974+
"line": 1,
1975+
"column": 1
1976+
},
1977+
}
1978+
]})
1979+
);
1980+
}
1981+
19511982
#[test]
19521983
fn test_excluded_paths_regex() {
19531984
let test_path = fixtures_path!().join("exclude-path");
@@ -2574,7 +2605,8 @@ The config file should contain every possible key for documentation purposes."
25742605
assert_eq!(json["total"], 1);
25752606
assert_eq!(json["redirects"], 1); // there was one redirect
25762607
assert_eq!(json["successful"], 1); // which resolved to a success
2577-
assert_eq!(json["redirect_map"], json!({}));
2608+
assert_eq!(json["redirect_map"], json!({})); // suppressed in non-verbose mode
2609+
assert_eq!(json["success_map"], json!({})); // suppressed in non-verbose mode
25782610
})
25792611
.await;
25802612

@@ -2587,6 +2619,17 @@ The config file should contain every possible key for documentation purposes."
25872619
assert_eq!(
25882620
json["redirect_map"],
25892621
json!({
2622+
"stdin":[{
2623+
"origin": redirect_url,
2624+
"redirects": [{
2625+
"code": 308,
2626+
"url": ok_url,
2627+
}]
2628+
}]})
2629+
);
2630+
assert_eq!(
2631+
json["success_map"],
2632+
json!({
25902633
"stdin":[{
25912634
"span": {
25922635
"column": 1,
@@ -2630,7 +2673,7 @@ The config file should contain every possible key for documentation purposes."
26302673

26312674
let stderr = str::from_utf8(&output.stderr).unwrap().to_string();
26322675

2633-
(stdout_to_json(&output.stdout).unwrap(), stderr)
2676+
(stdout_to_json(&output.stdout), stderr)
26342677
}
26352678
}
26362679

lychee-lib/src/types/status.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ impl Status {
229229
matches!(self.innermost(), Status::UnknownStatusCode(_))
230230
}
231231

232-
/// Extract the innermost [`Status`], handling nested variants.
232+
/// Extract the innermost [`Status`], handling nested variants
233233
#[must_use]
234234
pub const fn innermost(&self) -> &Self {
235235
match self {
@@ -238,6 +238,16 @@ impl Status {
238238
}
239239
}
240240

241+
/// Extract (potentially nested) [`Redirects`]
242+
#[must_use]
243+
pub const fn redirects(&self) -> Option<&Redirects> {
244+
match self {
245+
Status::Remapped(inner, _) => inner.redirects(),
246+
Status::Redirected(_, redirects) => Some(redirects),
247+
_ => None,
248+
}
249+
}
250+
241251
/// Return a unicode icon to visualize the status
242252
#[must_use]
243253
pub const fn icon(&self) -> &str {

0 commit comments

Comments
 (0)