1use serde::{Deserialize, Serialize};
10
11#[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 #[must_use]
44 pub const fn metadata(self) -> ScalarMetadata {
45 scalar_kind_registry!(metadata_from_registry, self)
46 }
47
48 #[must_use]
50 pub const fn coercion_family(self) -> ScalarCoercionFamily {
51 self.metadata().family
52 }
53
54 #[must_use]
56 pub const fn is_numeric_value(self) -> bool {
57 self.metadata().is_numeric_value
58 }
59
60 #[must_use]
62 pub const fn supports_numeric_coercion(self) -> bool {
63 self.metadata().supports_numeric_coercion
64 }
65
66 #[must_use]
68 pub const fn supports_arithmetic(self) -> bool {
69 self.metadata().supports_arithmetic
70 }
71
72 #[must_use]
74 pub const fn supports_equality(self) -> bool {
75 self.metadata().supports_equality
76 }
77
78 #[must_use]
80 pub const fn supports_ordering(self) -> bool {
81 self.metadata().supports_ordering
82 }
83
84 #[must_use]
86 pub const fn is_keyable(self) -> bool {
87 self.metadata().is_keyable
88 }
89
90 #[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#[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 #[must_use]
119 pub const fn family(self) -> ScalarCoercionFamily {
120 self.family
121 }
122
123 #[must_use]
125 pub const fn is_numeric_value(self) -> bool {
126 self.is_numeric_value
127 }
128
129 #[must_use]
131 pub const fn supports_numeric_coercion(self) -> bool {
132 self.supports_numeric_coercion
133 }
134
135 #[must_use]
137 pub const fn supports_arithmetic(self) -> bool {
138 self.supports_arithmetic
139 }
140
141 #[must_use]
143 pub const fn supports_equality(self) -> bool {
144 self.supports_equality
145 }
146
147 #[must_use]
149 pub const fn supports_ordering(self) -> bool {
150 self.supports_ordering
151 }
152
153 #[must_use]
155 pub const fn is_keyable(self) -> bool {
156 self.is_keyable
157 }
158
159 #[must_use]
161 pub const fn is_primary_key_component_encodable(self) -> bool {
162 self.is_primary_key_component_encodable
163 }
164}
165
166#[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
183pub 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 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}