use std::{collections::HashMap, fmt::Formatter, io};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, de};
use smallvec::SmallVec;
use crate::{
architectures::Architecture, archive::Component, package::PackageName, utils::DateTimeVisitor,
version::PackageVersion,
};
fn deserialize_datetime<'de, D>(deserializer: D) -> std::result::Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(DateTimeVisitor("%Y-%m-%d %H:%M:%S%.f%:z"))
}
fn deserialize_version<'de, D>(
deserializer: D,
) -> std::result::Result<Option<PackageVersion>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Debug)]
struct Visitor;
impl de::Visitor<'_> for Visitor {
type Value = Option<PackageVersion>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
write!(formatter, "a package version or '-'")
}
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
if s == "-" {
Ok(None)
} else {
PackageVersion::try_from(s)
.map_err(|_| de::Error::invalid_value(de::Unexpected::Str(s), &self))
.map(Some)
}
}
}
deserializer.deserialize_str(Visitor)
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Excuses {
#[serde(deserialize_with = "deserialize_datetime")]
pub generated_date: DateTime<Utc>,
pub sources: Vec<ExcusesItem>,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum Verdict {
#[serde(rename = "PASS")]
Pass,
#[serde(rename = "PASS_HINTED")]
PassHinted,
#[serde(rename = "REJECTED_NEEDS_APPROVAL")]
RejectedNeedsApproval,
#[serde(rename = "REJECTED_PERMANENTLY")]
RejectedPermanently,
#[serde(rename = "REJECTED_TEMPORARILY")]
RejectedTemporarily,
#[serde(rename = "REJECTED_CANNOT_DETERMINE_IF_PERMANENT")]
RejectedCannotDetermineIfPermanent,
#[serde(rename = "REJECTED_BLOCKED_BY_ANOTHER_ITEM")]
RejectedBlockedByAnotherItem,
#[serde(rename = "REJECTED_WAITING_FOR_ANOTHER_ITEM")]
RejectedWaitingForAnotherItem,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct AgeInfo {
pub age_requirement: u32,
pub current_age: u32,
pub verdict: Verdict,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct UnspecfiedPolicyInfo {
pub verdict: Verdict,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct BuiltOnBuildd {
pub signed_by: HashMap<Architecture, Option<String>>,
pub verdict: Verdict,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PolicyInfo {
pub age: Option<AgeInfo>,
pub builtonbuildd: Option<BuiltOnBuildd>,
pub autopkgtest: Option<UnspecfiedPolicyInfo>,
#[serde(flatten)]
pub extras: HashMap<String, UnspecfiedPolicyInfo>,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct MissingBuilds {
pub on_architectures: SmallVec<[Architecture; 16]>,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExcusesItem {
pub maintainer: Option<String>,
pub is_candidate: bool,
#[serde(deserialize_with = "deserialize_version")]
pub new_version: Option<PackageVersion>,
#[serde(deserialize_with = "deserialize_version")]
pub old_version: Option<PackageVersion>,
pub item_name: String,
pub source: PackageName,
pub invalidated_by_other_package: Option<bool>,
pub component: Option<Component>,
pub missing_builds: Option<MissingBuilds>,
#[serde(rename = "policy_info")]
pub policy_info: Option<PolicyInfo>,
pub excuses: Vec<String>,
pub migration_policy_verdict: Verdict,
}
impl ExcusesItem {
pub fn is_removal(&self) -> bool {
self.new_version.is_none()
}
pub fn is_binnmu(&self) -> bool {
self.new_version == self.old_version
}
pub fn binnmu_arch(&self) -> Option<Architecture> {
self.item_name.split_once('/').map(|(_, arch)| {
arch.split_once('_')
.map_or(arch, |(arch, _)| arch)
.try_into()
.unwrap()
})
}
pub fn is_from_pu(&self) -> bool {
self.item_name.ends_with("_pu")
}
pub fn is_from_tpu(&self) -> bool {
self.item_name.ends_with("_tpu")
}
}
pub type Result<T> = serde_yaml::Result<T>;
pub fn from_reader(reader: impl io::Read) -> Result<Excuses> {
serde_yaml::from_reader(reader)
}
pub fn from_str(data: &str) -> Result<Excuses> {
serde_yaml::from_str(data)
}