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// 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(
19    CandidType, Clone, Copy, Default, Debug, Deserialize, Display, Eq, FromStr, PartialEq, Serialize,
20)]
21pub enum Cardinality {
22    #[default]
23    One,
24    Opt,
25    Many,
26}
27
28impl FromMeta for Cardinality {
29    fn from_string(s: &str) -> Result<Self, darling::Error> {
30        s.parse::<Self>()
31            .map_err(|_| darling::Error::unknown_value(s))
32    }
33}
34
35impl ToTokens for Cardinality {
36    fn to_tokens(&self, tokens: &mut TokenStream) {
37        let ident = format_ident!("{self}");
38
39        tokens.extend(quote!(::icydb::schema::types::Cardinality::#ident));
40    }
41}
42
43//
44// Primitive
45//
46// Scalar primitive catalog used by schema macros and generated runtime wiring.
47// This enum is the canonical source for primitive capability checks
48// (ordering, arithmetic, casting, key-encoding, and hashing support).
49//
50
51#[derive(
52    CandidType, Clone, Copy, Debug, Deserialize, Display, Eq, PartialEq, FromStr, Serialize,
53)]
54#[remain::sorted]
55pub enum Primitive {
56    Account,
57    Blob,
58    Bool,
59    Date,
60    Decimal,
61    Duration,
62    Float32,
63    Float64,
64    Int,
65    Int8,
66    Int16,
67    Int32,
68    Int64,
69    Int128,
70    Nat,
71    Nat8,
72    Nat16,
73    Nat32,
74    Nat64,
75    Nat128,
76    Principal,
77    Subaccount,
78    Text,
79    Timestamp,
80    Ulid,
81    Unit,
82}
83
84const fn primitive_scalar_kind(primitive: Primitive) -> ScalarKind {
85    match primitive {
86        Primitive::Account => ScalarKind::Account,
87        Primitive::Blob => ScalarKind::Blob,
88        Primitive::Bool => ScalarKind::Bool,
89        Primitive::Date => ScalarKind::Date,
90        Primitive::Decimal => ScalarKind::Decimal,
91        Primitive::Duration => ScalarKind::Duration,
92        Primitive::Float32 => ScalarKind::Float32,
93        Primitive::Float64 => ScalarKind::Float64,
94        Primitive::Int => ScalarKind::IntBig,
95        Primitive::Int8 | Primitive::Int16 | Primitive::Int32 | Primitive::Int64 => ScalarKind::Int,
96        Primitive::Int128 => ScalarKind::Int128,
97        Primitive::Nat => ScalarKind::UintBig,
98        Primitive::Nat8 | Primitive::Nat16 | Primitive::Nat32 | Primitive::Nat64 => {
99            ScalarKind::Uint
100        }
101        Primitive::Nat128 => ScalarKind::Uint128,
102        Primitive::Principal => ScalarKind::Principal,
103        Primitive::Subaccount => ScalarKind::Subaccount,
104        Primitive::Text => ScalarKind::Text,
105        Primitive::Timestamp => ScalarKind::Timestamp,
106        Primitive::Ulid => ScalarKind::Ulid,
107        Primitive::Unit => ScalarKind::Unit,
108    }
109}
110
111impl Primitive {
112    #[must_use]
113    pub const fn supports_arithmetic(self) -> bool {
114        primitive_scalar_kind(self).supports_arithmetic()
115    }
116
117    #[must_use]
118    pub const fn is_storage_key_encodable(self) -> bool {
119        primitive_scalar_kind(self).is_storage_key_encodable()
120    }
121
122    #[must_use]
123    pub const fn supports_remainder(self) -> bool {
124        matches!(
125            self,
126            Self::Decimal
127                | Self::Int8
128                | Self::Int16
129                | Self::Int32
130                | Self::Int64
131                | Self::Int128
132                | Self::Nat8
133                | Self::Nat16
134                | Self::Nat32
135                | Self::Nat64
136                | Self::Nat128
137        )
138    }
139
140    #[must_use]
141    pub const fn supports_copy(self) -> bool {
142        !matches!(self, Self::Blob | Self::Int | Self::Nat | Self::Text)
143    }
144
145    #[must_use]
146    pub const fn supports_hash(self) -> bool {
147        !matches!(self, Self::Blob | Self::Unit)
148    }
149
150    // NumericValue can fallibly route all numeric-like primitives through Decimal.
151    #[must_use]
152    pub const fn supports_numeric_value(self) -> bool {
153        matches!(
154            self,
155            Self::Date
156                | Self::Decimal
157                | Self::Duration
158                | Self::Int
159                | Self::Int8
160                | Self::Int16
161                | Self::Int32
162                | Self::Int64
163                | Self::Int128
164                | Self::Float32
165                | Self::Float64
166                | Self::Nat
167                | Self::Nat8
168                | Self::Nat16
169                | Self::Nat32
170                | Self::Nat64
171                | Self::Nat128
172                | Self::Timestamp
173        )
174    }
175
176    // both Ord and PartialOrd
177    #[must_use]
178    pub const fn supports_ord(self) -> bool {
179        primitive_scalar_kind(self).supports_ordering()
180    }
181
182    //
183    // grouped helpers
184    //
185
186    #[must_use]
187    pub const fn is_decimal(self) -> bool {
188        matches!(self, Self::Decimal)
189    }
190
191    // is_numeric
192    // Includes ints, floats, and Decimal.
193    #[must_use]
194    pub const fn is_numeric(self) -> bool {
195        self.is_int() || self.is_float() || self.is_decimal()
196    }
197
198    #[must_use]
199    pub const fn is_float(self) -> bool {
200        matches!(self, Self::Float32 | Self::Float64)
201    }
202
203    #[must_use]
204    pub const fn is_signed_int(self) -> bool {
205        matches!(
206            self,
207            Self::Int | Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 | Self::Int128
208        )
209    }
210
211    #[must_use]
212    pub const fn is_unsigned_int(self) -> bool {
213        matches!(
214            self,
215            Self::Nat | Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 | Self::Nat128
216        )
217    }
218
219    #[must_use]
220    pub const fn is_int(self) -> bool {
221        self.is_signed_int() || self.is_unsigned_int()
222    }
223
224    #[must_use]
225    pub fn as_type(self) -> TokenStream {
226        let ident = format_ident!("{self}");
227
228        quote!(::icydb::types::#ident)
229    }
230}
231
232impl FromMeta for Primitive {
233    fn from_string(s: &str) -> Result<Self, darling::Error> {
234        s.parse::<Self>()
235            .map_err(|_| darling::Error::unknown_value(s))
236    }
237}
238
239impl ToTokens for Primitive {
240    fn to_tokens(&self, tokens: &mut TokenStream) {
241        let ident = format_ident!("{self}");
242
243        tokens.extend(quote!(::icydb::schema::types::Primitive::#ident));
244    }
245}