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
101impl fmt::Display for Priority {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 match *self {
104 Self::CRITICAL => {
105 let val = self.0.get();
106 write!(f, "Critical({val})")
107 }
108 Self::HIGH => {
109 let val = self.0.get();
110 write!(f, "High({val})")
111 }
112 Self::MEDIUM => {
113 let val = self.0.get();
114 write!(f, "Medium({val})")
115 }
116 Self::LOW => {
117 let val = self.0.get();
118 write!(f, "Low({val})")
119 }
120 Self::BACKGROUND => {
121 let val = self.0.get();
122 write!(f, "Background({val})")
123 }
124 _ => {
125 let val = self.0.get();
126 write!(f, "Priority({val})")
127 }
128 }
129 }
130}
131
132impl From<Priority> for u8 {
133 fn from(priority: Priority) -> Self {
134 priority.0.get()
135 }
136}
137
138impl TryFrom<u8> for Priority {
139 type Error = DomainError;
140
141 fn try_from(value: u8) -> Result<Self, Self::Error> {
142 Self::new(value)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 trait PriorityRule {
151 fn validate(&self, priority: Priority) -> bool;
152 fn name(&self) -> &'static str;
153 }
154
155 struct MinimumPriority(Priority);
156
157 impl PriorityRule for MinimumPriority {
158 fn validate(&self, priority: Priority) -> bool {
159 priority >= self.0
160 }
161
162 fn name(&self) -> &'static str {
163 "minimum_priority"
164 }
165 }
166
167 struct PriorityRange {
168 min: Priority,
169 max: Priority,
170 }
171
172 impl PriorityRule for PriorityRange {
173 fn validate(&self, priority: Priority) -> bool {
174 priority >= self.min && priority <= self.max
175 }
176
177 fn name(&self) -> &'static str {
178 "priority_range"
179 }
180 }
181
182 struct PriorityRules {
183 rules: Vec<Box<dyn PriorityRule + Send + Sync>>,
184 }
185
186 impl PriorityRules {
187 fn new() -> Self {
188 Self { rules: Vec::new() }
189 }
190
191 fn add_rule(mut self, rule: impl PriorityRule + Send + Sync + 'static) -> Self {
192 self.rules.push(Box::new(rule));
193 self
194 }
195
196 fn validate(&self, priority: Priority) -> DomainResult<()> {
197 for rule in &self.rules {
198 if !rule.validate(priority) {
199 return Err(DomainError::InvalidPriority(format!(
200 "Priority {priority} violates rule: {}",
201 rule.name()
202 )));
203 }
204 }
205 Ok(())
206 }
207 }
208
209 #[test]
210 fn test_priority_constants() {
211 assert_eq!(Priority::CRITICAL.value(), 100);
212 assert_eq!(Priority::HIGH.value(), 80);
213 assert_eq!(Priority::MEDIUM.value(), 50);
214 assert_eq!(Priority::LOW.value(), 25);
215 assert_eq!(Priority::BACKGROUND.value(), 10);
216 }
217
218 #[test]
219 fn test_priority_validation() {
220 assert!(Priority::new(1).is_ok());
221 assert!(Priority::new(255).is_ok());
222 assert!(Priority::new(0).is_err());
223 }
224
225 #[test]
226 fn test_priority_ordering() {
227 assert!(Priority::CRITICAL > Priority::HIGH);
228 assert!(Priority::HIGH > Priority::MEDIUM);
229 assert!(Priority::MEDIUM > Priority::LOW);
230 assert!(Priority::LOW > Priority::BACKGROUND);
231 }
232
233 #[test]
234 fn test_priority_arithmetic() {
235 let p = Priority::MEDIUM;
236 assert_eq!(p.increase_by(10).value(), 60);
237 assert_eq!(p.decrease_by(10).value(), 40);
238
239 let max_p = Priority::new(255).unwrap();
241 assert_eq!(max_p.increase_by(10).value(), 255);
242
243 let min_p = Priority::new(1).unwrap();
244 assert_eq!(min_p.decrease_by(10).value(), 1);
245 }
246
247 #[test]
248 fn test_priority_percentage() {
249 let p = Priority::from_percentage(50.0).unwrap();
250 assert!(p.to_percentage() >= 49.0 && p.to_percentage() <= 51.0);
251
252 assert!(Priority::from_percentage(101.0).is_err());
253 assert!(Priority::from_percentage(-1.0).is_err());
254 }
255
256 #[test]
257 fn test_priority_rules() {
258 let rules = PriorityRules::new()
259 .add_rule(MinimumPriority(Priority::LOW))
260 .add_rule(PriorityRange {
261 min: Priority::LOW,
262 max: Priority::CRITICAL,
263 });
264
265 assert!(rules.validate(Priority::MEDIUM).is_ok());
266 assert!(rules.validate(Priority::new(5).unwrap()).is_err());
267 assert!(rules.validate(Priority::new(200).unwrap()).is_err());
268 }
269
270 #[test]
271 fn test_priority_display() {
272 assert_eq!(Priority::CRITICAL.to_string(), "Critical(100)");
273 assert_eq!(Priority::HIGH.to_string(), "High(80)");
274 assert_eq!(Priority::new(42).unwrap().to_string(), "Priority(42)");
275 }
276}