Skip to main content

icydb_schema/decimal/
wire.rs

1use crate::{TypeParseError, decimal::Decimal};
2use candid::CandidType;
3use serde::{Deserialize, Serialize};
4use serde_bytes::ByteBuf;
5
6impl CandidType for Decimal {
7    fn ty() -> candid::types::Type {
8        <String as CandidType>::ty()
9    }
10
11    fn _ty() -> candid::types::Type {
12        candid::types::TypeInner::Text.into()
13    }
14
15    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
16    where
17        S: candid::types::Serializer,
18    {
19        serializer.serialize_text(&self.to_string())
20    }
21}
22
23impl<'de> Deserialize<'de> for Decimal {
24    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
25    where
26        D: serde::Deserializer<'de>,
27    {
28        #[derive(Deserialize)]
29        #[serde(untagged)]
30        enum DecimalPayload {
31            Binary((ByteBuf, u32)),
32            Text(String),
33        }
34
35        if deserializer.is_human_readable() {
36            let s = String::deserialize(deserializer)?;
37            return s
38                .parse::<Self>()
39                .map_err(|_| serde::de::Error::custom(TypeParseError::InvalidDecimal));
40        }
41
42        // Candid currently reports non-human-readable, but Decimal's Candid wire type is `text`.
43        // Accept both payloads here so Candid decode remains correct while binary formats
44        // continue to use the canonical `(mantissa_bytes, scale)` shape.
45        let payload: DecimalPayload = Deserialize::deserialize(deserializer)?;
46        let (mantissa_bytes, scale) = match payload {
47            DecimalPayload::Binary(parts) => parts,
48            DecimalPayload::Text(s) => {
49                return s
50                    .parse::<Self>()
51                    .map_err(|_| serde::de::Error::custom(TypeParseError::InvalidDecimal));
52            }
53        };
54
55        if mantissa_bytes.len() != 16 {
56            return Err(serde::de::Error::custom(TypeParseError::InvalidDecimal));
57        }
58
59        let mut mantissa_buf = [0u8; 16];
60        mantissa_buf.copy_from_slice(mantissa_bytes.as_ref());
61        let mantissa = i128::from_be_bytes(mantissa_buf);
62
63        Self::checked_from_mantissa_scale(mantissa, scale)
64            .ok_or_else(|| serde::de::Error::custom(TypeParseError::InvalidDecimal))
65    }
66}
67
68impl Serialize for Decimal {
69    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70    where
71        S: serde::Serializer,
72    {
73        serializer.serialize_str(&self.to_string())
74    }
75}
76
77// lossy f32 done on purpose as these ORM floats aren't designed for NaN etc.
78impl From<f32> for Decimal {
79    fn from(n: f32) -> Self {
80        Self::from_f32_lossy(n).unwrap_or(Self::ZERO)
81    }
82}
83
84impl From<f64> for Decimal {
85    fn from(n: f64) -> Self {
86        Self::from_f64_lossy(n).unwrap_or(Self::ZERO)
87    }
88}
89
90macro_rules! impl_decimal_from_signed_int {
91    ( $( $type:ty ),* ) => {
92        $(
93            impl From<$type> for Decimal {
94                fn from(n: $type) -> Self {
95                    Self {
96                        mantissa: i128::from(n),
97                        scale: 0,
98                    }
99                }
100            }
101        )*
102    };
103}
104
105macro_rules! impl_decimal_from_unsigned_int {
106    ( $( $type:ty ),* ) => {
107        $(
108            impl From<$type> for Decimal {
109                fn from(n: $type) -> Self {
110                    Self {
111                        mantissa: i128::from(n),
112                        scale: 0,
113                    }
114                }
115            }
116        )*
117    };
118}
119
120impl_decimal_from_unsigned_int!(u8, u16, u32, u64);
121impl_decimal_from_signed_int!(i8, i16, i32, i64, i128);
122
123impl From<u128> for Decimal {
124    fn from(n: u128) -> Self {
125        let mantissa = i128::try_from(n).unwrap_or(i128::MAX);
126        Self { mantissa, scale: 0 }
127    }
128}