1mod family;
2
3#[cfg(test)]
4mod tests;
5
6use crate::{
7 db::store::StorageKey,
8 prelude::*,
9 traits::{EnumValue, FieldValue, NumFromPrimitive},
10 types::*,
11};
12use candid::CandidType;
13use serde::{Deserialize, Serialize};
14use std::cmp::Ordering;
15
16pub use family::{ValueFamily, ValueFamilyExt};
18
19const F64_SAFE_I64: i64 = 1i64 << 53;
24const F64_SAFE_U64: u64 = 1u64 << 53;
25const F64_SAFE_I128: i128 = 1i128 << 53;
26const F64_SAFE_U128: u128 = 1u128 << 53;
27
28enum NumericRepr {
33 Decimal(Decimal),
34 F64(f64),
35 None,
36}
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum TextMode {
44 Cs, Ci, }
47
48#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub enum Value {
59 Account(Account),
60 Blob(Vec<u8>),
61 Bool(bool),
62 Date(Date),
63 Decimal(Decimal),
64 Duration(Duration),
65 Enum(ValueEnum),
66 E8s(E8s),
67 E18s(E18s),
68 Float32(Float32),
69 Float64(Float64),
70 Int(i64),
71 Int128(Int128),
72 IntBig(Int),
73 List(Vec<Self>),
77 None,
78 Principal(Principal),
79 Subaccount(Subaccount),
80 Text(String),
81 Timestamp(Timestamp),
82 Uint(u64),
83 Uint128(Nat128),
84 UintBig(Nat),
85 Ulid(Ulid),
86 Unit,
87 Unsupported,
88}
89
90macro_rules! value_is_numeric_from_registry {
92 ( @args $value:expr; @entries $( ($scalar:ident, $family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr) ),* $(,)? ) => {
93 match $value {
94 $( $value_pat => $is_numeric, )*
95 _ => false,
96 }
97 };
98}
99
100macro_rules! value_storage_key_case {
101 ( $value:expr, Unit, true ) => {
102 if let Value::Unit = $value {
103 Some(StorageKey::Unit)
104 } else {
105 None
106 }
107 };
108 ( $value:expr, $scalar:ident, true ) => {
109 if let Value::$scalar(v) = $value {
110 Some(StorageKey::$scalar(*v))
111 } else {
112 None
113 }
114 };
115 ( $value:expr, $scalar:ident, false ) => {
116 None
117 };
118}
119
120macro_rules! value_storage_key_from_registry {
121 ( @args $value:expr; @entries $( ($scalar:ident, $family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:tt) ),* $(,)? ) => {
122 {
123 let mut key = None;
124 $(
125 match key {
126 Some(_) => {}
127 None => {
128 key = value_storage_key_case!($value, $scalar, $is_keyable);
129 }
130 }
131 )*
132 key
133 }
134 };
135}
136
137impl Value {
138 pub fn from_slice<T>(items: &[T]) -> Self
147 where
148 T: Into<Self> + Clone,
149 {
150 Self::List(items.iter().cloned().map(Into::into).collect())
151 }
152
153 pub fn from_list<T>(items: Vec<T>) -> Self
157 where
158 T: Into<Self>,
159 {
160 Self::List(items.into_iter().map(Into::into).collect())
161 }
162
163 pub fn from_enum<E: EnumValue>(value: E) -> Self {
165 Self::Enum(value.to_value_enum())
166 }
167
168 #[must_use]
170 pub fn enum_strict<E: Path>(variant: &str) -> Self {
171 Self::Enum(ValueEnum::strict::<E>(variant))
172 }
173
174 #[must_use]
181 pub const fn is_numeric(&self) -> bool {
182 scalar_registry!(value_is_numeric_from_registry, self)
183 }
184
185 #[must_use]
187 pub const fn is_text(&self) -> bool {
188 matches!(self, Self::Text(_))
189 }
190
191 #[must_use]
193 pub const fn is_unit(&self) -> bool {
194 matches!(self, Self::Unit)
195 }
196
197 #[must_use]
198 pub const fn is_scalar(&self) -> bool {
199 match self {
200 Self::List(_) | Self::Unit => false,
202 _ => true,
203 }
204 }
205
206 fn numeric_repr(&self) -> NumericRepr {
207 if let Some(d) = self.to_decimal() {
208 return NumericRepr::Decimal(d);
209 }
210 if let Some(f) = self.to_f64_lossless() {
211 return NumericRepr::F64(f);
212 }
213 NumericRepr::None
214 }
215
216 #[must_use]
225 pub const fn as_storage_key(&self) -> Option<StorageKey> {
226 scalar_registry!(value_storage_key_from_registry, self)
227 }
228
229 #[must_use]
230 pub const fn as_text(&self) -> Option<&str> {
231 if let Self::Text(s) = self {
232 Some(s.as_str())
233 } else {
234 None
235 }
236 }
237
238 #[must_use]
239 pub const fn as_list(&self) -> Option<&[Self]> {
240 if let Self::List(xs) = self {
241 Some(xs.as_slice())
242 } else {
243 None
244 }
245 }
246
247 fn to_decimal(&self) -> Option<Decimal> {
248 match self {
249 Self::Decimal(d) => Some(*d),
250 Self::Duration(d) => Decimal::from_u64(d.get()),
251 Self::E8s(v) => Some(v.to_decimal()),
252 Self::E18s(v) => v.to_decimal(),
253 Self::Float64(f) => Decimal::from_f64(f.get()),
254 Self::Float32(f) => Decimal::from_f32(f.get()),
255 Self::Int(i) => Decimal::from_i64(*i),
256 Self::Int128(i) => Decimal::from_i128(i.get()),
257 Self::IntBig(i) => i.to_i128().and_then(Decimal::from_i128),
258 Self::Timestamp(t) => Decimal::from_u64(t.get()),
259 Self::Uint(u) => Decimal::from_u64(*u),
260 Self::Uint128(u) => Decimal::from_u128(u.get()),
261 Self::UintBig(u) => u.to_u128().and_then(Decimal::from_u128),
262
263 _ => None,
264 }
265 }
266
267 #[allow(clippy::cast_precision_loss)]
269 fn to_f64_lossless(&self) -> Option<f64> {
270 match self {
271 Self::Duration(d) if d.get() <= F64_SAFE_U64 => Some(d.get() as f64),
272 Self::Float64(f) => Some(f.get()),
273 Self::Float32(f) => Some(f64::from(f.get())),
274 Self::Int(i) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(i) => Some(*i as f64),
275 Self::Int128(i) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(&i.get()) => {
276 Some(i.get() as f64)
277 }
278 Self::IntBig(i) => i.to_i128().and_then(|v| {
279 (-F64_SAFE_I128..=F64_SAFE_I128)
280 .contains(&v)
281 .then_some(v as f64)
282 }),
283 Self::Timestamp(t) if t.get() <= F64_SAFE_U64 => Some(t.get() as f64),
284 Self::Uint(u) if *u <= F64_SAFE_U64 => Some(*u as f64),
285 Self::Uint128(u) if u.get() <= F64_SAFE_U128 => Some(u.get() as f64),
286 Self::UintBig(u) => u
287 .to_u128()
288 .and_then(|v| (v <= F64_SAFE_U128).then_some(v as f64)),
289
290 _ => None,
291 }
292 }
293
294 #[must_use]
296 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
297 match (self.numeric_repr(), other.numeric_repr()) {
298 (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
299 (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
300 _ => None,
301 }
302 }
303
304 fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
309 if s.is_ascii() {
310 return std::borrow::Cow::Owned(s.to_ascii_lowercase());
311 }
312 std::borrow::Cow::Owned(s.to_lowercase())
315 }
316
317 fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
318 match mode {
319 TextMode::Cs => std::borrow::Cow::Borrowed(s),
320 TextMode::Ci => Self::fold_ci(s),
321 }
322 }
323
324 fn text_op(
325 &self,
326 other: &Self,
327 mode: TextMode,
328 f: impl Fn(&str, &str) -> bool,
329 ) -> Option<bool> {
330 let (a, b) = (self.as_text()?, other.as_text()?);
331 let a = Self::text_with_mode(a, mode);
332 let b = Self::text_with_mode(b, mode);
333 Some(f(&a, &b))
334 }
335
336 fn ci_key(&self) -> Option<String> {
337 match self {
338 Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
339 Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
340 Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
341 Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
342 _ => None,
343 }
344 }
345
346 fn eq_ci(a: &Self, b: &Self) -> bool {
347 if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
348 return ak == bk;
349 }
350
351 a == b
352 }
353
354 fn normalize_list_ref(v: &Self) -> Vec<&Self> {
355 match v {
356 Self::List(vs) => vs.iter().collect(),
357 v => vec![v],
358 }
359 }
360
361 fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
362 where
363 F: Fn(&Self, &Self) -> bool,
364 {
365 self.as_list()
366 .map(|items| items.iter().any(|v| eq(v, needle)))
367 }
368
369 #[allow(clippy::unnecessary_wraps)]
370 fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
371 where
372 F: Fn(&Self, &Self) -> bool,
373 {
374 let needles = Self::normalize_list_ref(needles);
375 match self {
376 Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
377 scalar => Some(needles.iter().any(|n| eq(scalar, n))),
378 }
379 }
380
381 #[allow(clippy::unnecessary_wraps)]
382 fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
383 where
384 F: Fn(&Self, &Self) -> bool,
385 {
386 let needles = Self::normalize_list_ref(needles);
387 match self {
388 Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
389 scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
390 }
391 }
392
393 fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
394 where
395 F: Fn(&Self, &Self) -> bool,
396 {
397 if let Self::List(items) = haystack {
398 Some(items.iter().any(|h| eq(h, self)))
399 } else {
400 None
401 }
402 }
403
404 #[must_use]
405 pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
407 self.text_op(other, mode, |a, b| a == b)
408 }
409
410 #[must_use]
411 pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
413 self.text_op(needle, mode, |a, b| a.contains(b))
414 }
415
416 #[must_use]
417 pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
419 self.text_op(needle, mode, |a, b| a.starts_with(b))
420 }
421
422 #[must_use]
423 pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
425 self.text_op(needle, mode, |a, b| a.ends_with(b))
426 }
427
428 #[must_use]
433 pub const fn is_empty(&self) -> Option<bool> {
434 match self {
435 Self::List(xs) => Some(xs.is_empty()),
436 Self::Text(s) => Some(s.is_empty()),
437 Self::Blob(b) => Some(b.is_empty()),
438
439 Self::None => Some(true),
441
442 _ => None,
443 }
444 }
445
446 #[must_use]
447 pub fn is_not_empty(&self) -> Option<bool> {
449 self.is_empty().map(|b| !b)
450 }
451
452 #[must_use]
457 pub fn contains(&self, needle: &Self) -> Option<bool> {
459 self.contains_by(needle, |a, b| a == b)
460 }
461
462 #[must_use]
463 pub fn contains_any(&self, needles: &Self) -> Option<bool> {
465 self.contains_any_by(needles, |a, b| a == b)
466 }
467
468 #[must_use]
469 pub fn contains_all(&self, needles: &Self) -> Option<bool> {
471 self.contains_all_by(needles, |a, b| a == b)
472 }
473
474 #[must_use]
475 pub fn in_list(&self, haystack: &Self) -> Option<bool> {
477 self.in_list_by(haystack, |a, b| a == b)
478 }
479
480 #[must_use]
481 pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
483 match self {
484 Self::List(_) => self.contains_by(needle, Self::eq_ci),
485 _ => Some(Self::eq_ci(self, needle)),
486 }
487 }
488
489 #[must_use]
490 pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
492 self.contains_any_by(needles, Self::eq_ci)
493 }
494
495 #[must_use]
496 pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
498 self.contains_all_by(needles, Self::eq_ci)
499 }
500
501 #[must_use]
502 pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
504 self.in_list_by(haystack, Self::eq_ci)
505 }
506}
507
508impl FieldValue for Value {
509 fn to_value(&self) -> Value {
510 self.clone()
511 }
512}
513
514#[macro_export]
515macro_rules! impl_from_for {
516 ( $( $type:ty => $variant:ident ),* $(,)? ) => {
517 $(
518 impl From<$type> for Value {
519 fn from(v: $type) -> Self {
520 Self::$variant(v.into())
521 }
522 }
523 )*
524 };
525}
526
527impl_from_for! {
528 Account => Account,
529 Date => Date,
530 Decimal => Decimal,
531 Duration => Duration,
532 E8s => E8s,
533 E18s => E18s,
534 bool => Bool,
535 i8 => Int,
536 i16 => Int,
537 i32 => Int,
538 i64 => Int,
539 i128 => Int128,
540 Int => IntBig,
541 Principal => Principal,
542 Subaccount => Subaccount,
543 &str => Text,
544 String => Text,
545 Timestamp => Timestamp,
546 u8 => Uint,
547 u16 => Uint,
548 u32 => Uint,
549 u64 => Uint,
550 u128 => Uint128,
551 Nat => UintBig,
552 Ulid => Ulid,
553}
554
555impl ValueFamilyExt for Value {
556 fn family(&self) -> ValueFamily {
557 match self {
558 Self::Date(_)
560 | Self::Decimal(_)
561 | Self::Duration(_)
562 | Self::E8s(_)
563 | Self::E18s(_)
564 | Self::Float32(_)
565 | Self::Float64(_)
566 | Self::Int(_)
567 | Self::Int128(_)
568 | Self::Timestamp(_)
569 | Self::Uint(_)
570 | Self::Uint128(_)
571 | Self::IntBig(_)
572 | Self::UintBig(_) => ValueFamily::Numeric,
573
574 Self::Text(_) => ValueFamily::Textual,
576
577 Self::Ulid(_) | Self::Principal(_) | Self::Account(_) => ValueFamily::Identifier,
579
580 Self::Enum(_) => ValueFamily::Enum,
582
583 Self::List(_) => ValueFamily::Collection,
585
586 Self::Blob(_) | Self::Subaccount(_) => ValueFamily::Blob,
588
589 Self::Bool(_) => ValueFamily::Bool,
591
592 Self::None => ValueFamily::Null,
594 Self::Unit => ValueFamily::Unit,
595
596 Self::Unsupported => ValueFamily::Unsupported,
598 }
599 }
600}
601
602impl<E: EntityIdentity> From<Ref<E>> for Value {
603 fn from(r: Ref<E>) -> Self {
604 r.to_value() }
606}
607
608impl From<Vec<Self>> for Value {
609 fn from(vec: Vec<Self>) -> Self {
610 Self::List(vec)
611 }
612}
613
614impl From<()> for Value {
615 fn from((): ()) -> Self {
616 Self::Unit
617 }
618}
619
620impl PartialOrd for Value {
626 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
627 match (self, other) {
628 (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
629 (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
630 (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
631 (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
632 (Self::E8s(a), Self::E8s(b)) => a.partial_cmp(b),
633 (Self::E18s(a), Self::E18s(b)) => a.partial_cmp(b),
634 (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
635 (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
636 (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
637 (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
638 (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
639 (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
640 (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
641 (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
642 (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
643 (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
644 (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
645 (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
646 (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
647 (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
648
649 _ => None,
651 }
652 }
653}
654
655#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd, Serialize)]
661pub struct ValueEnum {
662 pub variant: String,
663 pub path: Option<String>,
664 pub payload: Option<Box<Value>>,
665}
666
667impl ValueEnum {
668 #[must_use]
669 pub fn new(variant: &str, path: Option<&str>) -> Self {
671 Self {
672 variant: variant.to_string(),
673 path: path.map(ToString::to_string),
674 payload: None,
675 }
676 }
677
678 #[must_use]
679 pub fn strict<E: Path>(variant: &str) -> Self {
681 Self::new(variant, Some(E::PATH))
682 }
683
684 #[must_use]
685 pub fn from_enum<E: EnumValue>(value: E) -> Self {
687 value.to_value_enum()
688 }
689
690 #[must_use]
691 pub fn loose(variant: &str) -> Self {
693 Self::new(variant, None)
694 }
695
696 #[must_use]
697 pub fn with_payload(mut self, payload: Value) -> Self {
699 self.payload = Some(Box::new(payload));
700 self
701 }
702}