df_hyperliquid_core/utils/
figure.rs1use std::fmt;
7
8use serde::{Deserialize, Deserializer, de};
9use serde_json::value::RawValue;
10
11use crate::utils::dec::Dec;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct Figure(String);
16
17impl Figure {
18 #[must_use]
20 pub fn as_str(&self) -> &str {
21 &self.0
22 }
23
24 #[must_use]
27 pub fn to_dec(&self) -> Option<Dec> {
28 let (mantissa, exponent) = match self.0.split_once(['e', 'E']) {
29 Some((mantissa, exponent)) => (mantissa, exponent.parse::<i32>().ok()?),
30 None => (self.0.as_str(), 0),
31 };
32 let mut value = Dec::parse(mantissa).ok()?;
33 let mut shift = exponent.unsigned_abs();
34 while shift > 0 {
35 let step = shift.min(18);
36 let factor = match exponent < 0 {
37 true => Dec::new(1, step)?,
38 false => Dec::new(10i128.checked_pow(step)?, 0)?,
39 };
40 value = value.checked_mul(factor)?;
41 shift -= step;
42 }
43 Some(value)
44 }
45
46 fn read(text: &str) -> Option<Figure> {
47 let plain = |text: &str| {
48 !text.is_empty()
49 && text.bytes().all(|byte| byte.is_ascii_digit() || matches!(byte, b'-' | b'+' | b'.' | b'e' | b'E'))
50 };
51 match text.strip_prefix('"') {
52 Some(_) => serde_json::from_str::<String>(text).ok().filter(|inner| plain(inner)).map(Figure),
53 None => plain(text).then(|| Figure(text.to_owned())),
54 }
55 }
56}
57
58impl fmt::Display for Figure {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 f.write_str(&self.0)
61 }
62}
63
64impl<'de> Deserialize<'de> for Figure {
67 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
68 let raw = Box::<RawValue>::deserialize(deserializer)?;
69 Figure::read(raw.get().trim()).ok_or_else(|| de::Error::custom(format!("`{}` is not a number", raw.get())))
70 }
71}
72
73#[cfg(test)]
74#[path = "tests/figure.rs"]
75mod tests;