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