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