pricelevel/utils/
value.rs1use crate::errors::PriceLevelError;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::str::FromStr;
5
6#[derive(
8 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
9)]
10#[serde(transparent)]
11pub struct Price(u128);
12
13impl Price {
14 pub const ZERO: Self = Self(0);
16
17 #[must_use]
19 pub const fn new(value: u128) -> Self {
20 Self(value)
21 }
22
23 pub fn try_new(value: u128) -> Result<Self, PriceLevelError> {
32 Ok(Self(value))
33 }
34
35 pub fn from_f64(value: f64) -> Result<Self, PriceLevelError> {
45 if !value.is_finite() || value < 0.0 {
46 return Err(PriceLevelError::InvalidOperation {
47 message: format!("invalid price from f64: {value}"),
48 });
49 }
50
51 let rounded = value.round();
52 if rounded >= 2.0_f64.powi(128) {
54 return Err(PriceLevelError::InvalidOperation {
55 message: format!("price from f64 out of u128 range: {value}"),
56 });
57 }
58
59 Ok(Self(rounded as u128))
60 }
61
62 #[must_use]
64 pub fn to_f64_lossy(self) -> f64 {
65 self.0 as f64
66 }
67
68 #[must_use]
70 pub const fn as_u128(self) -> u128 {
71 self.0
72 }
73}
74
75impl fmt::Display for Price {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{}", self.0)
78 }
79}
80
81impl FromStr for Price {
82 type Err = PriceLevelError;
83
84 fn from_str(s: &str) -> Result<Self, Self::Err> {
85 s.parse::<u128>()
86 .map(Self)
87 .map_err(|_| PriceLevelError::InvalidFieldValue {
88 field: "price".to_string(),
89 value: s.to_string(),
90 })
91 }
92}
93
94#[derive(
96 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
97)]
98#[serde(transparent)]
99pub struct Quantity(u64);
100
101impl Quantity {
102 pub const ZERO: Self = Self(0);
104
105 #[must_use]
107 pub const fn new(value: u64) -> Self {
108 Self(value)
109 }
110
111 pub fn try_new(value: u64) -> Result<Self, PriceLevelError> {
120 Ok(Self(value))
121 }
122
123 pub fn from_f64(value: f64) -> Result<Self, PriceLevelError> {
133 if !value.is_finite() || value < 0.0 {
134 return Err(PriceLevelError::InvalidOperation {
135 message: format!("invalid quantity from f64: {value}"),
136 });
137 }
138
139 let rounded = value.round();
140 if rounded >= 2.0_f64.powi(64) {
142 return Err(PriceLevelError::InvalidOperation {
143 message: format!("quantity from f64 out of u64 range: {value}"),
144 });
145 }
146
147 Ok(Self(rounded as u64))
148 }
149
150 #[must_use]
152 pub fn to_f64_lossy(self) -> f64 {
153 self.0 as f64
154 }
155
156 #[must_use]
158 pub const fn as_u64(self) -> u64 {
159 self.0
160 }
161}
162
163impl fmt::Display for Quantity {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(f, "{}", self.0)
166 }
167}
168
169impl FromStr for Quantity {
170 type Err = PriceLevelError;
171
172 fn from_str(s: &str) -> Result<Self, Self::Err> {
173 s.parse::<u64>()
174 .map(Self)
175 .map_err(|_| PriceLevelError::InvalidFieldValue {
176 field: "quantity".to_string(),
177 value: s.to_string(),
178 })
179 }
180}
181
182#[derive(
184 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
185)]
186#[serde(transparent)]
187pub struct TimestampMs(u64);
188
189impl TimestampMs {
190 pub const ZERO: Self = Self(0);
192
193 #[must_use]
195 pub const fn new(value: u64) -> Self {
196 Self(value)
197 }
198
199 pub fn try_new(value: u64) -> Result<Self, PriceLevelError> {
209 Ok(Self(value))
210 }
211
212 #[must_use]
214 pub const fn as_u64(self) -> u64 {
215 self.0
216 }
217}
218
219impl fmt::Display for TimestampMs {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 write!(f, "{}", self.0)
222 }
223}
224
225impl FromStr for TimestampMs {
226 type Err = PriceLevelError;
227
228 fn from_str(s: &str) -> Result<Self, Self::Err> {
229 s.parse::<u64>()
230 .map(Self)
231 .map_err(|_| PriceLevelError::InvalidFieldValue {
232 field: "timestamp".to_string(),
233 value: s.to_string(),
234 })
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::{Price, Quantity, TimestampMs};
241 use std::str::FromStr;
242
243 #[test]
244 fn price_roundtrip() {
245 let value = Price::new(1_000);
246 let parsed = Price::from_str(&value.to_string());
247 assert!(parsed.is_ok());
248 assert_eq!(parsed.unwrap_or_default(), value);
249 }
250
251 #[test]
252 fn quantity_roundtrip() {
253 let value = Quantity::new(42);
254 let parsed = Quantity::from_str(&value.to_string());
255 assert!(parsed.is_ok());
256 assert_eq!(parsed.unwrap_or_default(), value);
257 }
258
259 #[test]
260 fn timestamp_roundtrip() {
261 let value = TimestampMs::new(1_716_000_000_000);
262 let parsed = TimestampMs::from_str(&value.to_string());
263 assert!(parsed.is_ok());
264 assert_eq!(parsed.unwrap_or_default(), value);
265 }
266
267 #[test]
268 fn from_f64_rejects_negative() {
269 assert!(Price::from_f64(-1.0).is_err());
270 assert!(Quantity::from_f64(-1.0).is_err());
271 }
272
273 #[test]
274 fn from_f64_rejects_out_of_range() {
275 assert!(matches!(
278 Price::from_f64(2.0_f64.powi(128)),
279 Err(crate::errors::PriceLevelError::InvalidOperation { .. })
280 ));
281 assert!(matches!(
282 Quantity::from_f64(2.0_f64.powi(64)),
283 Err(crate::errors::PriceLevelError::InvalidOperation { .. })
284 ));
285 assert!(Price::from_f64(1_000_000.0).is_ok());
287 assert!(Quantity::from_f64(1_000_000.0).is_ok());
288 }
289
290 #[test]
291 fn from_f64_rejects_non_finite() {
292 assert!(Price::from_f64(f64::NAN).is_err());
293 assert!(Price::from_f64(f64::INFINITY).is_err());
294 assert!(Quantity::from_f64(f64::NAN).is_err());
295 }
296}