1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
4#[serde(rename_all = "snake_case")]
5#[non_exhaustive]
6pub enum ParityLevel {
7 Complete,
8 Partial,
9 #[serde(other)]
10 Unknown,
11}
12
13#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct ParityRatio {
16 pub numerator: u64,
17 pub denominator: u64,
18}
19
20#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
21#[non_exhaustive]
22pub struct ParityInventory {
23 pub total: u64,
24 pub complete: u64,
25 pub partial: u64,
26 pub not_applicable: u64,
27}
28
29#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
30#[non_exhaustive]
31pub struct ParityCheckpoint {
32 pub level: ParityLevel,
33 pub complete_ratio: ParityRatio,
34 pub inventory: ParityInventory,
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
38#[non_exhaustive]
39pub struct ReferenceRevision {
40 pub version: String,
41 pub revision: String,
42}
43
44#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
45#[non_exhaustive]
46pub struct SoftwareParityReferences {
47 pub reticulum: ReferenceRevision,
48 pub lxmf: ReferenceRevision,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
52#[non_exhaustive]
53pub struct SoftwareParityOrientation {
54 pub advisory: bool,
55 pub references: SoftwareParityReferences,
56 pub overall: ParityCheckpoint,
57 pub reticulum: ParityCheckpoint,
58 pub lxmf: ParityCheckpoint,
59}
60
61impl ParityCheckpoint {
62 fn from_inventory(inventory: ParityInventory) -> Self {
63 let denominator = inventory.complete + inventory.partial;
64 let level = if denominator == 0 {
65 ParityLevel::Unknown
66 } else if inventory.partial == 0 {
67 ParityLevel::Complete
68 } else {
69 ParityLevel::Partial
70 };
71 Self {
72 level,
73 complete_ratio: ParityRatio { numerator: inventory.complete, denominator },
74 inventory,
75 }
76 }
77}
78
79fn inventory(
80 total: usize,
81 complete: usize,
82 partial: usize,
83 not_applicable: usize,
84) -> ParityInventory {
85 ParityInventory {
86 total: u64::try_from(total).expect("parity inventory total fits u64"),
87 complete: u64::try_from(complete).expect("parity complete count fits u64"),
88 partial: u64::try_from(partial).expect("parity partial count fits u64"),
89 not_applicable: u64::try_from(not_applicable)
90 .expect("parity not-applicable count fits u64"),
91 }
92}
93
94pub fn current_software_parity_orientation() -> SoftwareParityOrientation {
95 SoftwareParityOrientation {
96 advisory: true,
97 references: SoftwareParityReferences {
98 reticulum: ReferenceRevision {
99 version: crate::PYTHON_RETICULUM_REFERENCE_VERSION.to_owned(),
100 revision: crate::PYTHON_RETICULUM_REFERENCE_REF.to_owned(),
101 },
102 lxmf: ReferenceRevision {
103 version: crate::PYTHON_LXMF_REFERENCE_VERSION.to_owned(),
104 revision: crate::PYTHON_LXMF_REFERENCE_REF.to_owned(),
105 },
106 },
107 overall: ParityCheckpoint::from_inventory(inventory(
108 crate::PYTHON_SOFTWARE_PARITY_TOTAL,
109 crate::PYTHON_SOFTWARE_PARITY_COMPLETE,
110 crate::PYTHON_SOFTWARE_PARITY_PARTIAL,
111 crate::PYTHON_SOFTWARE_PARITY_NOT_APPLICABLE,
112 )),
113 reticulum: ParityCheckpoint::from_inventory(inventory(
114 crate::PYTHON_RETICULUM_PARITY_TOTAL,
115 crate::PYTHON_RETICULUM_PARITY_COMPLETE,
116 crate::PYTHON_RETICULUM_PARITY_PARTIAL,
117 crate::PYTHON_RETICULUM_PARITY_NOT_APPLICABLE,
118 )),
119 lxmf: ParityCheckpoint::from_inventory(inventory(
120 crate::PYTHON_LXMF_PARITY_TOTAL,
121 crate::PYTHON_LXMF_PARITY_COMPLETE,
122 crate::PYTHON_LXMF_PARITY_PARTIAL,
123 crate::PYTHON_LXMF_PARITY_NOT_APPLICABLE,
124 )),
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn current_orientation_reports_grouped_complete_over_applicable_ratios() {
134 let orientation = current_software_parity_orientation();
135
136 assert!(orientation.advisory);
137 assert_eq!(orientation.overall.level, ParityLevel::Partial);
138 assert_eq!(
139 orientation.overall.complete_ratio,
140 ParityRatio { numerator: 1_695, denominator: 1_810 }
141 );
142 assert_eq!(
143 orientation.overall.inventory,
144 ParityInventory { total: 1_811, complete: 1_695, partial: 115, not_applicable: 1 }
145 );
146 assert_eq!(orientation.reticulum.level, ParityLevel::Partial);
147 assert_eq!(
148 orientation.reticulum.complete_ratio,
149 ParityRatio { numerator: 1_493, denominator: 1_608 }
150 );
151 assert_eq!(
152 orientation.reticulum.inventory,
153 ParityInventory { total: 1_608, complete: 1_493, partial: 115, not_applicable: 0 }
154 );
155 assert_eq!(orientation.lxmf.level, ParityLevel::Complete);
156 assert_eq!(
157 orientation.lxmf.complete_ratio,
158 ParityRatio { numerator: 202, denominator: 202 }
159 );
160 assert_eq!(
161 orientation.lxmf.inventory,
162 ParityInventory { total: 202, complete: 202, partial: 0, not_applicable: 0 }
163 );
164 }
165
166 #[test]
167 fn empty_applicable_inventory_has_unknown_level() {
168 let checkpoint = ParityCheckpoint::from_inventory(ParityInventory {
169 total: 1,
170 complete: 0,
171 partial: 0,
172 not_applicable: 1,
173 });
174
175 assert_eq!(checkpoint.level, ParityLevel::Unknown);
176 assert_eq!(checkpoint.complete_ratio, ParityRatio { numerator: 0, denominator: 0 });
177 }
178}