1use 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 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 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}