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 PercentageF64(f64);
264
265impl PercentageF64 {
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 PercentageF64 {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 write!(f, "{}%", self.0)
296 }
297}
298
299impl TryFrom<f64> for PercentageF64 {
300 type Error = PrimitiveError;
301
302 fn try_from(value: f64) -> Result<Self, Self::Error> {
303 Self::new(value)
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::{ByteSize, Percent, PercentageF64, Port, PositiveFloat, PositiveInt};
310 use crate::PrimitiveError;
311 use alloc::string::ToString;
312
313 #[test]
314 fn percent_accepts_boundary_values() {
315 assert_eq!(Percent::new(0).unwrap().get(), 0);
316 assert_eq!(Percent::new(50).unwrap().get(), 50);
317 assert_eq!(Percent::new(100).unwrap().get(), 100);
318 }
319
320 #[test]
321 fn percent_rejects_out_of_range() {
322 assert_eq!(
323 Percent::new(101).unwrap_err(),
324 PrimitiveError::OutOfRange {
325 min: 0,
326 max: 100,
327 actual: 101
328 }
329 );
330 }
331
332 #[test]
333 fn percent_display_prints_percent_sign() {
334 assert_eq!(Percent::new(42).unwrap().to_string(), "42%");
335 }
336
337 #[test]
338 fn percent_fraction() {
339 assert_eq!(Percent::new(25).unwrap().as_fraction(), 0.25);
340 }
341
342 #[test]
343 fn port_accepts_boundaries() {
344 assert_eq!(Port::new(1).unwrap().get(), 1);
345 assert_eq!(Port::new(65535).unwrap().get(), 65535);
346 }
347
348 #[test]
349 fn port_rejects_zero() {
350 assert_eq!(
351 Port::new(0).unwrap_err(),
352 PrimitiveError::OutOfRange {
353 min: 1,
354 max: 65535,
355 actual: 0
356 }
357 );
358 }
359
360 #[test]
361 fn byte_size_constructors_work() {
362 assert_eq!(ByteSize::from_bytes(512).as_bytes(), 512);
363 assert_eq!(ByteSize::from_kb(1).as_bytes(), 1024);
364 assert_eq!(ByteSize::from_mb(1).as_bytes(), 1024 * 1024);
365 assert_eq!(ByteSize::from_gb(1).as_bytes(), 1024 * 1024 * 1024);
366 }
367
368 #[test]
369 fn byte_size_display_works() {
370 assert_eq!(ByteSize::from_bytes(512).to_string(), "512 B");
371 assert_eq!(ByteSize::from_kb(1).to_string(), "1.00 KB");
372 assert_eq!(ByteSize::from_kb(1536 / 1024).to_string(), "1.00 KB");
373 assert_eq!(ByteSize::from_bytes(1536).to_string(), "1.50 KB");
374 assert_eq!(
375 ByteSize::from_bytes(1024 * 1024 + 512 * 1024).to_string(),
376 "1.50 MB"
377 );
378 assert_eq!(
379 ByteSize::from_bytes(1024 * 1024 * 1024 + 512 * 1024 * 1024).to_string(),
380 "1.50 GB"
381 );
382 }
383
384 #[test]
385 fn percent_try_from_u8() {
386 assert_eq!(Percent::try_from(50u8).unwrap().get(), 50);
387 assert!(Percent::try_from(101u8).is_err());
388 }
389
390 #[test]
391 fn percent_from_into_u8() {
392 let p = Percent::new(75).unwrap();
393 let v: u8 = p.into();
394 assert_eq!(v, 75);
395 }
396
397 #[test]
398 fn port_try_from_u16() {
399 assert_eq!(Port::try_from(8080u16).unwrap().get(), 8080);
400 assert!(Port::try_from(0u16).is_err());
401 }
402
403 #[test]
404 fn port_from_into_u16() {
405 let p = Port::new(443).unwrap();
406 let v: u16 = p.into();
407 assert_eq!(v, 443);
408 }
409
410 #[test]
411 fn port_display() {
412 assert_eq!(Port::new(8080).unwrap().to_string(), "8080");
413 }
414
415 #[test]
416 fn byte_size_from_u64() {
417 let s = ByteSize::from(2048u64);
418 assert_eq!(s.as_bytes(), 2048);
419 }
420
421 #[test]
422 fn byte_size_into_u64() {
423 let s = ByteSize::from_bytes(4096);
424 let v: u64 = s.into();
425 assert_eq!(v, 4096);
426 }
427
428 #[test]
429 fn positive_int_accepts_nonzero() {
430 assert_eq!(PositiveInt::new(1).unwrap().get(), 1);
431 assert_eq!(PositiveInt::new(u64::MAX).unwrap().get(), u64::MAX);
432 }
433
434 #[test]
435 fn positive_int_rejects_zero() {
436 assert!(PositiveInt::new(0).is_err());
437 }
438
439 #[test]
440 fn positive_int_display() {
441 assert_eq!(PositiveInt::new(42).unwrap().to_string(), "42");
442 }
443
444 #[test]
445 fn positive_int_try_from_and_into() {
446 let p = PositiveInt::try_from(10u64).unwrap();
447 let v: u64 = p.into();
448 assert_eq!(v, 10);
449 }
450
451 #[test]
452 fn positive_float_accepts_positive() {
453 assert_eq!(PositiveFloat::new(0.001).unwrap().get(), 0.001);
454 assert_eq!(PositiveFloat::new(f64::MAX).unwrap().get(), f64::MAX);
455 }
456
457 #[test]
458 fn positive_float_rejects_zero() {
459 assert!(PositiveFloat::new(0.0).is_err());
460 }
461
462 #[test]
463 fn positive_float_rejects_negative() {
464 assert!(PositiveFloat::new(-1.0).is_err());
465 }
466
467 #[test]
468 fn positive_float_rejects_nan() {
469 assert!(PositiveFloat::new(f64::NAN).is_err());
470 }
471
472 #[test]
473 fn positive_float_rejects_infinity() {
474 assert!(PositiveFloat::new(f64::INFINITY).is_err());
475 }
476
477 #[test]
478 fn positive_float_try_from() {
479 assert!(PositiveFloat::try_from(1.5f64).is_ok());
480 assert!(PositiveFloat::try_from(0.0f64).is_err());
481 }
482
483 #[test]
484 fn percentage_f64_accepts_boundaries() {
485 assert_eq!(PercentageF64::new(0.0).unwrap().get(), 0.0);
486 assert_eq!(PercentageF64::new(50.5).unwrap().get(), 50.5);
487 assert_eq!(PercentageF64::new(100.0).unwrap().get(), 100.0);
488 }
489
490 #[test]
491 fn percentage_f64_rejects_out_of_range() {
492 assert!(PercentageF64::new(-0.1).is_err());
493 assert!(PercentageF64::new(100.1).is_err());
494 }
495
496 #[test]
497 fn percentage_f64_rejects_nan() {
498 assert!(PercentageF64::new(f64::NAN).is_err());
499 }
500
501 #[test]
502 fn percentage_f64_as_fraction() {
503 assert_eq!(PercentageF64::new(25.0).unwrap().as_fraction(), 0.25);
504 }
505
506 #[test]
507 fn percentage_f64_display() {
508 assert_eq!(PercentageF64::new(42.5).unwrap().to_string(), "42.5%");
509 }
510
511 #[test]
512 fn percentage_f64_try_from() {
513 assert!(PercentageF64::try_from(50.0f64).is_ok());
514 assert!(PercentageF64::try_from(101.0f64).is_err());
515 }
516}