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);