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