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    Float32,
54    Float64,
55    Int,
56    Int8,
57    Int16,
58    Int32,
59    Int64,
60    Int128,
61    Nat,
62    Nat8,
63    Nat16,
64    Nat32,
65    Nat64,
66    Nat128,
67    Principal,
68    Subaccount,
69    Text,
70    Timestamp,
71    Ulid,
72    Unit,
73}
74
75const fn primitive_scalar_kind(primitive: Primitive) -> ScalarKind {
76    match primitive {
77        Primitive::Account => ScalarKind::Account,
78        Primitive::Blob => ScalarKind::Blob,
79        Primitive::Bool => ScalarKind::Bool,
80        Primitive::Date => ScalarKind::Date,
81        Primitive::Decimal => ScalarKind::Decimal,
82        Primitive::Duration => ScalarKind::Duration,
83        Primitive::Float32 => ScalarKind::Float32,
84        Primitive::Float64 => ScalarKind::Float64,
85        Primitive::Int => ScalarKind::IntBig,
86        Primitive::Int8 | Primitive::Int16 | Primitive::Int32 | Primitive::Int64 => ScalarKind::Int,
87        Primitive::Int128 => ScalarKind::Int128,
88        Primitive::Nat => ScalarKind::UintBig,
89        Primitive::Nat8 | Primitive::Nat16 | Primitive::Nat32 | Primitive::Nat64 => {
90            ScalarKind::Uint
91        }
92        Primitive::Nat128 => ScalarKind::Uint128,
93        Primitive::Principal => ScalarKind::Principal,
94        Primitive::Subaccount => ScalarKind::Subaccount,
95        Primitive::Text => ScalarKind::Text,
96        Primitive::Timestamp => ScalarKind::Timestamp,
97        Primitive::Ulid => ScalarKind::Ulid,
98        Primitive::Unit => ScalarKind::Unit,
99    }
100}
101
102impl Primitive {
103    #[must_use]
104    pub const fn supports_arithmetic(self) -> bool {
105        primitive_scalar_kind(self).supports_arithmetic()
106    }
107
108    #[must_use]
109    pub const fn is_storage_key_encodable(self) -> bool {
110        primitive_scalar_kind(self).is_storage_key_encodable()
111    }
112
113    #[must_use]
114    pub const fn supports_remainder(self) -> bool {
115        matches!(
116            self,
117            Self::Decimal
118                | Self::Int8
119                | Self::Int16
120                | Self::Int32
121                | Self::Int64
122                | Self::Int128
123                | Self::Nat8
124                | Self::Nat16
125                | Self::Nat32
126                | Self::Nat64
127                | Self::Nat128
128        )
129    }
130
131    #[must_use]
132    pub const fn supports_copy(self) -> bool {
133        !matches!(self, Self::Blob | Self::Int | Self::Nat | Self::Text)
134    }
135
136    #[must_use]
137    pub const fn supports_hash(self) -> bool {
138        !matches!(self, Self::Blob | Self::Unit)
139    }
140
141    // Int and Nat are unbounded integers so have no native representation
142    #[must_use]
143    pub const fn supports_num_cast(self) -> bool {
144        matches!(
145            self,
146            Self::Date
147                | Self::Decimal
148                | Self::Duration
149                | Self::Int8
150                | Self::Int16
151                | Self::Int32
152                | Self::Int64
153                | Self::Float32
154                | Self::Float64
155                | Self::Nat8
156                | Self::Nat16
157                | Self::Nat32
158                | Self::Nat64
159                | Self::Timestamp
160        )
161    }
162
163    // both Ord and PartialOrd
164    #[must_use]
165    pub const fn supports_ord(self) -> bool {
166        primitive_scalar_kind(self).supports_ordering()
167    }
168
169    //
170    // grouped helpers
171    //
172
173    #[must_use]
174    pub const fn is_decimal(self) -> bool {
175        matches!(self, Self::Decimal)
176    }
177
178    // is_numeric
179    // Includes ints, floats, and Decimal.
180    #[must_use]
181    pub const fn is_numeric(self) -> bool {
182        self.is_int() || self.is_float() || self.is_decimal()
183    }
184
185    #[must_use]
186    pub const fn is_float(self) -> bool {
187        matches!(self, Self::Float32 | Self::Float64)
188    }
189
190    #[must_use]
191    pub const fn is_signed_int(self) -> bool {
192        matches!(
193            self,
194            Self::Int | Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 | Self::Int128
195        )
196    }
197
198    #[must_use]
199    pub const fn is_unsigned_int(self) -> bool {
200        matches!(
201            self,
202            Self::Nat | Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 | Self::Nat128
203        )
204    }
205
206    #[must_use]
207    pub const fn is_int(self) -> bool {
208        self.is_signed_int() || self.is_unsigned_int()
209    }
210
211    #[must_use]
212    pub fn as_type(self) -> TokenStream {
213        let ident = format_ident!("{self}");
214
215        quote!(::icydb::types::#ident)
216    }
217
218    ///
219    /// Returns the numeric cast function suffix for supported primitives.
220    /// Emits a structured error for non-numeric primitives.
221    ///
222    pub fn num_cast_fn(self) -> Result<&'static str, darling::Error> {
223        match self {
224            Self::Float32 => Ok("f32"),
225            Self::Float64 | Self::Decimal => Ok("f64"),
226            Self::Int8 => Ok("i8"),
227            Self::Int16 => Ok("i16"),
228            Self::Int32 | Self::Date => Ok("i32"),
229            Self::Int64 => Ok("i64"),
230            Self::Nat8 => Ok("u8"),
231            Self::Nat16 => Ok("u16"),
232            Self::Nat32 => Ok("u32"),
233            Self::Nat64 | Self::Duration | Self::Timestamp => Ok("u64"),
234            _ => Err(darling::Error::custom(format!(
235                "numeric cast is unsupported for primitive {self}"
236            ))),
237        }
238    }
239}
240
241impl FromMeta for Primitive {
242    fn from_string(s: &str) -> Result<Self, darling::Error> {
243        s.parse::<Self>()
244            .map_err(|_| darling::Error::unknown_value(s))
245    }
246}
247
248impl ToTokens for Primitive {
249    fn to_tokens(&self, tokens: &mut TokenStream) {
250        let ident = format_ident!("{self}");
251
252        tokens.extend(quote!(::icydb::schema::types::Primitive::#ident));
253    }
254}