made_core/value_objects/
rounds.rs1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::error::DomainError;
8
9pub const MAX_ROUNDS: u32 = 16;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(transparent)]
20pub struct Rounds(u32);
21
22impl Rounds {
23 pub const ZERO: Self = Self(0);
24
25 pub fn new(value: u32) -> Result<Self, DomainError> {
26 if value > MAX_ROUNDS {
27 return Err(DomainError::OutOfRange {
28 field: "rounds",
29 value: f64::from(value),
30 min: 0.0,
31 max: f64::from(MAX_ROUNDS),
32 });
33 }
34 Ok(Self(value))
35 }
36
37 #[must_use]
38 pub fn get(self) -> u32 {
39 self.0
40 }
41}
42
43impl Default for Rounds {
44 fn default() -> Self {
47 Self(1)
48 }
49}
50
51impl fmt::Display for Rounds {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "{}", self.0)
54 }
55}
56
57impl TryFrom<u32> for Rounds {
58 type Error = DomainError;
59 fn try_from(value: u32) -> Result<Self, Self::Error> {
60 Self::new(value)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn zero_is_allowed() {
70 assert_eq!(Rounds::new(0).unwrap().get(), 0);
71 assert_eq!(Rounds::ZERO.get(), 0);
72 }
73
74 #[test]
75 fn default_is_one() {
76 assert_eq!(Rounds::default().get(), 1);
77 }
78
79 #[test]
80 fn upper_bound_is_max_rounds() {
81 assert!(Rounds::new(MAX_ROUNDS).is_ok());
82 }
83
84 #[test]
85 fn above_upper_bound_is_rejected() {
86 let err = Rounds::new(MAX_ROUNDS + 1).unwrap_err();
87 assert!(matches!(
88 err,
89 DomainError::OutOfRange {
90 field: "rounds",
91 ..
92 }
93 ));
94 }
95
96 #[test]
97 fn display_is_numeric() {
98 assert_eq!(Rounds::new(3).unwrap().to_string(), "3");
99 }
100
101 #[test]
102 fn serde_is_transparent() {
103 assert_eq!(
104 serde_json::to_string(&Rounds::new(4).unwrap()).unwrap(),
105 "4"
106 );
107 }
108}