pjson_rs_domain/value_objects/
priority.rs1use crate::{DomainError, DomainResult};
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::num::NonZeroU8;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
21pub struct Priority(NonZeroU8);
22
23impl Priority {
24 pub const CRITICAL: Self = Self::new_unchecked(100);
26
27 pub const HIGH: Self = Self::new_unchecked(80);
29
30 pub const MEDIUM: Self = Self::new_unchecked(50);
32
33 pub const LOW: Self = Self::new_unchecked(25);
35
36 pub const BACKGROUND: Self = Self::new_unchecked(10);
38
39 pub fn new(value: u8) -> DomainResult<Self> {
41 NonZeroU8::new(value)
42 .map(Self)
43 .ok_or_else(|| DomainError::InvalidPriority("Priority cannot be zero".to_string()))
44 }
45
46 const fn new_unchecked(value: u8) -> Self {
48 unsafe { Self(NonZeroU8::new_unchecked(value)) }
50 }
51
52 pub fn value(self) -> u8 {
54 self.0.get()
55 }
56
57 pub fn increase_by(self, delta: u8) -> Self {
59 let new_value = self.0.get().saturating_add(delta);
60 Self(NonZeroU8::new(new_value).unwrap_or(NonZeroU8::MAX))
61 }
62
63 pub fn decrease_by(self, delta: u8) -> Self {
65 let new_value = self.0.get().saturating_sub(delta);
66 Self(NonZeroU8::new(new_value).unwrap_or(NonZeroU8::MIN))
67 }
68
69 pub fn is_critical(self) -> bool {
71 self.0.get() >= Self::CRITICAL.0.get()
72 }
73
74 pub fn is_high_or_above(self) -> bool {
76 self.0.get() >= Self::HIGH.0.get()
77 }
78
79 pub fn from_percentage(percent: f32) -> DomainResult<Self> {
81 if !(0.0..=100.0).contains(&percent) {
82 return Err(DomainError::InvalidPriority(format!(
83 "Percentage must be 0-100, got {percent}"
84 )));
85 }
86
87 let value = (percent * 2.55).round() as u8;
88 Self::new(value.max(1)) }
90
91 pub fn to_percentage(self) -> f32 {
93 (self.0.get() as f32 / 255.0) * 100.0
94 }
95
96 pub fn unwrap_or(self, _default: u8) -> u8 {
98 self.0.get()
99 }
100}
101
102impl fmt::Display for Priority {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 match *self {
105 Self::CRITICAL => {
106 let val = self.0.get();
107 write!(f, "Critical({val})")
108 }
109 Self::HIGH => {
110 let val = self.0.get();
111 write!(f, "High({val})")
112 }
113 Self::MEDIUM => {
114 let val = self.0.get();
115 write!(f, "Medium({val})")
116 }
117 Self::LOW => {
118 let val = self.0.get();
119 write!(f, "Low({val})")
120 }
121 Self::BACKGROUND => {
122 let val = self.0.get();
123 write!(f, "Background({val})")
124 }
125 _ => {
126 let val = self.0.get();
127 write!(f, "Priority({val})")
128 }
129 }
130 }
131}
132
133impl From<Priority> for u8 {
134 fn from(priority: Priority) -> Self {
135 priority.0.get()
136 }
137}
138
139impl TryFrom<u8> for Priority {
140 type Error = DomainError;
141
142 fn try_from(value: u8) -> Result<Self, Self::Error> {
143 Self::new(value)
144 }
145}
146
147#[allow(dead_code)]
149pub trait PriorityRule {
150 fn validate(&self, priority: Priority) -> bool;
151 fn name(&self) -> &'static str;
152}
153
154#[allow(dead_code)]
156#[derive(Debug, Clone)]
157pub struct MinimumPriority(pub Priority);
158
159#[allow(dead_code)]
160impl PriorityRule for MinimumPriority {
161 fn validate(&self, priority: Priority) -> bool {
162 priority >= self.0
163 }
164
165 fn name(&self) -> &'static str {
166 "minimum_priority"
167 }
168}
169
170#[allow(dead_code)]
172#[derive(Debug, Clone)]
173pub struct PriorityRange {
174 pub min: Priority,
175 pub max: Priority,
176}
177
178#[allow(dead_code)]
179impl PriorityRule for PriorityRange {
180 fn validate(&self, priority: Priority) -> bool {
181 priority >= self.min && priority <= self.max
182 }
183
184 fn name(&self) -> &'static str {
185 "priority_range"
186 }
187}
188
189#[allow(dead_code)]
191pub struct PriorityRules {
192 rules: Vec<Box<dyn PriorityRule + Send + Sync>>,
193}
194
195#[allow(dead_code)]
196impl PriorityRules {
197 pub fn new() -> Self {
198 Self { rules: Vec::new() }
199 }
200
201 pub fn add_rule(mut self, rule: impl PriorityRule + Send + Sync + 'static) -> Self {
202 self.rules.push(Box::new(rule));
203 self
204 }
205
206 pub fn validate(&self, priority: Priority) -> DomainResult<()> {
207 for rule in &self.rules {
208 if !rule.validate(priority) {
209 return Err(DomainError::InvalidPriority(format!(
210 "Priority {priority} violates rule: {}",
211 rule.name()
212 )));
213 }
214 }
215 Ok(())
216 }
217}
218
219impl Default for PriorityRules {
220 fn default() -> Self {
221 Self::new()
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_priority_constants() {
231 assert_eq!(Priority::CRITICAL.value(), 100);
232 assert_eq!(Priority::HIGH.value(), 80);
233 assert_eq!(Priority::MEDIUM.value(), 50);
234 assert_eq!(Priority::LOW.value(), 25);
235 assert_eq!(Priority::BACKGROUND.value(), 10);
236 }
237
238 #[test]
239 fn test_priority_validation() {
240 assert!(Priority::new(1).is_ok());
241 assert!(Priority::new(255).is_ok());
242 assert!(Priority::new(0).is_err());
243 }
244
245 #[test]
246 fn test_priority_ordering() {
247 assert!(Priority::CRITICAL > Priority::HIGH);
248 assert!(Priority::HIGH > Priority::MEDIUM);
249 assert!(Priority::MEDIUM > Priority::LOW);
250 assert!(Priority::LOW > Priority::BACKGROUND);
251 }
252
253 #[test]
254 fn test_priority_arithmetic() {
255 let p = Priority::MEDIUM;
256 assert_eq!(p.increase_by(10).value(), 60);
257 assert_eq!(p.decrease_by(10).value(), 40);
258
259 let max_p = Priority::new(255).unwrap();
261 assert_eq!(max_p.increase_by(10).value(), 255);
262
263 let min_p = Priority::new(1).unwrap();
264 assert_eq!(min_p.decrease_by(10).value(), 1);
265 }
266
267 #[test]
268 fn test_priority_percentage() {
269 let p = Priority::from_percentage(50.0).unwrap();
270 assert!(p.to_percentage() >= 49.0 && p.to_percentage() <= 51.0);
271
272 assert!(Priority::from_percentage(101.0).is_err());
273 assert!(Priority::from_percentage(-1.0).is_err());
274 }
275
276 #[test]
277 fn test_priority_rules() {
278 let rules = PriorityRules::new()
279 .add_rule(MinimumPriority(Priority::LOW))
280 .add_rule(PriorityRange {
281 min: Priority::LOW,
282 max: Priority::CRITICAL,
283 });
284
285 assert!(rules.validate(Priority::MEDIUM).is_ok());
286 assert!(rules.validate(Priority::new(5).unwrap()).is_err());
287 assert!(rules.validate(Priority::new(200).unwrap()).is_err());
288 }
289
290 #[test]
291 fn test_priority_display() {
292 assert_eq!(Priority::CRITICAL.to_string(), "Critical(100)");
293 assert_eq!(Priority::HIGH.to_string(), "High(80)");
294 assert_eq!(Priority::new(42).unwrap().to_string(), "Priority(42)");
295 }
296}