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;
10 pub const MAX: u8 = 100;
11
12 pub fn new(value: u8) -> PrimitiveResult<Self> {
14 if value > Self::MAX {
15 return Err(PrimitiveError::OutOfRange {
16 min: Self::MIN as u128,
17 max: Self::MAX as u128,
18 actual: value as u128,
19 });
20 }
21 Ok(Self(value))
22 }
23
24 pub const fn get(self) -> u8 {
26 self.0
27 }
28
29 pub fn as_fraction(self) -> f64 {
31 f64::from(self.0) / 100.0
32 }
33}
34
35impl fmt::Display for Percent {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 write!(f, "{}%", self.0)
38 }
39}
40
41impl TryFrom<u8> for Percent {
42 type Error = PrimitiveError;
43
44 fn try_from(value: u8) -> Result<Self, Self::Error> {
45 Self::new(value)
46 }
47}
48
49impl From<Percent> for u8 {
50 fn from(value: Percent) -> Self {
51 value.get()
52 }
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct Port(u16);
58
59impl Port {
60 pub const MIN: u16 = 1;
61 pub const MAX: u16 = 65535;
62
63 pub fn new(value: u16) -> PrimitiveResult<Self> {
65 if !(Self::MIN..=Self::MAX).contains(&value) {
66 return Err(PrimitiveError::OutOfRange {
67 min: Self::MIN as u128,
68 max: Self::MAX as u128,
69 actual: value as u128,
70 });
71 }
72 Ok(Self(value))
73 }
74
75 pub const fn get(self) -> u16 {
77 self.0
78 }
79}
80
81impl fmt::Display for Port {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(f, "{}", self.0)
84 }
85}
86
87impl TryFrom<u16> for Port {
88 type Error = PrimitiveError;
89
90 fn try_from(value: u16) -> Result<Self, Self::Error> {
91 Self::new(value)
92 }
93}
94
95impl From<Port> for u16 {
96 fn from(value: Port) -> Self {
97 value.get()
98 }
99}
100
101#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub struct ByteSize(u64);
104
105impl ByteSize {
106 pub const fn from_bytes(bytes: u64) -> Self {
108 Self(bytes)
109 }
110
111 pub const fn from_kb(kb: u64) -> Self {
115 Self(kb.saturating_mul(1024))
116 }
117
118 pub const fn from_mb(mb: u64) -> Self {
122 Self(mb.saturating_mul(1024 * 1024))
123 }
124
125 pub const fn from_gb(gb: u64) -> Self {
129 Self(gb.saturating_mul(1024 * 1024 * 1024))
130 }
131
132 pub const fn as_bytes(self) -> u64 {
134 self.0
135 }
136}
137
138impl fmt::Display for ByteSize {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 const KB: u64 = 1024;
141 const MB: u64 = KB * 1024;
142 const GB: u64 = MB * 1024;
143
144 let bytes = self.0;
145 if bytes < KB {
146 write!(f, "{bytes} B")
147 } else if bytes < MB {
148 write!(f, "{:.2} KB", bytes as f64 / KB as f64)
149 } else if bytes < GB {
150 write!(f, "{:.2} MB", bytes as f64 / MB as f64)
151 } else {
152 write!(f, "{:.2} GB", bytes as f64 / GB as f64)
153 }
154 }
155}
156
157impl From<u64> for ByteSize {
158 fn from(value: u64) -> Self {
159 Self::from_bytes(value)
160 }
161}
162
163impl From<ByteSize> for u64 {
164 fn from(value: ByteSize) -> Self {
165 value.as_bytes()
166 }
167}
168
169#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
173pub struct PositiveInt(u64);
174
175impl PositiveInt {
176 pub fn new(value: u64) -> PrimitiveResult<Self> {
178 if value == 0 {
179 return Err(PrimitiveError::OutOfRange {
180 min: 1,
181 max: u64::MAX as u128,
182 actual: 0,
183 });
184 }
185 Ok(Self(value))
186 }
187
188 pub const fn get(self) -> u64 {
189 self.0
190 }
191}
192
193impl fmt::Display for PositiveInt {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "{}", self.0)
196 }
197}
198
199impl TryFrom<u64> for PositiveInt {
200 type Error = PrimitiveError;
201
202 fn try_from(value: u64) -> Result<Self, Self::Error> {
203 Self::new(value)
204 }
205}
206
207impl From<PositiveInt> for u64 {
208 fn from(value: PositiveInt) -> Self {
209 value.get()
210 }
211}
212
213#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
217pub struct PositiveFloat(f64);
218
219impl PositiveFloat {
220 pub fn new(value: f64) -> PrimitiveResult<Self> {
223 if !value.is_finite() || value <= 0.0 {
224 return Err(PrimitiveError::Invalid {
225 message: "value must be a finite positive number greater than zero",
226 });
227 }
228 Ok(Self(value))
229 }
230
231 pub fn get(self) -> f64 {
232 self.0
233 }
234}
235
236impl fmt::Display for PositiveFloat {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 write!(f, "{}", self.0)
239 }
240}
241
242impl TryFrom<f64> for PositiveFloat {
243 type Error = PrimitiveError;
244
245 fn try_from(value: f64) -> Result<Self, Self::Error> {
246 Self::new(value)
247 }
248}
249
250#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
257pub struct PercentageF64(f64);
258
259impl PercentageF64 {
260 pub const MIN: f64 = 0.0;
261 pub const MAX: f64 = 100.0;
262
263 pub fn new(value: f64) -> PrimitiveResult<Self> {
266 if !value.is_finite() || !(Self::MIN..=Self::MAX).contains(&value) {
267 return Err(PrimitiveError::Invalid {
268 message: "percentage must be a finite number between 0.0 and 100.0 inclusive",
269 });
270 }
271 Ok(Self(value))
272 }
273
274 pub fn get(self) -> f64 {
275 self.0
276 }
277
278 pub fn as_fraction(self) -> f64 {
280 self.0 / 100.0
281 }
282}
283
284impl fmt::Display for PercentageF64 {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 write!(f, "{}%", self.0)
287 }
288}
289
290impl TryFrom<f64> for PercentageF64 {
291 type Error = PrimitiveError;
292
293 fn try_from(value: f64) -> Result<Self, Self::Error> {
294 Self::new(value)
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::{ByteSize, Percent, PercentageF64, Port, PositiveFloat, PositiveInt};
301 use crate::PrimitiveError;
302 use alloc::string::ToString;
303
304 #[test]
305 fn percent_accepts_boundary_values() {
306 assert_eq!(Percent::new(0).unwrap().get(), 0);
307 assert_eq!(Percent::new(50).unwrap().get(), 50);
308 assert_eq!(Percent::new(100).unwrap().get(), 100);
309 }
310
311 #[test]
312 fn percent_rejects_out_of_range() {
313 assert_eq!(
314 Percent::new(101).unwrap_err(),
315 PrimitiveError::OutOfRange {
316 min: 0,
317 max: 100,
318 actual: 101
319 }
320 );
321 }
322
323 #[test]
324 fn percent_display_prints_percent_sign() {
325 assert_eq!(Percent::new(42).unwrap().to_string(), "42%");
326 }
327
328 #[test]
329 fn percent_fraction() {
330 assert_eq!(Percent::new(25).unwrap().as_fraction(), 0.25);
331 }
332
333 #[test]
334 fn port_accepts_boundaries() {
335 assert_eq!(Port::new(1).unwrap().get(), 1);
336 assert_eq!(Port::new(65535).unwrap().get(), 65535);
337 }
338
339 #[test]
340 fn port_rejects_zero() {
341 assert_eq!(
342 Port::new(0).unwrap_err(),
343 PrimitiveError::OutOfRange {
344 min: 1,
345 max: 65535,
346 actual: 0
347 }
348 );
349 }
350
351 #[test]
352 fn byte_size_constructors_work() {
353 assert_eq!(ByteSize::from_bytes(512).as_bytes(), 512);
354 assert_eq!(ByteSize::from_kb(1).as_bytes(), 1024);
355 assert_eq!(ByteSize::from_mb(1).as_bytes(), 1024 * 1024);
356 assert_eq!(ByteSize::from_gb(1).as_bytes(), 1024 * 1024 * 1024);
357 }
358
359 #[test]
360 fn byte_size_display_works() {
361 assert_eq!(ByteSize::from_bytes(512).to_string(), "512 B");
362 assert_eq!(ByteSize::from_kb(1).to_string(), "1.00 KB");
363 assert_eq!(ByteSize::from_kb(1536 / 1024).to_string(), "1.00 KB");
364 assert_eq!(ByteSize::from_bytes(1536).to_string(), "1.50 KB");
365 assert_eq!(
366 ByteSize::from_bytes(1024 * 1024 + 512 * 1024).to_string(),
367 "1.50 MB"
368 );
369 assert_eq!(
370 ByteSize::from_bytes(1024 * 1024 * 1024 + 512 * 1024 * 1024).to_string(),
371 "1.50 GB"
372 );
373 }
374
375 #[test]
376 fn percent_try_from_u8() {
377 assert_eq!(Percent::try_from(50u8).unwrap().get(), 50);
378 assert!(Percent::try_from(101u8).is_err());
379 }
380
381 #[test]
382 fn percent_from_into_u8() {
383 let p = Percent::new(75).unwrap();
384 let v: u8 = p.into();
385 assert_eq!(v, 75);
386 }
387
388 #[test]
389 fn port_try_from_u16() {
390 assert_eq!(Port::try_from(8080u16).unwrap().get(), 8080);
391 assert!(Port::try_from(0u16).is_err());
392 }
393
394 #[test]
395 fn port_from_into_u16() {
396 let p = Port::new(443).unwrap();
397 let v: u16 = p.into();
398 assert_eq!(v, 443);
399 }
400
401 #[test]
402 fn port_display() {
403 assert_eq!(Port::new(8080).unwrap().to_string(), "8080");
404 }
405
406 #[test]
407 fn byte_size_from_u64() {
408 let s = ByteSize::from(2048u64);
409 assert_eq!(s.as_bytes(), 2048);
410 }
411
412 #[test]
413 fn byte_size_into_u64() {
414 let s = ByteSize::from_bytes(4096);
415 let v: u64 = s.into();
416 assert_eq!(v, 4096);
417 }
418
419 #[test]
420 fn positive_int_accepts_nonzero() {
421 assert_eq!(PositiveInt::new(1).unwrap().get(), 1);
422 assert_eq!(PositiveInt::new(u64::MAX).unwrap().get(), u64::MAX);
423 }
424
425 #[test]
426 fn positive_int_rejects_zero() {
427 assert!(PositiveInt::new(0).is_err());
428 }
429
430 #[test]
431 fn positive_int_display() {
432 assert_eq!(PositiveInt::new(42).unwrap().to_string(), "42");
433 }
434
435 #[test]
436 fn positive_int_try_from_and_into() {
437 let p = PositiveInt::try_from(10u64).unwrap();
438 let v: u64 = p.into();
439 assert_eq!(v, 10);
440 }
441
442 #[test]
443 fn positive_float_accepts_positive() {
444 assert_eq!(PositiveFloat::new(0.001).unwrap().get(), 0.001);
445 assert_eq!(PositiveFloat::new(f64::MAX).unwrap().get(), f64::MAX);
446 }
447
448 #[test]
449 fn positive_float_rejects_zero() {
450 assert!(PositiveFloat::new(0.0).is_err());
451 }
452
453 #[test]
454 fn positive_float_rejects_negative() {
455 assert!(PositiveFloat::new(-1.0).is_err());
456 }
457
458 #[test]
459 fn positive_float_rejects_nan() {
460 assert!(PositiveFloat::new(f64::NAN).is_err());
461 }
462
463 #[test]
464 fn positive_float_rejects_infinity() {
465 assert!(PositiveFloat::new(f64::INFINITY).is_err());
466 }
467
468 #[test]
469 fn positive_float_try_from() {
470 assert!(PositiveFloat::try_from(1.5f64).is_ok());
471 assert!(PositiveFloat::try_from(0.0f64).is_err());
472 }
473
474 #[test]
475 fn percentage_f64_accepts_boundaries() {
476 assert_eq!(PercentageF64::new(0.0).unwrap().get(), 0.0);
477 assert_eq!(PercentageF64::new(50.5).unwrap().get(), 50.5);
478 assert_eq!(PercentageF64::new(100.0).unwrap().get(), 100.0);
479 }
480
481 #[test]
482 fn percentage_f64_rejects_out_of_range() {
483 assert!(PercentageF64::new(-0.1).is_err());
484 assert!(PercentageF64::new(100.1).is_err());
485 }
486
487 #[test]
488 fn percentage_f64_rejects_nan() {
489 assert!(PercentageF64::new(f64::NAN).is_err());
490 }
491
492 #[test]
493 fn percentage_f64_as_fraction() {
494 assert_eq!(PercentageF64::new(25.0).unwrap().as_fraction(), 0.25);
495 }
496
497 #[test]
498 fn percentage_f64_display() {
499 assert_eq!(PercentageF64::new(42.5).unwrap().to_string(), "42.5%");
500 }
501
502 #[test]
503 fn percentage_f64_try_from() {
504 assert!(PercentageF64::try_from(50.0f64).is_ok());
505 assert!(PercentageF64::try_from(101.0f64).is_err());
506 }
507}