Skip to main content

icydb_schema/
types.rs

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