use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArchNewsItem {
pub date: String,
pub title: String,
pub url: String,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum AdvisorySeverity {
#[default]
Unknown,
Low,
Medium,
High,
Critical,
}
impl AdvisorySeverity {
#[must_use]
pub const fn rank(self) -> u8 {
match self {
Self::Critical => 5,
Self::High => 4,
Self::Medium => 3,
Self::Low => 2,
Self::Unknown => 1,
}
}
#[must_use]
pub fn parse(s: &str) -> Self {
match s.trim().to_ascii_lowercase().as_str() {
"low" => Self::Low,
"medium" => Self::Medium,
"high" => Self::High,
"critical" => Self::Critical,
_ => Self::Unknown,
}
}
}
impl std::fmt::Display for AdvisorySeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Unknown => "unknown",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Critical => "critical",
};
f.write_str(s)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecurityAdvisory {
pub id: String,
pub date: String,
pub title: String,
pub summary: Option<String>,
pub url: Option<String>,
pub severity: AdvisorySeverity,
pub packages: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn severity_ranks() {
assert_eq!(AdvisorySeverity::Critical.rank(), 5);
assert_eq!(AdvisorySeverity::High.rank(), 4);
assert_eq!(AdvisorySeverity::Medium.rank(), 3);
assert_eq!(AdvisorySeverity::Low.rank(), 2);
assert_eq!(AdvisorySeverity::Unknown.rank(), 1);
}
#[test]
fn severity_parsing() {
assert_eq!(
AdvisorySeverity::parse("Critical"),
AdvisorySeverity::Critical
);
assert_eq!(AdvisorySeverity::parse("HIGH"), AdvisorySeverity::High);
assert_eq!(
AdvisorySeverity::parse(" medium "),
AdvisorySeverity::Medium
);
assert_eq!(AdvisorySeverity::parse("low"), AdvisorySeverity::Low);
assert_eq!(AdvisorySeverity::parse("weird"), AdvisorySeverity::Unknown);
assert_eq!(AdvisorySeverity::parse(""), AdvisorySeverity::Unknown);
}
#[test]
fn serde_roundtrips() {
let item = ArchNewsItem {
date: "2026-07-01".to_string(),
title: "Grub update".to_string(),
url: "https://archlinux.org/news/grub-update/".to_string(),
};
let back: ArchNewsItem =
serde_json::from_str(&serde_json::to_string(&item).expect("serialize item"))
.expect("deserialize item");
assert_eq!(back, item);
let advisory = SecurityAdvisory {
id: "https://security.archlinux.org/ASA-202607-1".to_string(),
date: "2026-07-01".to_string(),
title: "ASA-202607-1: openssl: multiple issues".to_string(),
summary: Some("Multiple issues".to_string()),
url: Some("https://security.archlinux.org/ASA-202607-1".to_string()),
severity: AdvisorySeverity::High,
packages: vec!["openssl".to_string()],
};
let back: SecurityAdvisory =
serde_json::from_str(&serde_json::to_string(&advisory).expect("serialize advisory"))
.expect("deserialize advisory");
assert_eq!(back, advisory);
}
}