1use candid::CandidType;
10use serde::{Deserialize, Serialize};
11
12#[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 #[must_use]
49 pub const fn metadata(self) -> ScalarMetadata {
50 scalar_kind_registry!(metadata_from_registry, self)
51 }
52
53 #[must_use]
55 pub const fn coercion_family(self) -> ScalarCoercionFamily {
56 self.metadata().family
57 }
58
59 #[must_use]
61 pub const fn is_numeric_value(self) -> bool {
62 self.metadata().is_numeric_value
63 }
64
65 #[must_use]
67 pub const fn supports_numeric_coercion(self) -> bool {
68 self.metadata().supports_numeric_coercion
69 }
70
71 #[must_use]
73 pub const fn supports_arithmetic(self) -> bool {
74 self.metadata().supports_arithmetic
75 }
76
77 #[must_use]
79 pub const fn supports_equality(self) -> bool {
80 self.metadata().supports_equality
81 }
82
83 #[must_use]
85 pub const fn supports_ordering(self) -> bool {
86 self.metadata().supports_ordering
87 }
88
89 #[must_use]
91 pub const fn is_keyable(self) -> bool {
92 self.metadata().is_keyable
93 }
94
95 #[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#[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 #[must_use]
124 pub const fn family(self) -> ScalarCoercionFamily {
125 self.family
126 }
127
128 #[must_use]
130 pub const fn is_numeric_value(self) -> bool {
131 self.is_numeric_value
132 }
133
134 #[must_use]
136 pub const fn supports_numeric_coercion(self) -> bool {
137 self.supports_numeric_coercion
138 }
139
140 #[must_use]
142 pub const fn supports_arithmetic(self) -> bool {
143 self.supports_arithmetic
144 }
145
146 #[must_use]
148 pub const fn supports_equality(self) -> bool {
149 self.supports_equality
150 }
151
152 #[must_use]
154 pub const fn supports_ordering(self) -> bool {
155 self.supports_ordering
156 }
157
158 #[must_use]
160 pub const fn is_keyable(self) -> bool {
161 self.is_keyable
162 }
163
164 #[must_use]
166 pub const fn is_primary_key_component_encodable(self) -> bool {
167 self.is_primary_key_component_encodable
168 }
169}
170
171#[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
188pub 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 documentation_primary_keys_match_compiled_capabilities() {
235 let guide = std::fs::read_to_string(concat!(
238 env!("CARGO_MANIFEST_DIR"),
239 "/../../docs/guides/schema-authoring.md"
240 ))
241 .expect("repository schema authoring guide");
242 let section = guide
243 .split_once("<!-- icydb-primary-key-primitives:start -->")
244 .expect("primary-key data start")
245 .1
246 .split_once("<!-- icydb-primary-key-primitives:end -->")
247 .expect("primary-key data end")
248 .0;
249 let documented = section
250 .split('`')
251 .skip(1)
252 .step_by(2)
253 .collect::<HashSet<_>>();
254 macro_rules! admitted_primitives {
255 (@entries $(($primitive:ident, $kind:ident)),* $(,)?) => {
256 [$( (stringify!($primitive), ScalarKind::$kind) ),*]
257 .into_iter()
258 .filter(|(_, kind)| kind.is_primary_key_component_encodable())
259 .map(|(name, _)| name)
260 .collect::<HashSet<_>>()
261 };
262 }
263 let expected = authoring_primitive_registry!(admitted_primitives);
264 assert_eq!(documented, expected);
265 }
266
267 #[test]
268 fn all_scalar_kind_variants_are_audited() {
269 for kind in EXPECTED_SCALAR_KINDS {
270 assert_variant_is_known(kind);
271 }
272 }
273
274 #[test]
275 fn all_scalar_kinds_metadata_is_available() {
276 for kind in ALL_SCALAR_KINDS {
277 let metadata = kind.metadata();
278
279 assert_eq!(kind.coercion_family(), metadata.family());
280 assert_eq!(kind.is_numeric_value(), metadata.is_numeric_value());
281 assert_eq!(
282 kind.supports_numeric_coercion(),
283 metadata.supports_numeric_coercion(),
284 );
285 assert_eq!(kind.supports_arithmetic(), metadata.supports_arithmetic());
286 assert_eq!(kind.supports_equality(), metadata.supports_equality());
287 assert_eq!(kind.supports_ordering(), metadata.supports_ordering());
288 assert_eq!(kind.is_keyable(), metadata.is_keyable());
289 assert_eq!(
290 kind.is_primary_key_component_encodable(),
291 metadata.is_primary_key_component_encodable(),
292 );
293 }
294 }
295
296 #[test]
297 fn enum_capabilities_are_equality_only() {
298 assert!(ScalarKind::Enum.supports_equality());
299 assert!(!ScalarKind::Enum.supports_ordering());
300 assert!(!ScalarKind::Enum.is_keyable());
301 }
302
303 #[test]
304 fn unit_capabilities_include_trivial_total_order() {
305 let metadata = ScalarKind::Unit.metadata();
306
307 assert!(metadata.supports_equality());
308 assert!(metadata.supports_ordering());
309 assert!(metadata.is_keyable());
310 assert!(metadata.is_primary_key_component_encodable());
311 assert!(!metadata.supports_numeric_coercion());
312 assert!(!metadata.supports_arithmetic());
313 }
314
315 fn assert_variant_is_known(kind: ScalarKind) {
316 match kind {
317 ScalarKind::Account
318 | ScalarKind::Blob
319 | ScalarKind::Bool
320 | ScalarKind::Date
321 | ScalarKind::Decimal
322 | ScalarKind::Duration
323 | ScalarKind::Enum
324 | ScalarKind::Float32
325 | ScalarKind::Float64
326 | ScalarKind::Int
327 | ScalarKind::Int128
328 | ScalarKind::IntBig
329 | ScalarKind::Principal
330 | ScalarKind::Subaccount
331 | ScalarKind::Text
332 | ScalarKind::Timestamp
333 | ScalarKind::Nat
334 | ScalarKind::Nat128
335 | ScalarKind::NatBig
336 | ScalarKind::U256
337 | ScalarKind::Ulid
338 | ScalarKind::Unit => {}
339 }
340 }
341}