Skip to main content

codoseo_core/
report.rs

1//! The result of running the checks over a crawl.
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5use crate::check::CheckId;
6
7/// An internal link to a page that has a problem, kept as a sample for the report.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct InlinkSample {
10    /// Index of the page being linked to.
11    pub target: u32,
12    /// Index of the page holding the link.
13    pub source: u32,
14    pub anchor: String,
15    pub nofollow: bool,
16}
17
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
19pub struct StatusCounts {
20    pub ok: u32,
21    pub redirect: u32,
22    pub client_error: u32,
23    pub server_error: u32,
24    /// No response: timeouts, connection errors, redirect loops.
25    pub failed: u32,
26    pub blocked: u32,
27}
28
29#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
30pub struct CrawlSummary {
31    pub pages: u32,
32    pub indexable: u32,
33    pub status: StatusCounts,
34    /// Pages per click depth; index = clicks, the last bucket is 10 and deeper.
35    pub depth: Vec<u32>,
36    /// Pages with no depth (found only in sitemaps).
37    pub no_depth: u32,
38    pub avg_response_ms: u32,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct CrawlReport {
43    pub health_score: u8,
44    pub checks_passed: u16,
45    pub checks_total: u16,
46    /// Failing checks only, in `CheckId` order, with the number of affected pages
47    /// (1 for site-wide checks). When read back, entries for checks this version doesn't
48    /// know (added by a later one) are skipped, so newer audits still load.
49    #[serde(deserialize_with = "known_counts")]
50    pub counts: Vec<(CheckId, u32)>,
51    pub inlink_samples: Vec<InlinkSample>,
52    pub summary: CrawlSummary,
53}
54
55/// Reads `counts`, dropping entries whose check slug is unknown.
56fn known_counts<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<(CheckId, u32)>, D::Error> {
57    let raw = Vec::<(String, u32)>::deserialize(d)?;
58    Ok(raw
59        .into_iter()
60        .filter_map(|(slug, n)| Some((CheckId::from_slug(&slug)?, n)))
61        .collect())
62}