canic_types/
decimal.rs

1use candid::{
2    CandidType,
3    types::{Serializer, Type, TypeInner},
4};
5use rust_decimal::Decimal as WrappedDecimal;
6use serde::{Deserialize, Serialize};
7use std::str::FromStr;
8
9///
10/// Decimal
11/// Candid-friendly decimal wrapper backed by `rust_decimal::Decimal`.
12///
13/// Candid encodes this as `text` using the canonical `to_string()` representation.
14///
15
16#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
17pub struct Decimal(pub WrappedDecimal);
18
19impl Decimal {
20    #[must_use]
21    pub const fn inner(self) -> WrappedDecimal {
22        self.0
23    }
24}
25
26impl CandidType for Decimal {
27    fn _ty() -> Type {
28        TypeInner::Text.into()
29    }
30
31    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
32    where
33        S: Serializer,
34    {
35        self.0.to_string().idl_serialize(serializer)
36    }
37}
38
39impl Serialize for Decimal {
40    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41    where
42        S: serde::Serializer,
43    {
44        serializer.serialize_str(&self.0.to_string())
45    }
46}
47
48impl<'de> Deserialize<'de> for Decimal {
49    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
50    where
51        D: serde::Deserializer<'de>,
52    {
53        let s = String::deserialize(deserializer)?;
54        let d = WrappedDecimal::from_str(&s).map_err(serde::de::Error::custom)?;
55        Ok(Self(d))
56    }
57}
58
59impl From<WrappedDecimal> for Decimal {
60    fn from(value: WrappedDecimal) -> Self {
61        Self(value)
62    }
63}
64
65impl From<u64> for Decimal {
66    fn from(value: u64) -> Self {
67        Self(WrappedDecimal::from(value))
68    }
69}
70
71impl core::fmt::Display for Decimal {
72    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
73        self.0.fmt(f)
74    }
75}
76
77impl FromStr for Decimal {
78    type Err = rust_decimal::Error;
79
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        Ok(Self(WrappedDecimal::from_str(s)?))
82    }
83}
84
85///
86/// TESTS
87///
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn decimal_roundtrips_via_string() {
95        let d: Decimal = "12.345".parse().unwrap();
96        assert_eq!(d.to_string(), "12.345");
97    }
98}