Skip to main content

codoseo_core/
change.rs

1//! A difference between two crawls of the same site.
2
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6use crate::check::Severity;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ChangeKind {
11    NewUrl,
12    RemovedUrl,
13    StatusChanged,
14    BecameNoindex,
15    TitleChanged,
16    TitleRemoved,
17    CanonicalChanged,
18    RedirectChainGrew,
19    RobotsTxtChanged,
20    SitemapShrank,
21    ErrorSpike,
22    SiteMoved,
23}
24
25impl ChangeKind {
26    /// The stable machine name (`became_noindex`), the same as in the database and in webhooks.
27    pub fn slug(self) -> &'static str {
28        match self {
29            ChangeKind::NewUrl => "new_url",
30            ChangeKind::RemovedUrl => "removed_url",
31            ChangeKind::StatusChanged => "status_changed",
32            ChangeKind::BecameNoindex => "became_noindex",
33            ChangeKind::TitleChanged => "title_changed",
34            ChangeKind::TitleRemoved => "title_removed",
35            ChangeKind::CanonicalChanged => "canonical_changed",
36            ChangeKind::RedirectChainGrew => "redirect_chain_grew",
37            ChangeKind::RobotsTxtChanged => "robots_txt_changed",
38            ChangeKind::SitemapShrank => "sitemap_shrank",
39            ChangeKind::ErrorSpike => "error_spike",
40            ChangeKind::SiteMoved => "site_moved",
41        }
42    }
43
44    /// What a person reads in an alert or the settings grid.
45    pub fn label(self) -> &'static str {
46        match self {
47            ChangeKind::NewUrl => "New URL",
48            ChangeKind::RemovedUrl => "URL removed",
49            ChangeKind::StatusChanged => "Status changed",
50            ChangeKind::BecameNoindex => "Became noindex",
51            ChangeKind::TitleChanged => "Title changed",
52            ChangeKind::TitleRemoved => "Title removed",
53            ChangeKind::CanonicalChanged => "Canonical changed",
54            ChangeKind::RedirectChainGrew => "Redirect chain grew",
55            ChangeKind::RobotsTxtChanged => "robots.txt changed",
56            ChangeKind::SitemapShrank => "Sitemap shrank",
57            ChangeKind::ErrorSpike => "Error spike (4xx/5xx)",
58            ChangeKind::SiteMoved => "Site moved",
59        }
60    }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct Change {
65    pub kind: ChangeKind,
66    pub severity: Severity,
67    pub url: Option<Url>,
68    pub before: String,
69    pub after: String,
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn slugs_match_the_serialized_names_and_labels_are_distinct() {
78        let all = [
79            ChangeKind::NewUrl,
80            ChangeKind::RemovedUrl,
81            ChangeKind::StatusChanged,
82            ChangeKind::BecameNoindex,
83            ChangeKind::TitleChanged,
84            ChangeKind::TitleRemoved,
85            ChangeKind::CanonicalChanged,
86            ChangeKind::RedirectChainGrew,
87            ChangeKind::RobotsTxtChanged,
88            ChangeKind::SitemapShrank,
89            ChangeKind::ErrorSpike,
90            ChangeKind::SiteMoved,
91        ];
92        for kind in all {
93            assert_eq!(
94                serde_json::to_value(kind).unwrap(),
95                serde_json::Value::String(kind.slug().to_owned())
96            );
97        }
98        let labels: std::collections::HashSet<_> = all.iter().map(|k| k.label()).collect();
99        assert_eq!(labels.len(), all.len());
100    }
101}