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