1mod family;
2
3#[cfg(test)]
4mod tests;
5
6use crate::{
7 prelude::*,
8 traits::{FieldValue, NumFromPrimitive},
9 types::*,
10};
11use candid::CandidType;
12use num_traits::ToPrimitive;
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>),
74 None,
75 Principal(Principal),
76 Subaccount(Subaccount),
77 Text(String),
78 Timestamp(Timestamp),
79 Uint(u64),
80 Uint128(Nat128),
81 UintBig(Nat),
82 Ulid(Ulid),
83 Unit,
84 Unsupported,
85}
86
87impl Value {
88 pub fn from_list<T: Into<Self> + Clone>(items: &[T]) -> Self {
94 Self::List(items.iter().cloned().map(Into::into).collect())
95 }
96
97 #[must_use]
104 pub const fn is_numeric(&self) -> bool {
105 matches!(
106 self,
107 Self::Decimal(_)
108 | Self::Duration(_)
109 | Self::E8s(_)
110 | Self::E18s(_)
111 | Self::Float32(_)
112 | Self::Float64(_)
113 | Self::Int(_)
114 | Self::Int128(_)
115 | Self::Timestamp(_)
116 | Self::Uint(_)
117 | Self::Uint128(_)
118 )
119 }
120
121 #[must_use]
123 pub const fn is_text(&self) -> bool {
124 matches!(self, Self::Text(_))
125 }
126
127 #[must_use]
129 pub const fn is_unit(&self) -> bool {
130 matches!(self, Self::Unit)
131 }
132
133 #[must_use]
134 pub const fn is_scalar(&self) -> bool {
135 match self {
136 Self::List(_) | Self::Unit => false,
138 _ => true,
139 }
140 }
141
142 fn numeric_repr(&self) -> NumericRepr {
143 if let Some(d) = self.to_decimal() {
144 return NumericRepr::Decimal(d);
145 }
146 if let Some(f) = self.to_f64_lossless() {
147 return NumericRepr::F64(f);
148 }
149 NumericRepr::None
150 }
151
152 #[must_use]
160 pub const fn as_key(&self) -> Option<Key> {
161 match self {
162 Self::Account(v) => Some(Key::Account(*v)),
163 Self::Int(v) => Some(Key::Int(*v)),
164 Self::Uint(v) => Some(Key::Uint(*v)),
165 Self::Principal(v) => Some(Key::Principal(*v)),
166 Self::Subaccount(v) => Some(Key::Subaccount(*v)),
167 Self::Timestamp(v) => Some(Key::Timestamp(*v)),
168 Self::Ulid(v) => Some(Key::Ulid(*v)),
169 Self::Unit => Some(Key::Unit),
170 _ => None,
171 }
172 }
173
174 #[must_use]
175 pub const fn as_text(&self) -> Option<&str> {
176 if let Self::Text(s) = self {
177 Some(s.as_str())
178 } else {
179 None
180 }
181 }
182
183 #[must_use]
184 pub const fn as_list(&self) -> Option<&[Self]> {
185 if let Self::List(xs) = self {
186 Some(xs.as_slice())
187 } else {
188 None
189 }
190 }
191
192 fn to_decimal(&self) -> Option<Decimal> {
193 match self {
194 Self::Decimal(d) => Some(*d),
195 Self::Duration(d) => Decimal::from_u64(d.get()),
196 Self::E8s(v) => Some(v.to_decimal()),
197 Self::E18s(v) => v.to_decimal(),
198 Self::Float64(f) => Decimal::from_f64(f.get()),
199 Self::Float32(f) => Decimal::from_f32(f.get()),
200 Self::Int(i) => Decimal::from_i64(*i),
201 Self::Int128(i) => Decimal::from_i128(i.get()),
202 Self::IntBig(i) => i.0.to_i128().and_then(Decimal::from_i128),
203 Self::Timestamp(t) => Decimal::from_u64(t.get()),
204 Self::Uint(u) => Decimal::from_u64(*u),
205 Self::Uint128(u) => Decimal::from_u128(u.get()),
206 Self::UintBig(u) => u.0.to_u128().and_then(Decimal::from_u128),
207
208 _ => None,
209 }
210 }
211
212 #[allow(clippy::cast_precision_loss)]
214 fn to_f64_lossless(&self) -> Option<f64> {
215 match self {
216 Self::Duration(d) if d.get() <= F64_SAFE_U64 => Some(d.get() as f64),
217 Self::Float64(f) => Some(f.get()),
218 Self::Float32(f) => Some(f64::from(f.get())),
219 Self::Int(i) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(i) => Some(*i as f64),
220 Self::Int128(i) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(&i.get()) => {
221 Some(i.get() as f64)
222 }
223 Self::IntBig(i) => i.0.to_i128().and_then(|v| {
224 (-F64_SAFE_I128..=F64_SAFE_I128)
225 .contains(&v)
226 .then_some(v as f64)
227 }),
228 Self::Timestamp(t) if t.get() <= F64_SAFE_U64 => Some(t.get() as f64),
229 Self::Uint(u) if *u <= F64_SAFE_U64 => Some(*u as f64),
230 Self::Uint128(u) if u.get() <= F64_SAFE_U128 => Some(u.get() as f64),
231 Self::UintBig(u) => {
232 u.0.to_u128()
233 .and_then(|v| (v <= F64_SAFE_U128).then_some(v as f64))
234 }
235
236 _ => None,
237 }
238 }
239
240 #[must_use]
242 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
243 match (self.numeric_repr(), other.numeric_repr()) {
244 (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
245 (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
246 _ => None,
247 }
248 }
249
250 fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
255 if s.is_ascii() {
256 return std::borrow::Cow::Owned(s.to_ascii_lowercase());
257 }
258 std::borrow::Cow::Owned(s.to_lowercase())
261 }
262
263 fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
264 match mode {
265 TextMode::Cs => std::borrow::Cow::Borrowed(s),
266 TextMode::Ci => Self::fold_ci(s),
267 }
268 }
269
270 fn text_op(
271 &self,
272 other: &Self,
273 mode: TextMode,
274 f: impl Fn(&str, &str) -> bool,
275 ) -> Option<bool> {
276 let (a, b) = (self.as_text()?, other.as_text()?);
277 let a = Self::text_with_mode(a, mode);
278 let b = Self::text_with_mode(b, mode);
279 Some(f(&a, &b))
280 }
281
282 fn ci_key(&self) -> Option<String> {
283 match self {
284 Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
285 Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
286 Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
287 Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
288 _ => None,
289 }
290 }
291
292 fn eq_ci(a: &Self, b: &Self) -> bool {
293 if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
294 return ak == bk;
295 }
296
297 a == b
298 }
299
300 fn normalize_list_ref(v: &Self) -> Vec<&Self> {
301 match v {
302 Self::List(vs) => vs.iter().collect(),
303 v => vec![v],
304 }
305 }
306
307 fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
308 where
309 F: Fn(&Self, &Self) -> bool,
310 {
311 self.as_list()
312 .map(|items| items.iter().any(|v| eq(v, needle)))
313 }
314
315 #[allow(clippy::unnecessary_wraps)]
316 fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
317 where
318 F: Fn(&Self, &Self) -> bool,
319 {
320 let needles = Self::normalize_list_ref(needles);
321 match self {
322 Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
323 scalar => Some(needles.iter().any(|n| eq(scalar, n))),
324 }
325 }
326
327 #[allow(clippy::unnecessary_wraps)]
328 fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
329 where
330 F: Fn(&Self, &Self) -> bool,
331 {
332 let needles = Self::normalize_list_ref(needles);
333 match self {
334 Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
335 scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
336 }
337 }
338
339 fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
340 where
341 F: Fn(&Self, &Self) -> bool,
342 {
343 if let Self::List(items) = haystack {
344 Some(items.iter().any(|h| eq(h, self)))
345 } else {
346 None
347 }
348 }
349
350 #[must_use]
351 pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
353 self.text_op(other, mode, |a, b| a == b)
354 }
355
356 #[must_use]
357 pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
359 self.text_op(needle, mode, |a, b| a.contains(b))
360 }
361
362 #[must_use]
363 pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
365 self.text_op(needle, mode, |a, b| a.starts_with(b))
366 }
367
368 #[must_use]
369 pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
371 self.text_op(needle, mode, |a, b| a.ends_with(b))
372 }
373
374 #[must_use]
379 pub const fn is_empty(&self) -> Option<bool> {
380 match self {
381 Self::List(xs) => Some(xs.is_empty()),
382 Self::Text(s) => Some(s.is_empty()),
383 Self::Blob(b) => Some(b.is_empty()),
384
385 Self::None => Some(true),
387
388 _ => None,
389 }
390 }
391
392 #[must_use]
393 pub fn is_not_empty(&self) -> Option<bool> {
395 self.is_empty().map(|b| !b)
396 }
397
398 #[must_use]
403 pub fn contains(&self, needle: &Self) -> Option<bool> {
405 self.contains_by(needle, |a, b| a == b)
406 }
407
408 #[must_use]
409 pub fn contains_any(&self, needles: &Self) -> Option<bool> {
411 self.contains_any_by(needles, |a, b| a == b)
412 }
413
414 #[must_use]
415 pub fn contains_all(&self, needles: &Self) -> Option<bool> {
417 self.contains_all_by(needles, |a, b| a == b)
418 }
419
420 #[must_use]
421 pub fn in_list(&self, haystack: &Self) -> Option<bool> {
423 self.in_list_by(haystack, |a, b| a == b)
424 }
425
426 #[must_use]
427 pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
429 match self {
430 Self::List(_) => self.contains_by(needle, Self::eq_ci),
431 _ => Some(Self::eq_ci(self, needle)),
432 }
433 }
434
435 #[must_use]
436 pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
438 self.contains_any_by(needles, Self::eq_ci)
439 }
440
441 #[must_use]
442 pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
444 self.contains_all_by(needles, Self::eq_ci)
445 }
446
447 #[must_use]
448 pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
450 self.in_list_by(haystack, Self::eq_ci)
451 }
452}
453
454#[macro_export]
455macro_rules! impl_from_for {
456 ( $( $type:ty => $variant:ident ),* $(,)? ) => {
457 $(
458 impl From<$type> for Value {
459 fn from(v: $type) -> Self {
460 Self::$variant(v.into())
461 }
462 }
463 )*
464 };
465}
466
467impl_from_for! {
468 Account => Account,
469 Date => Date,
470 Decimal => Decimal,
471 Duration => Duration,
472 E8s => E8s,
473 E18s => E18s,
474 bool => Bool,
475 i8 => Int,
476 i16 => Int,
477 i32 => Int,
478 i64 => Int,
479 i128 => Int128,
480 Int => IntBig,
481 Principal => Principal,
482 Subaccount => Subaccount,
483 &str => Text,
484 String => Text,
485 Timestamp => Timestamp,
486 u8 => Uint,
487 u16 => Uint,
488 u32 => Uint,
489 u64 => Uint,
490 u128 => Uint128,
491 Nat => UintBig,
492 Ulid => Ulid,
493}
494
495impl ValueFamilyExt for Value {
496 fn family(&self) -> ValueFamily {
497 match self {
498 Self::Date(_)
500 | Self::Decimal(_)
501 | Self::Duration(_)
502 | Self::E8s(_)
503 | Self::E18s(_)
504 | Self::Float32(_)
505 | Self::Float64(_)
506 | Self::Int(_)
507 | Self::Int128(_)
508 | Self::Timestamp(_)
509 | Self::Uint(_)
510 | Self::Uint128(_)
511 | Self::IntBig(_)
512 | Self::UintBig(_) => ValueFamily::Numeric,
513
514 Self::Text(_) => ValueFamily::Textual,
516
517 Self::Ulid(_) | Self::Principal(_) | Self::Account(_) => ValueFamily::Identifier,
519
520 Self::Enum(_) => ValueFamily::Enum,
522
523 Self::List(_) => ValueFamily::Collection,
525
526 Self::Blob(_) | Self::Subaccount(_) => ValueFamily::Blob,
528
529 Self::Bool(_) => ValueFamily::Bool,
531
532 Self::None => ValueFamily::Null,
534 Self::Unit => ValueFamily::Unit,
535
536 Self::Unsupported => ValueFamily::Unsupported,
538 }
539 }
540}
541
542impl FieldValue for Value {
543 fn to_value(&self) -> Value {
544 self.clone()
545 }
546}
547
548impl From<Vec<Self>> for Value {
549 fn from(vec: Vec<Self>) -> Self {
550 Self::List(vec)
551 }
552}
553
554impl From<()> for Value {
555 fn from((): ()) -> Self {
556 Self::Unit
557 }
558}
559
560impl PartialOrd for Value {
561 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
562 match (self, other) {
563 (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
564 (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
565 (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
566 (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
567 (Self::E8s(a), Self::E8s(b)) => a.partial_cmp(b),
568 (Self::E18s(a), Self::E18s(b)) => a.partial_cmp(b),
569 (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
570 (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
571 (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
572 (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
573 (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
574 (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
575 (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
576 (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
577 (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
578 (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
579 (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
580 (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
581 (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
582 (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
583
584 _ => None,
586 }
587 }
588}
589
590#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd, Serialize)]
596pub struct ValueEnum {
597 pub variant: String,
598 pub path: Option<String>,
599 pub payload: Option<Box<Value>>,
600}
601
602impl ValueEnum {
603 #[must_use]
604 pub fn new(variant: &str, path: Option<&str>) -> Self {
606 Self {
607 variant: variant.to_string(),
608 path: path.map(ToString::to_string),
609 payload: None,
610 }
611 }
612
613 #[must_use]
614 pub fn loose(variant: &str) -> Self {
616 Self::new(variant, None)
617 }
618
619 #[must_use]
620 pub fn with_payload(mut self, payload: Value) -> Self {
622 self.payload = Some(Box::new(payload));
623 self
624 }
625}