1use std::fmt;
13
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum Currency {
19 Toman,
21 Rial,
23}
24
25impl fmt::Display for Currency {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.write_str(match self {
28 Currency::Toman => "Toman",
29 Currency::Rial => "Rial",
30 })
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
48pub struct Amount {
49 rials: i64,
50}
51
52impl Amount {
53 #[must_use]
55 pub const fn toman(value: i64) -> Self {
56 Self {
57 rials: value.saturating_mul(10),
58 }
59 }
60
61 #[must_use]
63 pub const fn rial(value: i64) -> Self {
64 Self { rials: value }
65 }
66
67 #[must_use]
69 pub const fn new(value: i64, currency: Currency) -> Self {
70 match currency {
71 Currency::Toman => Self::toman(value),
72 Currency::Rial => Self::rial(value),
73 }
74 }
75
76 #[must_use]
78 pub const fn as_rials(&self) -> i64 {
79 self.rials
80 }
81
82 #[must_use]
86 pub const fn as_tomans(&self) -> i64 {
87 self.rials / 10
88 }
89
90 #[must_use]
92 pub const fn is_zero(&self) -> bool {
93 self.rials == 0
94 }
95}
96
97impl fmt::Display for Amount {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 let mut s = String::new();
101 let abs = self.rials.unsigned_abs().to_string();
102 let bytes = abs.as_bytes();
103 if self.rials < 0 {
104 s.push('-');
105 }
106 for (i, b) in bytes.iter().enumerate() {
107 if i > 0 && (bytes.len() - i).is_multiple_of(3) {
108 s.push(',');
109 }
110 s.push(*b as char);
111 }
112 write!(f, "{s} Rial")
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn unit_conversion() {
122 assert_eq!(Amount::toman(50_000).as_rials(), 500_000);
123 assert_eq!(Amount::rial(500_000).as_tomans(), 50_000);
124 }
125
126 #[test]
127 fn comparison() {
128 assert!(Amount::toman(100) < Amount::toman(200));
129 assert_eq!(Amount::toman(100), Amount::rial(1_000));
130 }
131
132 #[test]
133 fn display_with_separators() {
134 assert_eq!(Amount::rial(1_234_567).to_string(), "1,234,567 Rial");
135 assert_eq!(Amount::rial(0).to_string(), "0 Rial");
136 }
137
138 #[test]
139 fn negative_amount_displays() {
140 assert_eq!(Amount::rial(-500).to_string(), "-500 Rial");
141 }
142
143 #[test]
144 fn const_constructors() {
145 const FEE: Amount = Amount::toman(1_000);
146 assert_eq!(FEE.as_rials(), 10_000);
147 }
148}