Skip to main content

icydb_schema/
types.rs

1use crate::prelude::*;
2use candid::CandidType;
3use darling::FromMeta;
4use derive_more::{Display, FromStr};
5use icydb_primitives::ScalarKind;
6use proc_macro2::TokenStream;
7use quote::{ToTokens, format_ident, quote};
8
9///
10/// Cardinality
11///
12
13#[derive(
14    CandidType, Clone, Copy, Default, Debug, Deserialize, Display, Eq, FromStr, PartialEq, Serialize,
15)]
16pub enum Cardinality {
17    #[default]
18    One,
19    Opt,
20    Many,
21}
22
23impl FromMeta for Cardinality {
24    fn from_string(s: &str) -> Result<Self, darling::Error> {
25        s.parse::<Self>()
26            .map_err(|_| darling::Error::unknown_value(s))
27    }
28}
29
30impl ToTokens for Cardinality {
31    fn to_tokens(&self, tokens: &mut TokenStream) {
32        let ident = format_ident!("{self}");
33
34        tokens.extend(quote!(::icydb::schema::types::Cardinality::#ident));
35    }
36}
37
38///
39/// Primitive
40///
41
42#[derive(
43    CandidType, Clone, Copy, Debug, Deserialize, Display, Eq, PartialEq, FromStr, Serialize,
44)]
45#[remain::sorted]
46pub enum Primitive {
47    Account,
48    Blob,
49    Bool,
50    Date,
51    Decimal,
52    Duration,
53    E8s,
54    E18s,
55    Float32,
56    Float64,
57    Int,
58    Int8,
59    Int16,
60    Int32,
61    Int64,
62    Int128,
63    Nat,
64    Nat8,
65    Nat16,
66    Nat32,
67    Nat64,
68    Nat128,
69    Principal,
70    Subaccount,
71    Text,
72    Timestamp,
73    Ulid,
74    Unit,
75}
76
77const fn primitive_scalar_kind(primitive: Primitive) -> ScalarKind {
78    match primitive {
79        Primitive::Account => ScalarKind::Account,
80        Primitive::Blob => ScalarKind::Blob,
81        Primitive::Bool => ScalarKind::Bool,
82        Primitive::Date => ScalarKind::Date,
83        Primitive::Decimal => ScalarKind::Decimal,
84        Primitive::Duration => ScalarKind::Duration,
85        Primitive::E8s => ScalarKind::E8s,
86        Primitive::E18s => ScalarKind::E18s,
87        Primitive::Float32 => ScalarKind::Float32,
88        Primitive::Float64 => ScalarKind::Float64,
89        Primitive::Int => ScalarKind::IntBig,
90        Primitive::Int8 | Primitive::Int16 | Primitive::Int32 | Primitive::Int64 => ScalarKind::Int,
91        Primitive::Int128 => ScalarKind::Int128,
92        Primitive::Nat => ScalarKind::UintBig,
93        Primitive::Nat8 | Primitive::Nat16 | Primitive::Nat32 | Primitive::Nat64 => {
94            ScalarKind::Uint
95        }
96        Primitive::Nat128 => ScalarKind::Uint128,
97        Primitive::Principal => ScalarKind::Principal,
98        Primitive::Subaccount => ScalarKind::Subaccount,
99        Primitive::Text => ScalarKind::Text,
100        Primitive::Timestamp => ScalarKind::Timestamp,
101        Primitive::Ulid => ScalarKind::Ulid,
102        Primitive::Unit => ScalarKind::Unit,
103    }
104}
105
106impl Primitive {
107    #[must_use]
108    pub const fn supports_arithmetic(self) -> bool {
109        primitive_scalar_kind(self).supports_arithmetic()
110    }
111
112    #[must_use]
113    pub const fn is_storage_key_encodable(self) -> bool {
114        primitive_scalar_kind(self).is_storage_key_encodable()
115    }
116
117    #[must_use]
118    pub const fn supports_remainder(self) -> bool {
119        matches!(
120            self,
121            Self::Decimal
122                | Self::Int8
123                | Self::Int16
124                | Self::Int32
125                | Self::Int64
126                | Self::Int128
127                | Self::Nat8
128                | Self::Nat16
129                | Self::Nat32
130                | Self::Nat64
131                | Self::Nat128
132        )
133    }
134
135    #[must_use]
136    pub const fn supports_copy(self) -> bool {
137        !matches!(self, Self::Blob | Self::Int | Self::Nat | Self::Text)
138    }
139
140    #[must_use]
141    pub const fn supports_hash(self) -> bool {
142        !matches!(self, Self::Blob | Self::Unit)
143    }
144
145    // Int and Nat are unbounded integers so have no native representation
146    #[must_use]
147    pub const fn supports_num_cast(self) -> bool {
148        matches!(
149            self,
150            Self::Date
151                | Self::Decimal
152                | Self::Duration
153                | Self::E8s
154                | Self::E18s
155                | Self::Int8
156                | Self::Int16
157                | Self::Int32
158                | Self::Int64
159                | Self::Float32
160                | Self::Float64
161                | Self::Nat8
162                | Self::Nat16
163                | Self::Nat32
164                | Self::Nat64
165                | Self::Timestamp
166        )
167    }
168
169    // both Ord and PartialOrd
170    #[must_use]
171    pub const fn supports_ord(self) -> bool {
172        primitive_scalar_kind(self).supports_ordering()
173    }
174
175    //
176    // grouped helpers
177    //
178
179    #[must_use]
180    pub const fn is_decimal(self) -> bool {
181        matches!(self, Self::Decimal)
182    }
183
184    // is_numeric
185    // Includes ints, floats, fixed‑point (E8s/E18s), and Decimal.
186    #[must_use]
187    pub const fn is_numeric(self) -> bool {
188        self.is_int() || self.is_float() || self.is_fixed_point() || self.is_decimal()
189    }
190
191    #[must_use]
192    pub const fn is_float(self) -> bool {
193        matches!(self, Self::Float32 | Self::Float64)
194    }
195
196    #[must_use]
197    pub const fn is_signed_int(self) -> bool {
198        matches!(
199            self,
200            Self::Int | Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 | Self::Int128
201        )
202    }
203
204    #[must_use]
205    pub const fn is_unsigned_int(self) -> bool {
206        matches!(
207            self,
208            Self::Nat | Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 | Self::Nat128
209        )
210    }
211
212    #[must_use]
213    pub const fn is_int(self) -> bool {
214        self.is_signed_int() || self.is_unsigned_int()
215    }
216
217    #[must_use]
218    pub const fn is_fixed_point(self) -> bool {
219        matches!(self, Self::E8s | Self::E18s)
220    }
221
222    #[must_use]
223    pub fn as_type(self) -> TokenStream {
224        let ident = format_ident!("{self}");
225
226        quote!(::icydb::types::#ident)
227    }
228
229    ///
230    /// Returns the numeric cast function suffix for supported primitives.
231    /// Emits a structured error for non-numeric primitives.
232    ///
233    pub fn num_cast_fn(self) -> Result<&'static str, darling::Error> {
234        match self {
235            Self::E18s => Ok("u128"),
236            Self::Float32 => Ok("f32"),
237            Self::Float64 | Self::Decimal => Ok("f64"),
238            Self::Int8 => Ok("i8"),
239            Self::Int16 => Ok("i16"),
240            Self::Int32 | Self::Date => Ok("i32"),
241            Self::Int64 => Ok("i64"),
242            Self::Nat8 => Ok("u8"),
243            Self::Nat16 => Ok("u16"),
244            Self::Nat32 => Ok("u32"),
245            Self::Nat64 | Self::Duration | Self::E8s | Self::Timestamp => Ok("u64"),
246            _ => Err(darling::Error::custom(format!(
247                "numeric cast is unsupported for primitive {self}"
248            ))),
249        }
250    }
251}
252
253impl FromMeta for Primitive {
254    fn from_string(s: &str) -> Result<Self, darling::Error> {
255        s.parse::<Self>()
256            .map_err(|_| darling::Error::unknown_value(s))
257    }
258}
259
260impl ToTokens for Primitive {
261    fn to_tokens(&self, tokens: &mut TokenStream) {
262        let ident = format_ident!("{self}");
263
264        tokens.extend(quote!(::icydb::schema::types::Primitive::#ident));
265    }
266}