Skip to main content

icydb_schema/
types.rs

1use crate::prelude::*;
2use candid::CandidType;
3use darling::FromMeta;
4use icydb_primitives::ScalarKind;
5use proc_macro2::TokenStream;
6use quote::{ToTokens, format_ident, quote};
7use std::str::FromStr;
8
9//
10// Cardinality
11//
12// Schema-level multiplicity marker used by codegen and validation passes.
13// `One` means a required single value.
14// `Opt` means an optional slot (nullable / absent is valid).
15// `Many` means repeated values (for list/set-like shapes).
16//
17
18#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
19pub enum Cardinality {
20    #[default]
21    One,
22    Opt,
23    Many,
24}
25
26impl FromStr for Cardinality {
27    type Err = &'static str;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        match s {
31            "One" => Ok(Self::One),
32            "Opt" => Ok(Self::Opt),
33            "Many" => Ok(Self::Many),
34            _ => Err("unknown Cardinality"),
35        }
36    }
37}
38
39impl FromMeta for Cardinality {
40    fn from_string(s: &str) -> Result<Self, darling::Error> {
41        s.parse::<Self>()
42            .map_err(|_| darling::Error::unknown_value(s))
43    }
44}
45
46impl ToTokens for Cardinality {
47    fn to_tokens(&self, tokens: &mut TokenStream) {
48        let ident = format_ident!("{self:?}");
49
50        tokens.extend(quote!(::icydb::schema::types::Cardinality::#ident));
51    }
52}
53
54//
55// Primitive
56//
57// Scalar primitive catalog used by schema macros and generated runtime wiring.
58// This enum is the canonical source for primitive capability checks
59// (ordering, arithmetic, casting, key-encoding, and hashing support).
60//
61
62#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
63#[remain::sorted]
64pub enum Primitive {
65    Account,
66    Blob,
67    Bool,
68    Date,
69    Decimal,
70    Duration,
71    Float32,
72    Float64,
73    Int,
74    Int8,
75    Int16,
76    Int32,
77    Int64,
78    Int128,
79    Nat,
80    Nat8,
81    Nat16,
82    Nat32,
83    Nat64,
84    Nat128,
85    Principal,
86    Subaccount,
87    Text,
88    Timestamp,
89    Ulid,
90    Unit,
91}
92
93impl FromStr for Primitive {
94    type Err = &'static str;
95
96    fn from_str(s: &str) -> Result<Self, Self::Err> {
97        match s {
98            "Account" => Ok(Self::Account),
99            "Blob" => Ok(Self::Blob),
100            "Bool" => Ok(Self::Bool),
101            "Date" => Ok(Self::Date),
102            "Decimal" => Ok(Self::Decimal),
103            "Duration" => Ok(Self::Duration),
104            "Float32" => Ok(Self::Float32),
105            "Float64" => Ok(Self::Float64),
106            "Int" => Ok(Self::Int),
107            "Int8" => Ok(Self::Int8),
108            "Int16" => Ok(Self::Int16),
109            "Int32" => Ok(Self::Int32),
110            "Int64" => Ok(Self::Int64),
111            "Int128" => Ok(Self::Int128),
112            "Nat" => Ok(Self::Nat),
113            "Nat8" => Ok(Self::Nat8),
114            "Nat16" => Ok(Self::Nat16),
115            "Nat32" => Ok(Self::Nat32),
116            "Nat64" => Ok(Self::Nat64),
117            "Nat128" => Ok(Self::Nat128),
118            "Principal" => Ok(Self::Principal),
119            "Subaccount" => Ok(Self::Subaccount),
120            "Text" => Ok(Self::Text),
121            "Timestamp" => Ok(Self::Timestamp),
122            "Ulid" => Ok(Self::Ulid),
123            "Unit" => Ok(Self::Unit),
124            _ => Err("unknown Primitive"),
125        }
126    }
127}
128
129const fn primitive_scalar_kind(primitive: Primitive) -> ScalarKind {
130    match primitive {
131        Primitive::Account => ScalarKind::Account,
132        Primitive::Blob => ScalarKind::Blob,
133        Primitive::Bool => ScalarKind::Bool,
134        Primitive::Date => ScalarKind::Date,
135        Primitive::Decimal => ScalarKind::Decimal,
136        Primitive::Duration => ScalarKind::Duration,
137        Primitive::Float32 => ScalarKind::Float32,
138        Primitive::Float64 => ScalarKind::Float64,
139        Primitive::Int => ScalarKind::IntBig,
140        Primitive::Int8 | Primitive::Int16 | Primitive::Int32 | Primitive::Int64 => ScalarKind::Int,
141        Primitive::Int128 => ScalarKind::Int128,
142        Primitive::Nat => ScalarKind::NatBig,
143        Primitive::Nat8 | Primitive::Nat16 | Primitive::Nat32 | Primitive::Nat64 => ScalarKind::Nat,
144        Primitive::Nat128 => ScalarKind::Nat128,
145        Primitive::Principal => ScalarKind::Principal,
146        Primitive::Subaccount => ScalarKind::Subaccount,
147        Primitive::Text => ScalarKind::Text,
148        Primitive::Timestamp => ScalarKind::Timestamp,
149        Primitive::Ulid => ScalarKind::Ulid,
150        Primitive::Unit => ScalarKind::Unit,
151    }
152}
153
154impl Primitive {
155    #[must_use]
156    pub const fn supports_arithmetic(self) -> bool {
157        primitive_scalar_kind(self).supports_arithmetic()
158    }
159
160    #[must_use]
161    pub const fn is_storage_key_encodable(self) -> bool {
162        primitive_scalar_kind(self).is_storage_key_encodable()
163    }
164
165    #[must_use]
166    pub const fn supports_remainder(self) -> bool {
167        matches!(
168            self,
169            Self::Decimal
170                | Self::Int8
171                | Self::Int16
172                | Self::Int32
173                | Self::Int64
174                | Self::Int128
175                | Self::Nat8
176                | Self::Nat16
177                | Self::Nat32
178                | Self::Nat64
179                | Self::Nat128
180        )
181    }
182
183    #[must_use]
184    pub const fn supports_copy(self) -> bool {
185        !matches!(self, Self::Blob | Self::Int | Self::Nat | Self::Text)
186    }
187
188    #[must_use]
189    pub const fn supports_hash(self) -> bool {
190        !matches!(self, Self::Blob | Self::Unit)
191    }
192
193    // NumericValue can fallibly route all numeric-like primitives through Decimal.
194    #[must_use]
195    pub const fn supports_numeric_value(self) -> bool {
196        matches!(
197            self,
198            Self::Date
199                | Self::Decimal
200                | Self::Duration
201                | Self::Int
202                | Self::Int8
203                | Self::Int16
204                | Self::Int32
205                | Self::Int64
206                | Self::Int128
207                | Self::Float32
208                | Self::Float64
209                | Self::Nat
210                | Self::Nat8
211                | Self::Nat16
212                | Self::Nat32
213                | Self::Nat64
214                | Self::Nat128
215                | Self::Timestamp
216        )
217    }
218
219    // both Ord and PartialOrd
220    #[must_use]
221    pub const fn supports_ord(self) -> bool {
222        primitive_scalar_kind(self).supports_ordering()
223    }
224
225    //
226    // grouped helpers
227    //
228
229    #[must_use]
230    pub const fn is_decimal(self) -> bool {
231        matches!(self, Self::Decimal)
232    }
233
234    // is_numeric
235    // Includes ints, floats, and Decimal.
236    #[must_use]
237    pub const fn is_numeric(self) -> bool {
238        self.is_int() || self.is_float() || self.is_decimal()
239    }
240
241    #[must_use]
242    pub const fn is_float(self) -> bool {
243        matches!(self, Self::Float32 | Self::Float64)
244    }
245
246    #[must_use]
247    pub const fn is_signed_int(self) -> bool {
248        matches!(
249            self,
250            Self::Int | Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 | Self::Int128
251        )
252    }
253
254    #[must_use]
255    pub const fn is_unsigned_int(self) -> bool {
256        matches!(
257            self,
258            Self::Nat | Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 | Self::Nat128
259        )
260    }
261
262    #[must_use]
263    pub const fn is_int(self) -> bool {
264        self.is_signed_int() || self.is_unsigned_int()
265    }
266
267    #[must_use]
268    pub fn as_type(self) -> TokenStream {
269        let ident = format_ident!("{self:?}");
270
271        quote!(::icydb::types::#ident)
272    }
273}
274
275impl FromMeta for Primitive {
276    fn from_string(s: &str) -> Result<Self, darling::Error> {
277        s.parse::<Self>()
278            .map_err(|_| darling::Error::unknown_value(s))
279    }
280}
281
282impl ToTokens for Primitive {
283    fn to_tokens(&self, tokens: &mut TokenStream) {
284        let ident = format_ident!("{self:?}");
285
286        tokens.extend(quote!(::icydb::schema::types::Primitive::#ident));
287    }
288}