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::UintBig,
143        Primitive::Nat8 | Primitive::Nat16 | Primitive::Nat32 | Primitive::Nat64 => {
144            ScalarKind::Uint
145        }
146        Primitive::Nat128 => ScalarKind::Uint128,
147        Primitive::Principal => ScalarKind::Principal,
148        Primitive::Subaccount => ScalarKind::Subaccount,
149        Primitive::Text => ScalarKind::Text,
150        Primitive::Timestamp => ScalarKind::Timestamp,
151        Primitive::Ulid => ScalarKind::Ulid,
152        Primitive::Unit => ScalarKind::Unit,
153    }
154}
155
156impl Primitive {
157    #[must_use]
158    pub const fn supports_arithmetic(self) -> bool {
159        primitive_scalar_kind(self).supports_arithmetic()
160    }
161
162    #[must_use]
163    pub const fn is_storage_key_encodable(self) -> bool {
164        primitive_scalar_kind(self).is_storage_key_encodable()
165    }
166
167    #[must_use]
168    pub const fn supports_remainder(self) -> bool {
169        matches!(
170            self,
171            Self::Decimal
172                | Self::Int8
173                | Self::Int16
174                | Self::Int32
175                | Self::Int64
176                | Self::Int128
177                | Self::Nat8
178                | Self::Nat16
179                | Self::Nat32
180                | Self::Nat64
181                | Self::Nat128
182        )
183    }
184
185    #[must_use]
186    pub const fn supports_copy(self) -> bool {
187        !matches!(self, Self::Blob | Self::Int | Self::Nat | Self::Text)
188    }
189
190    #[must_use]
191    pub const fn supports_hash(self) -> bool {
192        !matches!(self, Self::Blob | Self::Unit)
193    }
194
195    // NumericValue can fallibly route all numeric-like primitives through Decimal.
196    #[must_use]
197    pub const fn supports_numeric_value(self) -> bool {
198        matches!(
199            self,
200            Self::Date
201                | Self::Decimal
202                | Self::Duration
203                | Self::Int
204                | Self::Int8
205                | Self::Int16
206                | Self::Int32
207                | Self::Int64
208                | Self::Int128
209                | Self::Float32
210                | Self::Float64
211                | Self::Nat
212                | Self::Nat8
213                | Self::Nat16
214                | Self::Nat32
215                | Self::Nat64
216                | Self::Nat128
217                | Self::Timestamp
218        )
219    }
220
221    // both Ord and PartialOrd
222    #[must_use]
223    pub const fn supports_ord(self) -> bool {
224        primitive_scalar_kind(self).supports_ordering()
225    }
226
227    //
228    // grouped helpers
229    //
230
231    #[must_use]
232    pub const fn is_decimal(self) -> bool {
233        matches!(self, Self::Decimal)
234    }
235
236    // is_numeric
237    // Includes ints, floats, and Decimal.
238    #[must_use]
239    pub const fn is_numeric(self) -> bool {
240        self.is_int() || self.is_float() || self.is_decimal()
241    }
242
243    #[must_use]
244    pub const fn is_float(self) -> bool {
245        matches!(self, Self::Float32 | Self::Float64)
246    }
247
248    #[must_use]
249    pub const fn is_signed_int(self) -> bool {
250        matches!(
251            self,
252            Self::Int | Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 | Self::Int128
253        )
254    }
255
256    #[must_use]
257    pub const fn is_unsigned_int(self) -> bool {
258        matches!(
259            self,
260            Self::Nat | Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 | Self::Nat128
261        )
262    }
263
264    #[must_use]
265    pub const fn is_int(self) -> bool {
266        self.is_signed_int() || self.is_unsigned_int()
267    }
268
269    #[must_use]
270    pub fn as_type(self) -> TokenStream {
271        let ident = format_ident!("{self:?}");
272
273        quote!(::icydb::types::#ident)
274    }
275}
276
277impl FromMeta for Primitive {
278    fn from_string(s: &str) -> Result<Self, darling::Error> {
279        s.parse::<Self>()
280            .map_err(|_| darling::Error::unknown_value(s))
281    }
282}
283
284impl ToTokens for Primitive {
285    fn to_tokens(&self, tokens: &mut TokenStream) {
286        let ident = format_ident!("{self:?}");
287
288        tokens.extend(quote!(::icydb::schema::types::Primitive::#ident));
289    }
290}