Skip to main content

icydb_schema/
numeric_value.rs

1//! Responsibility: scalar and newtype conversion through the canonical
2//! decimal numeric representation.
3//! Does not own: database coercion, arithmetic, comparison, or ordering policy.
4//! Boundary: typed numeric values <-> `Decimal` for generic typed policies.
5
6use crate::{Decimal, Duration, Float32, Float64, Timestamp};
7
8/// Fallible numeric round-trip contract used by generic validators and normalizers.
9///
10/// Implementors convert through `Decimal` so typed numeric policy stays
11/// explicit and local. Database evaluation semantics remain under `db::numeric`.
12pub trait NumericValue: Sized {
13    /// Convert this value into `Decimal` for generic numeric handling.
14    fn try_to_decimal(&self) -> Option<Decimal>;
15
16    /// Rebuild the value from `Decimal` after generic numeric handling.
17    fn try_from_decimal(value: Decimal) -> Option<Self>;
18}
19
20macro_rules! impl_numeric_value_signed {
21    ($($ty:ty),* $(,)?) => {
22        $(
23            impl NumericValue for $ty {
24                fn try_to_decimal(&self) -> Option<Decimal> {
25                    Decimal::from_i128(i128::from(*self))
26                }
27
28                fn try_from_decimal(value: Decimal) -> Option<Self> {
29                    value.to_i128().and_then(|inner| Self::try_from(inner).ok())
30                }
31            }
32        )*
33    };
34}
35
36macro_rules! impl_numeric_value_unsigned {
37    ($($ty:ty),* $(,)?) => {
38        $(
39            impl NumericValue for $ty {
40                fn try_to_decimal(&self) -> Option<Decimal> {
41                    Decimal::from_u128(u128::from(*self))
42                }
43
44                fn try_from_decimal(value: Decimal) -> Option<Self> {
45                    value.to_u128().and_then(|inner| Self::try_from(inner).ok())
46                }
47            }
48        )*
49    };
50}
51
52impl_numeric_value_signed!(i8, i16, i32, i64, i128);
53impl_numeric_value_unsigned!(u8, u16, u32, u64, u128);
54
55impl NumericValue for isize {
56    fn try_to_decimal(&self) -> Option<Decimal> {
57        Decimal::from_i128(i128::try_from(*self).ok()?)
58    }
59
60    fn try_from_decimal(value: Decimal) -> Option<Self> {
61        value.to_i128().and_then(|inner| Self::try_from(inner).ok())
62    }
63}
64
65impl NumericValue for usize {
66    fn try_to_decimal(&self) -> Option<Decimal> {
67        Decimal::from_u128(u128::try_from(*self).ok()?)
68    }
69
70    fn try_from_decimal(value: Decimal) -> Option<Self> {
71        value.to_u128().and_then(|inner| Self::try_from(inner).ok())
72    }
73}
74
75impl NumericValue for f32 {
76    fn try_to_decimal(&self) -> Option<Decimal> {
77        Decimal::from_f32_lossy(*self)
78    }
79
80    fn try_from_decimal(value: Decimal) -> Option<Self> {
81        value.to_f32()
82    }
83}
84
85impl NumericValue for f64 {
86    fn try_to_decimal(&self) -> Option<Decimal> {
87        Decimal::from_f64_lossy(*self)
88    }
89
90    fn try_from_decimal(value: Decimal) -> Option<Self> {
91        value.to_f64()
92    }
93}
94
95impl NumericValue for Duration {
96    fn try_to_decimal(&self) -> Option<Decimal> {
97        Decimal::from_u64(self.as_millis())
98    }
99
100    fn try_from_decimal(value: Decimal) -> Option<Self> {
101        value.to_u64().map(Self::from_millis)
102    }
103}
104
105impl NumericValue for Float32 {
106    fn try_to_decimal(&self) -> Option<Decimal> {
107        Decimal::from_f32_lossy(self.get())
108    }
109
110    fn try_from_decimal(value: Decimal) -> Option<Self> {
111        value.to_f32().and_then(Self::try_new)
112    }
113}
114
115impl NumericValue for Float64 {
116    fn try_to_decimal(&self) -> Option<Decimal> {
117        Decimal::from_f64_lossy(self.get())
118    }
119
120    fn try_from_decimal(value: Decimal) -> Option<Self> {
121        value.to_f64().and_then(Self::try_new)
122    }
123}
124
125impl NumericValue for Timestamp {
126    fn try_to_decimal(&self) -> Option<Decimal> {
127        Decimal::from_i64(self.as_millis())
128    }
129
130    fn try_from_decimal(value: Decimal) -> Option<Self> {
131        value.to_i64().map(Self::from_millis)
132    }
133}