use std::collections::BTreeMap;
use crate::collect::errors::CollectError;
use crate::collect::fault::CollectionFault;
const MAX_LISTED: usize = 20;
pub(crate) fn http_status(err: &CollectError) -> Option<u16> {
match err {
CollectError::Http(e) => e.status().map(|s| s.as_u16()),
_ => None,
}
}
pub(crate) struct FaultWording<'a> {
pub(crate) label: &'a str,
pub(crate) noun: &'a str,
pub(crate) fetch: &'a str,
pub(crate) lost: &'a str,
}
#[derive(Debug, Default)]
pub(crate) struct FetchFaults {
missing: Vec<String>,
failed: Vec<(String, Option<u16>)>,
}
impl FetchFaults {
pub(crate) fn record(&mut self, item: String, status: Option<u16>) {
if status == Some(404) {
self.missing.push(item);
} else {
self.failed.push((item, status));
}
}
pub(crate) fn into_faults(
mut self,
w: &FaultWording<'_>,
total: usize,
) -> Vec<CollectionFault> {
let mut out = Vec::new();
if !self.missing.is_empty() {
self.missing.sort();
out.push(CollectionFault::item_skipped(format!(
"{}: {} of {total} {} answered HTTP 404 on the {} and {}: {}. The repository \
or pull request is gone, renamed, or not visible to the token (see #146)",
w.label,
self.missing.len(),
w.noun,
w.fetch,
w.lost,
listed(self.missing.iter().map(String::clone)),
)));
}
if !self.failed.is_empty() {
self.failed.sort();
let mut counts = BTreeMap::<String, usize>::new();
for (_, status) in &self.failed {
*counts.entry(status_label(*status)).or_default() += 1;
}
let counts = counts
.iter()
.map(|(l, n)| format!("{l}: {n}"))
.collect::<Vec<_>>()
.join(", ");
out.push(CollectionFault::stage_failed(format!(
"{}: {} of {total} {} failed the {} and {} ({counts}): {}. An HTTP 403 usually \
means the token lacks the scope or SAML SSO authorization for that repository \
(see #146)",
w.label,
self.failed.len(),
w.noun,
w.fetch,
w.lost,
listed(
self.failed
.iter()
.map(|(item, s)| format!("{item} ({})", status_label(*s)))
),
)));
}
out
}
}
fn status_label(status: Option<u16>) -> String {
match status {
Some(code) => format!("HTTP {code}"),
None => "no HTTP status".to_string(),
}
}
fn listed(items: impl ExactSizeIterator<Item = String>) -> String {
let n = items.len();
let mut s = items.take(MAX_LISTED).collect::<Vec<_>>().join(", ");
if n > MAX_LISTED {
s.push_str(&format!(", and {} more", n - MAX_LISTED));
}
s
}