1use crate::{PrimitiveError, PrimitiveResult};
2use core::fmt;
3
4#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct Percent(u8);
7
8impl Percent {
9 pub const MIN: u8 = 0;
11 pub const MAX: u8 = 100;
13
14 pub const fn new(value: u8) -> PrimitiveResult<Self> {
16 if value > Self::MAX {
17 return Err(PrimitiveError::OutOfRange {
18 min: Self::MIN as u128,
19 max: Self::MAX as u128,
20 actual: value as u128,
21 });
22 }
23 Ok(Self(value))
24 }
25
26 pub const fn get(self) -> u8 {
28 self.0
29 }
30
31 pub fn as_fraction(self) -> f64 {
33 f64::from(self.0) / 100.0
34 }
35}
36
37impl fmt::Display for Percent {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "{}%", self.0)
40 }
41}
42
43impl TryFrom<u8> for Percent {
44 type Error = PrimitiveError;
45
46 fn try_from(value: u8) -> Result<Self, Self::Error> {
47 Self::new(value)
48 }
49}
50
51impl From<Percent> for u8 {
52 fn from(value: Percent) -> Self {
53 value.get()
54 }
55}
56
57#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct Port(u16);
60
61impl Port {
62 pub const MIN: u16 = 1;
64 pub const MAX: u16 = 65535;
66
67 pub const fn new(value: u16) -> PrimitiveResult<Self> {
69 if value < Self::MIN {
70 return Err(PrimitiveError::OutOfRange {
71 min: Self::MIN as u128,
72 max: Self::MAX as u128,
73 actual: value as u128,
74 });
75 }
76 Ok(Self(value))
77 }
78
79 pub const fn get(self) -> u16 {
81 self.0
82 }
83}
84
85impl fmt::Display for Port {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 write!(f, "{}", self.0)
88 }
89}
90
91impl TryFrom<u16> for Port {
92 type Error = PrimitiveError;
93
94 fn try_from(value: u16) -> Result<Self, Self::Error> {
95 Self::new(value)
96 }
97}
98
99impl From<Port> for u16 {
100 fn from(value: Port) -> Self {
101 value.get()
102 }
103}
104
105#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
107pub struct ByteSize(u64);
108
109impl ByteSize {
110 pub const fn from_bytes(bytes: u64) -> Self {
112 Self(bytes)
113 }
114
115 pub const fn from_kb(kb: u64) -> Self {
119 Self(kb.saturating_mul(1024))
120 }
121
122 pub const fn from_mb(mb: u64) -> Self {
126 Self(mb.saturating_mul(1024 * 1024))
127 }
128
129 pub const fn from_gb(gb: u64) -> Self {
133 Self(gb.saturating_mul(1024 * 1024 * 1024))
134 }
135
136 pub const fn as_bytes(self) -> u64 {
138 self.0
139 }
140}
141
142impl fmt::Display for ByteSize {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 const KB: u64 = 1024;
145 const MB: u64 = KB * 1024;
146 const GB: u64 = MB * 1024;
147
148 let bytes = self.0;
149 if bytes < KB {
150 write!(f, "{bytes} B")
151 } else if bytes < MB {
152 write!(f, "{:.2} KB", bytes as f64 / KB as f64)
153 } else if bytes < GB {
154 write!(f, "{:.2} MB", bytes as f64 / MB as f64)
155 } else {
156 write!(f, "{:.2} GB", bytes as f64 / GB as f64)
157 }
158 }
159}
160
161impl From<u64> for ByteSize {
162 fn from(value: u64) -> Self {
163 Self::from_bytes(value)
164 }
165}
166
167impl From<ByteSize> for u64 {
168 fn from(value: ByteSize) -> Self {
169 value.as_bytes()
170 }
171}
172
173#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
177pub struct PositiveInt(u64);
178
179impl PositiveInt {
180 pub const fn new(value: u64) -> PrimitiveResult<Self> {
182 if value == 0 {
183 return Err(PrimitiveError::OutOfRange {
184 min: 1,
185 max: u64::MAX as u128,
186 actual: 0,
187 });
188 }
189 Ok(Self(value))
190 }
191
192 pub const fn get(self) -> u64 {
194 self.0
195 }
196}
197
198impl fmt::Display for PositiveInt {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 write!(f, "{}", self.0)
201 }
202}
203
204impl TryFrom<u64> for PositiveInt {
205 type Error = PrimitiveError;
206
207 fn try_from(value: u64) -> Result<Self, Self::Error> {
208 Self::new(value)
209 }
210}
211
212impl From<PositiveInt> for u64 {
213 fn from(value: PositiveInt) -> Self {
214 value.get()
215 }
216}
217
218#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
222pub struct PositiveFloat(f64);
223
224impl PositiveFloat {
225 pub fn new(value: f64) -> PrimitiveResult<Self> {
228 if !value.is_finite() || value <= 0.0 {
229 return Err(PrimitiveError::Invalid {
230 message: "value must be a finite positive number greater than zero",
231 });
232 }
233 Ok(Self(value))
234 }
235
236 pub fn get(self) -> f64 {
238 self.0
239 }
240}
241
242impl fmt::Display for PositiveFloat {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 write!(f, "{}", self.0)
245 }
246}
247
248impl TryFrom<f64> for PositiveFloat {
249 type Error = PrimitiveError;
250
251 fn try_from(value: f64) -> Result<Self, Self::Error> {
252 Self::new(value)
253 }
254}
255
256#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
263pub struct PercentFloat(f64);
264
265impl PercentFloat {
266 pub const MIN: f64 = 0.0;
268 pub const MAX: f64 = 100.0;
270
271 pub fn new(value: f64) -> PrimitiveResult<Self> {
274 if !value.is_finite() || !(Self::MIN..=Self::MAX).contains(&value) {
275 return Err(PrimitiveError::Invalid {
276 message: "percentage must be a finite number between 0.0 and 100.0 inclusive",
277 });
278 }
279 Ok(Self(value))
280 }
281
282 pub fn get(self) -> f64 {
284 self.0
285 }
286
287 pub fn as_fraction(self) -> f64 {
289 self.0 / 100.0
290 }
291}
292
293impl fmt::Display for PercentFloat {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 write!(f, "{}%", self.0)
296 }
297}
298
299impl TryFrom<f64> for PercentFloat {
300 type Error = PrimitiveError;
301
302 fn try_from(value: f64) -> Result<Self, Self::Error> {
303 Self::new(value)
304 }
305}
306
307#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
314pub struct Probability(f64);
315
316impl Probability {
317 pub const MIN: f64 = 0.0;
319 pub const MAX: f64 = 1.0;
321
322 pub fn new(value: f64) -> PrimitiveResult<Self> {
325 if !value.is_finite() || !(Self::MIN..=Self::MAX).contains(&value) {
326 return Err(PrimitiveError::Invalid {
327 message: "probability must be a finite number between 0.0 and 1.0 inclusive",
328 });
329 }
330 Ok(Self(value))
331 }
332
333 pub fn get(self) -> f64 {
335 self.0
336 }
337
338 pub fn complement(self) -> Self {
341 Self(Self::MAX - self.0)
342 }
343}
344
345impl fmt::Display for Probability {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 write!(f, "{}", self.0)
348 }
349}
350
351impl TryFrom<f64> for Probability {
352 type Error = PrimitiveError;
353
354 fn try_from(value: f64) -> Result<Self, Self::Error> {
355 Self::new(value)
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::{ByteSize, Percent, PercentFloat, Port, PositiveFloat, PositiveInt, Probability};
362 use crate::PrimitiveError;
363 use alloc::string::ToString;
364
365 #[test]
366 fn percent_accepts_boundary_values() {
367 assert_eq!(Percent::new(0).unwrap().get(), 0);
368 assert_eq!(Percent::new(50).unwrap().get(), 50);
369 assert_eq!(Percent::new(100).unwrap().get(), 100);
370 }
371
372 #[test]
373 fn percent_rejects_out_of_range() {
374 assert_eq!(
375 Percent::new(101).unwrap_err(),
376 PrimitiveError::OutOfRange {
377 min: 0,
378 max: 100,
379 actual: 101
380 }
381 );
382 }
383
384 #[test]
385 fn percent_display_prints_percent_sign() {
386 assert_eq!(Percent::new(42).unwrap().to_string(), "42%");
387 }
388
389 #[test]
390 fn percent_fraction() {
391 assert_eq!(Percent::new(25).unwrap().as_fraction(), 0.25);
392 }
393
394 #[test]
395 fn port_accepts_boundaries() {
396 assert_eq!(Port::new(1).unwrap().get(), 1);
397 assert_eq!(Port::new(65535).unwrap().get(), 65535);
398 }
399
400 #[test]
401 fn port_rejects_zero() {
402 assert_eq!(
403 Port::new(0).unwrap_err(),
404 PrimitiveError::OutOfRange {
405 min: 1,
406 max: 65535,
407 actual: 0
408 }
409 );
410 }
411
412 #[test]
413 fn byte_size_constructors_work() {
414 assert_eq!(ByteSize::from_bytes(512).as_bytes(), 512);
415 assert_eq!(ByteSize::from_kb(1).as_bytes(), 1024);
416 assert_eq!(ByteSize::from_mb(1).as_bytes(), 1024 * 1024);
417 assert_eq!(ByteSize::from_gb(1).as_bytes(), 1024 * 1024 * 1024);
418 }
419
420 #[test]
421 fn byte_size_display_works() {
422 assert_eq!(ByteSize::from_bytes(512).to_string(), "512 B");
423 assert_eq!(ByteSize::from_kb(1).to_string(), "1.00 KB");
424 assert_eq!(ByteSize::from_kb(1536 / 1024).to_string(), "1.00 KB");
425 assert_eq!(ByteSize::from_bytes(1536).to_string(), "1.50 KB");
426 assert_eq!(
427 ByteSize::from_bytes(1024 * 1024 + 512 * 1024).to_string(),
428 "1.50 MB"
429 );
430 assert_eq!(
431 ByteSize::from_bytes(1024 * 1024 * 1024 + 512 * 1024 * 1024).to_string(),
432 "1.50 GB"
433 );
434 }
435
436 #[test]
437 fn percent_try_from_u8() {
438 assert_eq!(Percent::try_from(50u8).unwrap().get(), 50);
439 assert!(Percent::try_from(101u8).is_err());
440 }
441
442 #[test]
443 fn percent_from_into_u8() {
444 let p = Percent::new(75).unwrap();
445 let v: u8 = p.into();
446 assert_eq!(v, 75);
447 }
448
449 #[test]
450 fn port_try_from_u16() {
451 assert_eq!(Port::try_from(8080u16).unwrap().get(), 8080);
452 assert!(Port::try_from(0u16).is_err());
453 }
454
455 #[test]
456 fn port_from_into_u16() {
457 let p = Port::new(443).unwrap();
458 let v: u16 = p.into();
459 assert_eq!(v, 443);
460 }
461
462 #[test]
463 fn port_display() {
464 assert_eq!(Port::new(8080).unwrap().to_string(), "8080");
465 }
466
467 #[test]
468 fn byte_size_from_u64() {
469 let s = ByteSize::from(2048u64);
470 assert_eq!(s.as_bytes(), 2048);
471 }
472
473 #[test]
474 fn byte_size_into_u64() {
475 let s = ByteSize::from_bytes(4096);
476 let v: u64 = s.into();
477 assert_eq!(v, 4096);
478 }
479
480 #[test]
481 fn positive_int_accepts_nonzero() {
482 assert_eq!(PositiveInt::new(1).unwrap().get(), 1);
483 assert_eq!(PositiveInt::new(u64::MAX).unwrap().get(), u64::MAX);
484 }
485
486 #[test]
487 fn positive_int_rejects_zero() {
488 assert!(PositiveInt::new(0).is_err());
489 }
490
491 #[test]
492 fn positive_int_display() {
493 assert_eq!(PositiveInt::new(42).unwrap().to_string(), "42");
494 }
495
496 #[test]
497 fn positive_int_try_from_and_into() {
498 let p = PositiveInt::try_from(10u64).unwrap();
499 let v: u64 = p.into();
500 assert_eq!(v, 10);
501 }
502
503 #[test]
504 fn positive_float_accepts_positive() {
505 assert_eq!(PositiveFloat::new(0.001).unwrap().get(), 0.001);
506 assert_eq!(PositiveFloat::new(f64::MAX).unwrap().get(), f64::MAX);
507 }
508
509 #[test]
510 fn positive_float_rejects_zero() {
511 assert!(PositiveFloat::new(0.0).is_err());
512 }
513
514 #[test]
515 fn positive_float_rejects_negative() {
516 assert!(PositiveFloat::new(-1.0).is_err());
517 }
518
519 #[test]
520 fn positive_float_rejects_nan() {
521 assert!(PositiveFloat::new(f64::NAN).is_err());
522 }
523
524 #[test]
525 fn positive_float_rejects_infinity() {
526 assert!(PositiveFloat::new(f64::INFINITY).is_err());
527 }
528
529 #[test]
530 fn positive_float_try_from() {
531 assert!(PositiveFloat::try_from(1.5f64).is_ok());
532 assert!(PositiveFloat::try_from(0.0f64).is_err());
533 }
534
535 #[test]
536 fn percentage_f64_accepts_boundaries() {
537 assert_eq!(PercentFloat::new(0.0).unwrap().get(), 0.0);
538 assert_eq!(PercentFloat::new(50.5).unwrap().get(), 50.5);
539 assert_eq!(PercentFloat::new(100.0).unwrap().get(), 100.0);
540 }
541
542 #[test]
543 fn percentage_f64_rejects_out_of_range() {
544 assert!(PercentFloat::new(-0.1).is_err());
545 assert!(PercentFloat::new(100.1).is_err());
546 }
547
548 #[test]
549 fn percentage_f64_rejects_nan() {
550 assert!(PercentFloat::new(f64::NAN).is_err());
551 }
552
553 #[test]
554 fn percentage_f64_as_fraction() {
555 assert_eq!(PercentFloat::new(25.0).unwrap().as_fraction(), 0.25);
556 }
557
558 #[test]
559 fn percentage_f64_display() {
560 assert_eq!(PercentFloat::new(42.5).unwrap().to_string(), "42.5%");
561 }
562
563 #[test]
564 fn percentage_f64_try_from() {
565 assert!(PercentFloat::try_from(50.0f64).is_ok());
566 assert!(PercentFloat::try_from(101.0f64).is_err());
567 }
568
569 #[test]
570 fn probability_accepts_boundaries() {
571 assert_eq!(Probability::new(0.0).unwrap().get(), 0.0);
572 assert_eq!(Probability::new(0.5).unwrap().get(), 0.5);
573 assert_eq!(Probability::new(1.0).unwrap().get(), 1.0);
574 }
575
576 #[test]
577 fn probability_rejects_out_of_range() {
578 assert!(Probability::new(-0.000001).is_err());
579 assert!(Probability::new(1.000001).is_err());
580 }
581
582 #[test]
583 fn probability_rejects_nan_and_infinity() {
584 assert!(Probability::new(f64::NAN).is_err());
585 assert!(Probability::new(f64::INFINITY).is_err());
586 assert!(Probability::new(f64::NEG_INFINITY).is_err());
587 }
588
589 #[test]
590 fn probability_complement() {
591 assert_eq!(Probability::new(0.25).unwrap().complement().get(), 0.75);
592 assert_eq!(Probability::new(0.0).unwrap().complement().get(), 1.0);
593 assert_eq!(Probability::new(1.0).unwrap().complement().get(), 0.0);
594 }
595
596 #[test]
597 fn probability_display() {
598 assert_eq!(Probability::new(0.5).unwrap().to_string(), "0.5");
599 }
600
601 #[test]
602 fn probability_try_from() {
603 assert!(Probability::try_from(0.5f64).is_ok());
604 assert!(Probability::try_from(2.0f64).is_err());
605 }
606}