Skip to main content

icydb_schema/
scalar.rs

1//! Canonical scalar-kind identities and intrinsic capability metadata.
2//!
3//! Responsibility: canonical scalar-kind identities and intrinsic capability
4//! facts used by the public proposal contract and consumed by runtime policy.
5//! Does not own: query coercion decisions, storage encoding, or accepted-schema
6//! validation.
7//! Boundary: exposes compact scalar-kind capability descriptors and registry order.
8
9use candid::CandidType;
10use serde::{Deserialize, Serialize};
11
12///
13/// ScalarKind
14///
15/// Canonical scalar kind used for shared capability metadata.
16///
17
18#[derive(
19    CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,
20)]
21pub enum ScalarKind {
22    Account,
23    Blob,
24    Bool,
25    Date,
26    Decimal,
27    Duration,
28    Enum,
29    Float32,
30    Float64,
31    Int,
32    Int128,
33    IntBig,
34    Principal,
35    Subaccount,
36    Text,
37    Timestamp,
38    Nat,
39    Nat128,
40    NatBig,
41    U256,
42    Ulid,
43    Unit,
44}
45
46impl ScalarKind {
47    /// Return the full metadata descriptor for one scalar kind.
48    #[must_use]
49    pub const fn metadata(self) -> ScalarMetadata {
50        scalar_kind_registry!(metadata_from_registry, self)
51    }
52
53    /// Return coercion routing family for this scalar kind.
54    #[must_use]
55    pub const fn coercion_family(self) -> ScalarCoercionFamily {
56        self.metadata().family
57    }
58
59    /// Return whether this scalar participates in numeric-valued classification.
60    #[must_use]
61    pub const fn is_numeric_value(self) -> bool {
62        self.metadata().is_numeric_value
63    }
64
65    /// Return whether this scalar supports numeric widening coercion.
66    #[must_use]
67    pub const fn supports_numeric_coercion(self) -> bool {
68        self.metadata().supports_numeric_coercion
69    }
70
71    /// Return whether this scalar supports arithmetic trait derivation.
72    #[must_use]
73    pub const fn supports_arithmetic(self) -> bool {
74        self.metadata().supports_arithmetic
75    }
76
77    /// Return whether this scalar supports equality predicates.
78    #[must_use]
79    pub const fn supports_equality(self) -> bool {
80        self.metadata().supports_equality
81    }
82
83    /// Return whether this scalar supports ordering predicates.
84    #[must_use]
85    pub const fn supports_ordering(self) -> bool {
86        self.metadata().supports_ordering
87    }
88
89    /// Return whether this scalar is keyable at query/schema level.
90    #[must_use]
91    pub const fn is_keyable(self) -> bool {
92        self.metadata().is_keyable
93    }
94
95    /// Return whether this scalar can be encoded as a storage key.
96    #[must_use]
97    pub const fn is_primary_key_component_encodable(self) -> bool {
98        self.metadata().is_primary_key_component_encodable
99    }
100}
101
102///
103/// ScalarMetadata
104///
105/// Capability metadata shared across schema/core layers.
106///
107
108#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
109#[expect(clippy::struct_excessive_bools)]
110pub struct ScalarMetadata {
111    family: ScalarCoercionFamily,
112    is_numeric_value: bool,
113    supports_numeric_coercion: bool,
114    supports_arithmetic: bool,
115    supports_equality: bool,
116    supports_ordering: bool,
117    is_keyable: bool,
118    is_primary_key_component_encodable: bool,
119}
120
121impl ScalarMetadata {
122    /// Return coercion routing family for this scalar metadata entry.
123    #[must_use]
124    pub const fn family(self) -> ScalarCoercionFamily {
125        self.family
126    }
127
128    /// Return whether this scalar participates in numeric-valued classification.
129    #[must_use]
130    pub const fn is_numeric_value(self) -> bool {
131        self.is_numeric_value
132    }
133
134    /// Return whether this scalar supports numeric widening coercion.
135    #[must_use]
136    pub const fn supports_numeric_coercion(self) -> bool {
137        self.supports_numeric_coercion
138    }
139
140    /// Return whether this scalar supports arithmetic trait derivation.
141    #[must_use]
142    pub const fn supports_arithmetic(self) -> bool {
143        self.supports_arithmetic
144    }
145
146    /// Return whether this scalar supports equality predicates.
147    #[must_use]
148    pub const fn supports_equality(self) -> bool {
149        self.supports_equality
150    }
151
152    /// Return whether this scalar supports ordering predicates.
153    #[must_use]
154    pub const fn supports_ordering(self) -> bool {
155        self.supports_ordering
156    }
157
158    /// Return whether this scalar is keyable at query/schema level.
159    #[must_use]
160    pub const fn is_keyable(self) -> bool {
161        self.is_keyable
162    }
163
164    /// Return whether this scalar can be encoded as a storage key.
165    #[must_use]
166    pub const fn is_primary_key_component_encodable(self) -> bool {
167        self.is_primary_key_component_encodable
168    }
169}
170
171///
172/// ScalarCoercionFamily
173///
174/// Coarse intrinsic family used by downstream coercion and validation policy.
175///
176
177#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
178pub enum ScalarCoercionFamily {
179    Numeric,
180    Textual,
181    Identifier,
182    Enum,
183    Blob,
184    Bool,
185    Unit,
186}
187
188/// Ordered list of all scalar kinds in registry order.
189pub const ALL_SCALAR_KINDS: [ScalarKind; 22] = scalar_kind_registry!(all_kinds_from_registry);
190
191#[cfg(test)]
192mod tests {
193    use super::{ALL_SCALAR_KINDS, ScalarKind};
194    use std::collections::HashSet;
195
196    const EXPECTED_SCALAR_KINDS: [ScalarKind; 22] = [
197        ScalarKind::Account,
198        ScalarKind::Blob,
199        ScalarKind::Bool,
200        ScalarKind::Date,
201        ScalarKind::Decimal,
202        ScalarKind::Duration,
203        ScalarKind::Enum,
204        ScalarKind::Float32,
205        ScalarKind::Float64,
206        ScalarKind::Int,
207        ScalarKind::Int128,
208        ScalarKind::IntBig,
209        ScalarKind::Principal,
210        ScalarKind::Subaccount,
211        ScalarKind::Text,
212        ScalarKind::Timestamp,
213        ScalarKind::Nat,
214        ScalarKind::Nat128,
215        ScalarKind::NatBig,
216        ScalarKind::U256,
217        ScalarKind::Ulid,
218        ScalarKind::Unit,
219    ];
220
221    #[test]
222    fn all_scalar_kinds_has_expected_length_and_order() {
223        assert_eq!(ALL_SCALAR_KINDS, EXPECTED_SCALAR_KINDS);
224    }
225
226    #[test]
227    fn all_scalar_kinds_is_unique() {
228        let unique = ALL_SCALAR_KINDS.into_iter().collect::<HashSet<_>>();
229
230        assert_eq!(unique.len(), ALL_SCALAR_KINDS.len());
231    }
232
233    #[test]
234    fn all_scalar_kind_variants_are_audited() {
235        for kind in EXPECTED_SCALAR_KINDS {
236            assert_variant_is_known(kind);
237        }
238    }
239
240    #[test]
241    fn all_scalar_kinds_metadata_is_available() {
242        for kind in ALL_SCALAR_KINDS {
243            let metadata = kind.metadata();
244
245            assert_eq!(kind.coercion_family(), metadata.family());
246            assert_eq!(kind.is_numeric_value(), metadata.is_numeric_value());
247            assert_eq!(
248                kind.supports_numeric_coercion(),
249                metadata.supports_numeric_coercion(),
250            );
251            assert_eq!(kind.supports_arithmetic(), metadata.supports_arithmetic());
252            assert_eq!(kind.supports_equality(), metadata.supports_equality());
253            assert_eq!(kind.supports_ordering(), metadata.supports_ordering());
254            assert_eq!(kind.is_keyable(), metadata.is_keyable());
255            assert_eq!(
256                kind.is_primary_key_component_encodable(),
257                metadata.is_primary_key_component_encodable(),
258            );
259        }
260    }
261
262    #[test]
263    fn enum_capabilities_are_equality_only() {
264        assert!(ScalarKind::Enum.supports_equality());
265        assert!(!ScalarKind::Enum.supports_ordering());
266        assert!(!ScalarKind::Enum.is_keyable());
267    }
268
269    #[test]
270    fn unit_capabilities_include_trivial_total_order() {
271        let metadata = ScalarKind::Unit.metadata();
272
273        assert!(metadata.supports_equality());
274        assert!(metadata.supports_ordering());
275        assert!(metadata.is_keyable());
276        assert!(metadata.is_primary_key_component_encodable());
277        assert!(!metadata.supports_numeric_coercion());
278        assert!(!metadata.supports_arithmetic());
279    }
280
281    fn assert_variant_is_known(kind: ScalarKind) {
282        match kind {
283            ScalarKind::Account
284            | ScalarKind::Blob
285            | ScalarKind::Bool
286            | ScalarKind::Date
287            | ScalarKind::Decimal
288            | ScalarKind::Duration
289            | ScalarKind::Enum
290            | ScalarKind::Float32
291            | ScalarKind::Float64
292            | ScalarKind::Int
293            | ScalarKind::Int128
294            | ScalarKind::IntBig
295            | ScalarKind::Principal
296            | ScalarKind::Subaccount
297            | ScalarKind::Text
298            | ScalarKind::Timestamp
299            | ScalarKind::Nat
300            | ScalarKind::Nat128
301            | ScalarKind::NatBig
302            | ScalarKind::U256
303            | ScalarKind::Ulid
304            | ScalarKind::Unit => {}
305        }
306    }
307}