smart_config/types.rs
1use std::{fmt, ops};
2
3use compile_fmt::{clip, compile_panic};
4
5use crate::metadata::{EtherUnit, SizeUnit};
6
7/// A wrapper providing a clear reminder that the wrapped value represents the number of bytes.
8///
9/// # Examples
10///
11/// In non-const context, the most idiomatic way to produce a size is to multiply [`SizeUnit`] by `u64`:
12///
13/// ```
14/// # use smart_config::{metadata::SizeUnit, ByteSize};
15/// let size = 128 * SizeUnit::MiB;
16/// assert_eq!(size, ByteSize(128 << 20));
17/// ```
18///
19/// In const context, [`Self::new()`] may be used instead:
20///
21/// ```
22/// # use smart_config::{metadata::SizeUnit, ByteSize};
23/// const SIZE: ByteSize = ByteSize::new(128, SizeUnit::MiB);
24/// assert_eq!(SIZE, ByteSize(128 << 20));
25/// ```
26#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct ByteSize(pub u64);
28
29impl fmt::Debug for ByteSize {
30 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31 fmt::Display::fmt(self, formatter)
32 }
33}
34
35impl fmt::Display for ByteSize {
36 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
37 if self.0 == 0 {
38 formatter.write_str("0 B")
39 } else if self.0 % (1 << 30) == 0 {
40 write!(formatter, "{} GiB", self.0 >> 30)
41 } else if self.0 % (1 << 20) == 0 {
42 write!(formatter, "{} MiB", self.0 >> 20)
43 } else if self.0 % (1 << 10) == 0 {
44 write!(formatter, "{} KiB", self.0 >> 10)
45 } else {
46 write!(formatter, "{} B", self.0)
47 }
48 }
49}
50
51macro_rules! impl_unit_conversions {
52 ($ty:ident($raw:ty), $unit:ident) => {
53 impl From<$raw> for $ty {
54 fn from(value: $raw) -> Self {
55 Self(value)
56 }
57 }
58
59 impl $ty {
60 /// Creates a value with the specified unit of measurement checking for overflow.
61 pub const fn checked(val: $raw, unit: $unit) -> Option<Self> {
62 match val.checked_mul(unit.value_in_unit()) {
63 Some(val) => Some(Self(val)),
64 None => None,
65 }
66 }
67
68 /// Creates a value with the specified unit of measurement.
69 ///
70 /// # Panics
71 ///
72 /// Panics on overflow.
73 pub const fn new(val: $raw, unit: $unit) -> Self {
74 if let Some(size) = Self::checked(val, unit) {
75 size
76 } else {
77 compile_panic!(
78 val => compile_fmt::fmt::<$raw>(), " ", unit.as_str() => clip(16, ""), " does not fit into a value"
79 );
80 }
81 }
82
83 /// Adds two byte sizes.
84 pub const fn checked_add(self, rhs: Self) -> Option<Self> {
85 match self.0.checked_add(rhs.0) {
86 Some(val) => Some(Self(val)),
87 None => None,
88 }
89 }
90
91 /// Multiplies this size by the given factor.
92 pub const fn checked_mul(self, factor: $raw) -> Option<Self> {
93 match self.0.checked_mul(factor) {
94 Some(val) => Some(Self(val)),
95 None => None,
96 }
97 }
98 }
99
100 impl From<$unit> for $ty {
101 fn from(unit: $unit) -> Self {
102 Self(unit.value_in_unit())
103 }
104 }
105
106 /// Panics on overflow.
107 impl ops::Mul<$raw> for $unit {
108 type Output = $ty;
109
110 fn mul(self, rhs: $raw) -> Self::Output {
111 $ty::from(self)
112 .checked_mul(rhs)
113 .unwrap_or_else(|| panic!("Integer overflow getting {rhs} * {self}"))
114 }
115 }
116
117 /// Panics on overflow.
118 impl ops::Mul<$unit> for $raw {
119 type Output = $ty;
120
121 fn mul(self, rhs: $unit) -> Self::Output {
122 $ty::from(rhs)
123 .checked_mul(self)
124 .unwrap_or_else(|| panic!("Integer overflow getting {self} * {rhs}"))
125 }
126 }
127
128 /// Panics on overflow.
129 impl ops::Add for $ty {
130 type Output = Self;
131
132 fn add(self, rhs: Self) -> Self::Output {
133 self.checked_add(rhs)
134 .unwrap_or_else(|| panic!("Integer overflow getting {self} + {rhs}"))
135 }
136 }
137
138 /// Panics on overflow.
139 impl ops::Mul<$raw> for $ty {
140 type Output = Self;
141
142 fn mul(self, rhs: $raw) -> Self::Output {
143 self.checked_mul(rhs)
144 .unwrap_or_else(|| panic!("Integer overflow getting {self} * {rhs}"))
145 }
146 }
147 };
148}
149
150impl_unit_conversions!(ByteSize(u64), SizeUnit);
151
152/// A wrapper for ether amounts.
153///
154/// # Examples
155///
156/// In non-const context, the most idiomatic way to produce a size is to multiply [`EtherUnit`] by `u128`:
157///
158/// ```
159/// # use smart_config::{metadata::EtherUnit, EtherAmount};
160/// let size = 100 * EtherUnit::Gwei;
161/// assert_eq!(size, EtherAmount(100_000_000_000));
162/// ```
163///
164/// In const context, [`Self::new()`] may be used instead:
165///
166/// ```
167/// # use smart_config::{metadata::EtherUnit, EtherAmount};
168/// const AMOUNT: EtherAmount = EtherAmount::new(100, EtherUnit::Gwei);
169/// assert_eq!(AMOUNT, EtherAmount(100_000_000_000));
170/// ```
171///
172/// ## As config param
173///
174/// `EtherAmount` can be parsed from a string with a unit suffix. See also [`WithUnit`](crate::de::WithUnit).
175///
176/// ```
177/// # use smart_config::{metadata::EtherUnit, EtherAmount};
178/// let amount: EtherAmount = "123 gwei".parse()?;
179/// assert_eq!(amount, 123 * EtherUnit::Gwei);
180///
181/// // Decimal values are supported. The value conversion is lossless.
182/// let amount: EtherAmount = "0.0013 ether".parse()?;
183/// assert_eq!(amount, 1_300_000 * EtherUnit::Gwei);
184///
185/// // Scientific / exponential notation is supported as well.
186/// let amount: EtherAmount = "2.5e12 wei".parse()?;
187/// assert_eq!(amount, EtherAmount(2_500_000_000_000));
188/// # Ok::<_, serde_json::Error>(())
189/// ```
190#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
191pub struct EtherAmount(pub u128);
192
193impl fmt::Debug for EtherAmount {
194 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
195 fmt::Display::fmt(self, formatter)
196 }
197}
198
199impl fmt::Display for EtherAmount {
200 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
201 if self.0 % EtherUnit::Ether.value_in_unit() == 0 {
202 write!(
203 formatter,
204 "{} ether",
205 self.0 / EtherUnit::Ether.value_in_unit()
206 )
207 } else if self.0 % EtherUnit::Gwei.value_in_unit() == 0 {
208 write!(
209 formatter,
210 "{} gwei",
211 self.0 / EtherUnit::Gwei.value_in_unit()
212 )
213 } else {
214 write!(formatter, "{} wei", self.0)
215 }
216 }
217}
218
219impl_unit_conversions!(EtherAmount(u128), EtherUnit);