1use std::{collections::HashMap, fmt::Formatter, io};
10
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Deserializer, de};
13use smallvec::SmallVec;
14
15use crate::{
16 architectures::Architecture, archive::Component, package::PackageName, utils::DateTimeVisitor,
17 version::PackageVersion,
18};
19
20fn deserialize_datetime<'de, D>(deserializer: D) -> std::result::Result<DateTime<Utc>, D::Error>
22where
23 D: Deserializer<'de>,
24{
25 deserializer.deserialize_str(DateTimeVisitor("%Y-%m-%d %H:%M:%S%.f%:z"))
26}
27
28fn deserialize_version<'de, D>(
30 deserializer: D,
31) -> std::result::Result<Option<PackageVersion>, D::Error>
32where
33 D: Deserializer<'de>,
34{
35 #[derive(Debug)]
36 struct Visitor;
37
38 impl de::Visitor<'_> for Visitor {
39 type Value = Option<PackageVersion>;
40
41 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
42 write!(formatter, "a package version or '-'")
43 }
44
45 fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
46 where
47 E: de::Error,
48 {
49 if s == "-" {
50 Ok(None)
51 } else {
52 PackageVersion::try_from(s)
53 .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(s), &self))
54 .map(Some)
55 }
56 }
57 }
58
59 deserializer.deserialize_str(Visitor)
60}
61
62#[derive(Debug, PartialEq, Eq, Deserialize)]
64#[serde(rename_all = "kebab-case")]
65pub struct Excuses {
66 #[serde(deserialize_with = "deserialize_datetime")]
68 pub generated_date: DateTime<Utc>,
69 pub sources: Vec<ExcusesItem>,
73}
74
75#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
77pub enum Verdict {
78 #[serde(rename = "PASS")]
80 Pass,
81 #[serde(rename = "PASS_HINTED")]
83 PassHinted,
84 #[serde(rename = "REJECTED_NEEDS_APPROVAL")]
87 RejectedNeedsApproval,
88 #[serde(rename = "REJECTED_PERMANENTLY")]
90 RejectedPermanently,
91 #[serde(rename = "REJECTED_TEMPORARILY")]
93 RejectedTemporarily,
94 #[serde(rename = "REJECTED_CANNOT_DETERMINE_IF_PERMANENT")]
96 RejectedCannotDetermineIfPermanent,
97 #[serde(rename = "REJECTED_BLOCKED_BY_ANOTHER_ITEM")]
99 RejectedBlockedByAnotherItem,
100 #[serde(rename = "REJECTED_WAITING_FOR_ANOTHER_ITEM")]
102 RejectedWaitingForAnotherItem,
103}
104
105#[derive(Debug, PartialEq, Eq, Deserialize)]
107#[serde(rename_all = "kebab-case")]
108pub struct AgeInfo {
109 pub age_requirement: u32,
111 pub current_age: u32,
113 pub verdict: Verdict,
115}
116
117#[derive(Debug, PartialEq, Eq, Deserialize)]
119#[serde(rename_all = "kebab-case")]
120pub struct UnspecfiedPolicyInfo {
121 pub verdict: Verdict,
123}
124
125#[derive(Debug, PartialEq, Eq, Deserialize)]
127#[serde(rename_all = "kebab-case")]
128pub struct BuiltOnBuildd {
129 pub signed_by: HashMap<Architecture, Option<String>>,
131 pub verdict: Verdict,
133}
134
135#[derive(Debug, PartialEq, Eq, Deserialize)]
137#[serde(rename_all = "kebab-case")]
138pub struct PolicyInfo {
139 pub age: Option<AgeInfo>,
141 pub builtonbuildd: Option<BuiltOnBuildd>,
143 pub autopkgtest: Option<UnspecfiedPolicyInfo>,
145 #[serde(flatten)]
147 pub extras: HashMap<String, UnspecfiedPolicyInfo>,
148 }
158
159#[derive(Debug, PartialEq, Eq, Deserialize)]
161#[serde(rename_all = "kebab-case")]
162pub struct MissingBuilds {
163 pub on_architectures: SmallVec<[Architecture; 16]>,
166}
167
168#[derive(Debug, PartialEq, Eq, Deserialize)]
170#[serde(rename_all = "kebab-case")]
171pub struct ExcusesItem {
172 pub maintainer: Option<String>,
174 pub is_candidate: bool,
176 #[serde(deserialize_with = "deserialize_version")]
180 pub new_version: Option<PackageVersion>,
181 #[serde(deserialize_with = "deserialize_version")]
185 pub old_version: Option<PackageVersion>,
186 pub item_name: String,
188 pub source: PackageName,
190 pub invalidated_by_other_package: Option<bool>,
192 pub component: Option<Component>,
194 pub missing_builds: Option<MissingBuilds>,
196 #[serde(rename = "policy_info")]
198 pub policy_info: Option<PolicyInfo>,
199 pub excuses: Vec<String>,
201 pub migration_policy_verdict: Verdict,
203}
204
205impl ExcusesItem {
206 pub fn is_removal(&self) -> bool {
208 self.new_version.is_none()
209 }
210
211 pub fn is_binnmu(&self) -> bool {
213 self.new_version == self.old_version
214 }
215
216 pub fn binnmu_arch(&self) -> Option<Architecture> {
218 self.item_name.split_once('/').map(|(_, arch)| {
219 arch.split_once('_')
220 .map_or(arch, |(arch, _)| arch)
221 .try_into()
222 .unwrap()
223 })
224 }
225
226 pub fn is_from_pu(&self) -> bool {
228 self.item_name.ends_with("_pu")
229 }
230
231 pub fn is_from_tpu(&self) -> bool {
233 self.item_name.ends_with("_tpu")
234 }
235}
236
237pub type Result<T> = serde_yaml::Result<T>;
239
240pub fn from_reader(reader: impl io::Read) -> Result<Excuses> {
242 serde_yaml::from_reader(reader)
243}
244
245pub fn from_str(data: &str) -> Result<Excuses> {
247 serde_yaml::from_str(data)
248}