1#[cfg(feature = "decimal")]
5mod decimal;
6mod float_text;
7mod timestamp;
8
9use std::{
10 any::Any,
11 borrow::{Borrow, Cow},
12 cmp::Ordering,
13 fmt::{self, Debug, Write as _},
14 hash::{Hash, Hasher},
15 sync::Arc,
16};
17
18use chrono::{DateTime, Utc};
19#[cfg(feature = "decimal")]
20use rust_decimal::Decimal;
21use serde::{Deserialize, Deserializer, Serialize, Serializer, de, ser};
22
23pub(crate) use self::float_text::push_fixed;
24pub(crate) use self::timestamp::push_timestamp;
25
26use crate::highlight::ChangeDirection;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
31pub struct ColumnIx(u16);
32
33impl ColumnIx {
34 pub const fn new(ix: u16) -> Self {
36 Self(ix)
37 }
38
39 pub const fn get(self) -> usize {
41 self.0 as usize
42 }
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
54#[serde(transparent)]
55pub struct ColumnId(Cow<'static, str>);
56
57impl ColumnId {
58 pub const fn new_static(id: &'static str) -> Self {
60 Self(Cow::Borrowed(id))
61 }
62
63 pub fn new(id: impl Into<String>) -> Self {
65 Self(Cow::Owned(id.into()))
66 }
67
68 pub fn as_str(&self) -> &str {
70 &self.0
71 }
72}
73
74impl From<&'static str> for ColumnId {
75 fn from(id: &'static str) -> Self {
76 Self::new_static(id)
77 }
78}
79
80impl From<String> for ColumnId {
81 fn from(id: String) -> Self {
82 Self::new(id)
83 }
84}
85
86impl AsRef<str> for ColumnId {
87 fn as_ref(&self) -> &str {
88 self.as_str()
89 }
90}
91
92impl Borrow<str> for ColumnId {
93 fn borrow(&self) -> &str {
94 self.as_str()
95 }
96}
97
98impl fmt::Display for ColumnId {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 f.write_str(self.as_str())
101 }
102}
103
104#[cfg_attr(feature = "derive", doc = "```")]
120#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
121pub trait GridColumns: Copy + Eq + Hash + Debug + Send + Sync + 'static {
137 fn all() -> &'static [Self];
139 fn id(self) -> &'static str;
142 fn ix(self) -> ColumnIx;
144
145 fn column_id(self) -> ColumnId {
148 ColumnId::new_static(self.id())
149 }
150}
151
152#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
154pub struct ColumnSet(u128);
155
156impl ColumnSet {
157 pub const EMPTY: Self = Self(0);
159
160 pub fn first(count: usize) -> Self {
162 assert!(count <= 128, "a grid supports at most 128 columns");
163 if count == 128 {
164 Self(u128::MAX)
165 } else {
166 Self((1u128 << count) - 1)
167 }
168 }
169
170 pub fn insert(&mut self, column: ColumnIx) {
172 self.0 |= 1u128 << column.get();
173 }
174
175 pub fn contains(self, column: ColumnIx) -> bool {
177 self.0 & (1u128 << column.get()) != 0
178 }
179
180 pub fn is_empty(self) -> bool {
182 self.0 == 0
183 }
184
185 pub fn intersects(self, other: Self) -> bool {
187 self.0 & other.0 != 0
188 }
189
190 pub fn union_with(&mut self, other: Self) {
192 self.0 |= other.0;
193 }
194
195 pub fn len(self) -> usize {
197 self.0.count_ones() as usize
198 }
199
200 #[inline]
203 pub(crate) fn rank(self, column: ColumnIx) -> usize {
204 let below = (1u128 << column.get()) - 1;
205 (self.0 & below).count_ones() as usize
206 }
207
208 pub(crate) fn intersection(self, other: Self) -> Self {
210 Self(self.0 & other.0)
211 }
212
213 pub(crate) fn remove(&mut self, column: ColumnIx) {
215 self.0 &= !(1u128 << column.get());
216 }
217
218 pub(crate) fn iter(self) -> impl Iterator<Item = ColumnIx> {
220 let mut bits = self.0;
221 std::iter::from_fn(move || {
222 if bits == 0 {
223 return None;
224 }
225 let ix = bits.trailing_zeros() as u16;
226 bits &= bits - 1;
227 Some(ColumnIx::new(ix))
228 })
229 }
230
231 pub(crate) const fn bits(self) -> u128 {
233 self.0
234 }
235
236 pub(crate) const fn from_bits(bits: u128) -> Self {
238 Self(bits)
239 }
240}
241
242#[derive(Clone, Copy, Debug)]
251#[non_exhaustive]
252pub enum CellValue<'a> {
253 Empty,
255 Bool(bool),
257 Int(i64),
259 Float(f64),
261 #[cfg(feature = "decimal")]
265 Decimal(Decimal),
266 Text(&'a str),
268 Timestamp(DateTime<Utc>),
273 Icon(IconValue<'a>),
277 Image(ImageValue<'a>),
283 Link(LinkValue<'a>),
290 Custom(&'a dyn GridValue),
293}
294
295impl PartialEq for CellValue<'_> {
296 fn eq(&self, other: &Self) -> bool {
297 match (self, other) {
298 (Self::Empty, Self::Empty) => true,
299 (Self::Bool(a), Self::Bool(b)) => a == b,
300 (Self::Int(a), Self::Int(b)) => a == b,
301 (Self::Float(a), Self::Float(b)) => a == b,
302 #[cfg(feature = "decimal")]
303 (Self::Decimal(a), Self::Decimal(b)) => a == b,
304 (Self::Text(a), Self::Text(b)) => a == b,
305 (Self::Timestamp(a), Self::Timestamp(b)) => a == b,
306 (Self::Icon(a), Self::Icon(b)) => a == b,
307 (Self::Image(a), Self::Image(b)) => a == b,
308 (Self::Link(a), Self::Link(b)) => a == b,
309 (Self::Custom(a), Self::Custom(b)) => a.cmp_value(*b).is_eq(),
310 _ => false,
311 }
312 }
313}
314
315impl CellValue<'_> {
316 pub fn cmp_total(&self, other: &Self) -> Ordering {
340 fn rank(value: &CellValue) -> u8 {
341 match value {
342 CellValue::Empty => 0,
343 CellValue::Bool(_) => 1,
344 CellValue::Int(_) | CellValue::Float(_) => 2,
345 #[cfg(feature = "decimal")]
346 CellValue::Decimal(_) => 2,
347 CellValue::Timestamp(_) => 3,
348 CellValue::Text(_)
349 | CellValue::Icon(_)
350 | CellValue::Image(_)
351 | CellValue::Link(_) => 4,
352 CellValue::Custom(_) => 5,
353 }
354 }
355 match (self, other) {
356 (CellValue::Bool(a), CellValue::Bool(b)) => a.cmp(b),
357 (CellValue::Int(a), CellValue::Int(b)) => a.cmp(b),
358 (CellValue::Float(a), CellValue::Float(b)) => a.total_cmp(b),
359 (CellValue::Int(a), CellValue::Float(b)) => cmp_int_float(*a, *b),
360 (CellValue::Float(a), CellValue::Int(b)) => cmp_int_float(*b, *a).reverse(),
361 #[cfg(feature = "decimal")]
362 (CellValue::Decimal(a), CellValue::Decimal(b)) => a.cmp(b),
363 #[cfg(feature = "decimal")]
365 (CellValue::Decimal(a), CellValue::Int(b)) => a.cmp(&Decimal::from(*b)),
366 #[cfg(feature = "decimal")]
367 (CellValue::Int(a), CellValue::Decimal(b)) => Decimal::from(*a).cmp(b),
368 #[cfg(feature = "decimal")]
369 (CellValue::Decimal(a), CellValue::Float(b)) => decimal::cmp_decimal_float(*a, *b),
370 #[cfg(feature = "decimal")]
371 (CellValue::Float(a), CellValue::Decimal(b)) => {
372 decimal::cmp_decimal_float(*b, *a).reverse()
373 }
374 (CellValue::Text(a), CellValue::Text(b)) => a.cmp(b),
375 (CellValue::Timestamp(a), CellValue::Timestamp(b)) => a.cmp(b),
376 (CellValue::Custom(a), CellValue::Custom(b)) => a.cmp_value(*b),
377 _ => match (self.sort_text(), other.sort_text()) {
379 (Some(a), Some(b)) => a.cmp(b),
380 _ => rank(self).cmp(&rank(other)),
381 },
382 }
383 }
384
385 fn sort_text(&self) -> Option<&str> {
387 match self {
388 CellValue::Text(text) => Some(text),
389 CellValue::Icon(icon) => Some(icon.text),
390 CellValue::Image(image) => Some(image.text),
391 CellValue::Link(link) => Some(link.text),
392 _ => None,
393 }
394 }
395
396 pub fn is_number(&self) -> bool {
398 match self {
399 CellValue::Int(_) | CellValue::Float(_) => true,
400 #[cfg(feature = "decimal")]
401 CellValue::Decimal(_) => true,
402 _ => false,
403 }
404 }
405}
406
407#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
415pub struct IconValue<'a> {
416 path: &'a str,
417 text: &'a str,
418}
419
420impl<'a> IconValue<'a> {
421 pub const fn new(path: &'a str, text: &'a str) -> Self {
423 Self { path, text }
424 }
425
426 pub const fn path(&self) -> &'a str {
428 self.path
429 }
430
431 pub const fn text(&self) -> &'a str {
433 self.text
434 }
435}
436
437#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
440pub struct LinkValue<'a> {
441 text: &'a str,
442 url: &'a str,
443}
444
445impl<'a> LinkValue<'a> {
446 pub const fn new(text: &'a str, url: &'a str) -> Self {
448 Self { text, url }
449 }
450
451 pub const fn text(&self) -> &'a str {
453 self.text
454 }
455
456 pub const fn url(&self) -> &'a str {
458 self.url
459 }
460}
461
462#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
470pub struct ImageValue<'a> {
471 id: &'a str,
472 text: &'a str,
473}
474
475impl<'a> ImageValue<'a> {
476 pub const fn new(id: &'a str, text: &'a str) -> Self {
478 Self { id, text }
479 }
480
481 pub const fn id(&self) -> &'a str {
483 self.id
484 }
485
486 pub const fn text(&self) -> &'a str {
488 self.text
489 }
490}
491
492pub trait GridValue: Any + Debug + Send + Sync {
539 fn cmp_value(&self, other: &dyn GridValue) -> Ordering;
545
546 fn write_text(&self, out: &mut String);
550
551 fn export_value(&self) -> Option<CellValue<'_>> {
555 None
556 }
557
558 fn to_owned_value(&self) -> OwnedValue {
570 let mut text = String::new();
571 self.write_text(&mut text);
572 OwnedValue::Text(text.into())
573 }
574}
575
576impl dyn GridValue {
577 pub fn as_any(&self) -> &dyn Any {
579 self
580 }
581
582 pub fn downcast_ref<T: GridValue>(&self) -> Option<&T> {
584 self.as_any().downcast_ref()
585 }
586}
587
588#[derive(Clone, Debug)]
606#[non_exhaustive]
607pub enum OwnedValue {
608 Empty,
610 Bool(bool),
612 Int(i64),
614 Float(f64),
616 #[cfg(feature = "decimal")]
618 Decimal(Decimal),
619 Text(Arc<str>),
621 Timestamp(DateTime<Utc>),
623 Custom(Arc<dyn GridValue>),
630}
631
632impl OwnedValue {
633 pub fn custom(value: impl GridValue) -> Self {
637 Self::Custom(Arc::new(value))
638 }
639
640 pub fn from_cell(value: CellValue<'_>) -> Self {
645 match value {
646 CellValue::Empty => Self::Empty,
647 CellValue::Bool(value) => Self::Bool(value),
648 CellValue::Int(value) => Self::Int(value),
649 CellValue::Float(value) => Self::Float(value),
650 #[cfg(feature = "decimal")]
651 CellValue::Decimal(value) => Self::Decimal(value),
652 CellValue::Text(text) => Self::Text(text.into()),
653 CellValue::Timestamp(value) => Self::Timestamp(value),
654 CellValue::Icon(icon) => Self::Text(icon.text().into()),
655 CellValue::Image(image) => Self::Text(image.text().into()),
656 CellValue::Link(link) => Self::Text(link.text().into()),
657 CellValue::Custom(value) => value.to_owned_value(),
658 }
659 }
660
661 pub fn as_cell(&self) -> CellValue<'_> {
663 match self {
664 Self::Empty => CellValue::Empty,
665 Self::Bool(value) => CellValue::Bool(*value),
666 Self::Int(value) => CellValue::Int(*value),
667 Self::Float(value) => CellValue::Float(*value),
668 #[cfg(feature = "decimal")]
669 Self::Decimal(value) => CellValue::Decimal(*value),
670 Self::Text(text) => CellValue::Text(text),
671 Self::Timestamp(value) => CellValue::Timestamp(*value),
672 Self::Custom(value) => CellValue::Custom(&**value),
673 }
674 }
675
676 pub fn cmp_total(&self, other: &Self) -> Ordering {
678 self.as_cell().cmp_total(&other.as_cell())
679 }
680}
681
682impl PartialEq for OwnedValue {
683 fn eq(&self, other: &Self) -> bool {
684 self.cmp_total(other).is_eq()
685 }
686}
687
688impl Eq for OwnedValue {}
689
690impl Hash for OwnedValue {
691 fn hash<H: Hasher>(&self, state: &mut H) {
692 match self {
693 Self::Empty => state.write_u8(0),
694 Self::Bool(value) => {
695 state.write_u8(1);
696 value.hash(state);
697 }
698 Self::Int(value) => {
699 state.write_u8(2);
700 value.hash(state);
701 }
702 Self::Float(value) => match integral(*value) {
704 Some(int) => {
705 state.write_u8(2);
706 int.hash(state);
707 }
708 None => {
710 state.write_u8(3);
711 value.to_bits().hash(state);
712 }
713 },
714 #[cfg(feature = "decimal")]
716 Self::Decimal(value) => match decimal::equal_int_or_float(*value) {
717 Some(number) => number.hash(state),
718 None => {
719 let normal = value.normalize();
721 state.write_u8(5);
722 normal.mantissa().hash(state);
723 normal.scale().hash(state);
724 }
725 },
726 Self::Text(text) => {
727 state.write_u8(4);
728 text.hash(state);
729 }
730 Self::Timestamp(value) => {
731 state.write_u8(6);
732 value.hash(state);
733 }
734 Self::Custom(value) => {
737 state.write_u8(7);
738 value.as_any().type_id().hash(state);
739 }
740 }
741 }
742}
743
744fn integral(float: f64) -> Option<i64> {
747 let in_range = (-9.223_372_036_854_775_808e18..9.223_372_036_854_775_808e18).contains(&float);
748 (in_range && float.fract() == 0.0 && !(float == 0.0 && float.is_sign_negative()))
749 .then_some(float as i64)
750}
751
752impl Serialize for OwnedValue {
753 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
754 match self {
755 Self::Empty => serializer.serialize_none(),
756 Self::Bool(value) => serializer.serialize_bool(*value),
757 Self::Int(value) => serializer.serialize_i64(*value),
758 Self::Float(value) if value.is_finite() => serializer.serialize_f64(*value),
759 Self::Float(value) => Err(ser::Error::custom(format_args!(
760 "{value} is not a finite number"
761 ))),
762 #[cfg(feature = "decimal")]
763 Self::Decimal(value) => {
764 use ser::SerializeMap as _;
765 let mut map = serializer.serialize_map(Some(1))?;
766 map.serialize_entry("decimal", &decimal::DecimalText(*value))?;
767 map.end()
768 }
769 Self::Text(text) => serializer.serialize_str(text),
770 Self::Timestamp(value) => {
771 use ser::SerializeMap as _;
772 let mut map = serializer.serialize_map(Some(1))?;
773 map.serialize_entry("timestamp", ×tamp::TimestampText(*value))?;
774 map.end()
775 }
776 Self::Custom(value) => match value.export_value() {
777 Some(export) if !matches!(export, CellValue::Custom(_)) => {
778 Self::from_cell(export).serialize(serializer)
779 }
780 _ => {
781 let mut text = String::new();
782 value.write_text(&mut text);
783 serializer.serialize_str(&text)
784 }
785 },
786 }
787 }
788}
789
790#[derive(Deserialize)]
793#[serde(field_identifier, rename_all = "lowercase")]
794enum TaggedValue {
795 Timestamp,
796 #[cfg(feature = "decimal")]
797 Decimal,
798}
799
800impl<'de> Deserialize<'de> for OwnedValue {
801 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
802 struct ValueVisitor;
803
804 impl<'v> de::Visitor<'v> for ValueVisitor {
805 type Value = OwnedValue;
806
807 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
808 formatter.write_str(if cfg!(feature = "decimal") {
809 r#"null, a boolean, a number, a string, {"timestamp": text} or {"decimal": text}"#
810 } else {
811 r#"null, a boolean, a number, a string or {"timestamp": text}"#
812 })
813 }
814
815 fn visit_unit<E: de::Error>(self) -> Result<OwnedValue, E> {
816 Ok(OwnedValue::Empty)
817 }
818
819 fn visit_none<E: de::Error>(self) -> Result<OwnedValue, E> {
820 Ok(OwnedValue::Empty)
821 }
822
823 fn visit_bool<E: de::Error>(self, value: bool) -> Result<OwnedValue, E> {
824 Ok(OwnedValue::Bool(value))
825 }
826
827 fn visit_i64<E: de::Error>(self, value: i64) -> Result<OwnedValue, E> {
828 Ok(OwnedValue::Int(value))
829 }
830
831 fn visit_u64<E: de::Error>(self, value: u64) -> Result<OwnedValue, E> {
832 i64::try_from(value)
833 .map(OwnedValue::Int)
834 .map_err(|_| E::invalid_value(de::Unexpected::Unsigned(value), &self))
835 }
836
837 fn visit_f64<E: de::Error>(self, value: f64) -> Result<OwnedValue, E> {
838 Ok(OwnedValue::Float(value))
839 }
840
841 fn visit_str<E: de::Error>(self, value: &str) -> Result<OwnedValue, E> {
842 Ok(OwnedValue::Text(value.into()))
843 }
844
845 fn visit_map<A: de::MapAccess<'v>>(self, mut map: A) -> Result<OwnedValue, A::Error> {
846 let Some(tag) = map.next_key::<TaggedValue>()? else {
847 return Err(de::Error::invalid_length(0, &self));
848 };
849 let text: String = map.next_value()?;
850 let value = match tag {
851 TaggedValue::Timestamp => {
852 OwnedValue::Timestamp(timestamp::read_timestamp(&text)?)
853 }
854 #[cfg(feature = "decimal")]
855 TaggedValue::Decimal => OwnedValue::Decimal(decimal::read_decimal(&text)?),
856 };
857 if map.next_key::<de::IgnoredAny>()?.is_some() {
858 return Err(de::Error::invalid_length(2, &self));
859 }
860 Ok(value)
861 }
862 }
863
864 deserializer.deserialize_any(ValueVisitor)
865 }
866}
867
868#[inline]
879pub(crate) fn display_text<'a>(value: CellValue<'a>, buffer: &'a mut String) -> &'a str {
880 display_text_in(value, NumberFormat::new(), buffer)
881}
882
883#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
900pub struct NumberFormat {
901 decimals: u8,
902}
903
904impl NumberFormat {
905 pub const MAX_DECIMALS: u8 = 15;
907
908 pub const fn new() -> Self {
910 Self { decimals: 2 }
911 }
912
913 pub const fn with_decimals(mut self, decimals: u8) -> Self {
916 self.decimals = if decimals > Self::MAX_DECIMALS {
917 Self::MAX_DECIMALS
918 } else {
919 decimals
920 };
921 self
922 }
923
924 pub const fn decimals(&self) -> u8 {
926 self.decimals
927 }
928}
929
930impl Default for NumberFormat {
931 fn default() -> Self {
932 Self::new()
933 }
934}
935
936#[inline]
939pub(crate) fn display_text_in<'a>(
940 value: CellValue<'a>,
941 format: NumberFormat,
942 buffer: &'a mut String,
943) -> &'a str {
944 match value {
945 CellValue::Text(text) => text,
946 CellValue::Empty | CellValue::Bool(false) => "",
947 CellValue::Bool(true) => "✓",
948 CellValue::Int(value) => {
949 buffer.clear();
950 let _ = write!(buffer, "{value}");
951 buffer
952 }
953 CellValue::Float(value) => {
954 buffer.clear();
955 float_text::push_fixed(value, format.decimals, buffer);
956 buffer
957 }
958 #[cfg(feature = "decimal")]
959 CellValue::Decimal(value) => {
960 buffer.clear();
961 decimal::push_decimal(value, buffer);
962 buffer
963 }
964 CellValue::Timestamp(value) => {
965 buffer.clear();
966 push_timestamp(value, buffer);
967 buffer
968 }
969 CellValue::Icon(icon) => icon.text(),
970 CellValue::Image(image) => image.text(),
971 CellValue::Link(link) => link.text(),
972 CellValue::Custom(value) => {
973 buffer.clear();
974 value.write_text(buffer);
975 buffer
976 }
977 }
978}
979
980#[inline]
990pub(crate) fn number_fraction_digits(value: CellValue<'_>, format: NumberFormat) -> Option<u32> {
991 let value = match value {
992 CellValue::Custom(value) => value.export_value()?,
993 value => value,
994 };
995 match value {
996 CellValue::Int(_) => Some(0),
997 CellValue::Float(_) => Some(u32::from(format.decimals)),
998 #[cfg(feature = "decimal")]
999 CellValue::Decimal(value) => Some(value.scale()),
1000 _ => None,
1001 }
1002}
1003
1004pub(crate) fn may_occur_in_number_text(text: &str) -> bool {
1009 text.bytes().all(|byte| {
1010 matches!(
1011 byte.to_ascii_lowercase(),
1012 b'0'..=b'9' | b'-' | b'.' | b'n' | b'a' | b'i' | b'f'
1013 )
1014 })
1015}
1016
1017fn cmp_int_float(int: i64, float: f64) -> Ordering {
1021 if float.is_nan() {
1022 return if float.is_sign_negative() {
1024 Ordering::Greater
1025 } else {
1026 Ordering::Less
1027 };
1028 }
1029 if float >= 9.223_372_036_854_775_807e18 {
1030 return Ordering::Less;
1031 }
1032 if float < -9.223_372_036_854_775_808e18 {
1033 return Ordering::Greater;
1034 }
1035 let whole = float.trunc();
1036 let fraction = if float == 0.0 { float } else { float - whole };
1040 i128::from(int)
1041 .cmp(&(whole as i128))
1042 .then_with(|| 0.0f64.total_cmp(&fraction))
1043}
1044
1045pub trait GridRow: Send + Sync + 'static {
1047 type Key: Clone + Eq + Hash + Debug + Send + Sync + 'static;
1049 type Column: GridColumns;
1051
1052 fn key(&self) -> &Self::Key;
1054
1055 fn value(&self, column: Self::Column) -> CellValue<'_>;
1058
1059 fn changed_columns(&self, previous: &Self, out: &mut ColumnSet) {
1067 for &column in Self::Column::all() {
1068 if self.value(column) != previous.value(column) {
1069 out.insert(column.ix());
1070 }
1071 }
1072 }
1073
1074 fn parent_key(&self) -> Option<&Self::Key> {
1081 None
1082 }
1083
1084 fn change_direction(&self, previous: &Self, column: Self::Column) -> Option<ChangeDirection> {
1092 let (value, before) = (self.value(column), previous.value(column));
1093 if !(value.is_number() && before.is_number()) {
1094 return None;
1095 }
1096 match value.cmp_total(&before) {
1097 Ordering::Greater => Some(ChangeDirection::Up),
1098 Ordering::Less => Some(ChangeDirection::Down),
1099 Ordering::Equal => None,
1100 }
1101 }
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106 use super::*;
1107
1108 #[test]
1109 fn test_cell_value_order_is_exact_across_int_and_float() {
1110 let big = 1i64 << 53;
1111 let values = [
1112 CellValue::Int(big + 1),
1113 CellValue::Float(big as f64),
1114 CellValue::Int(big),
1115 CellValue::Float(f64::NAN),
1116 CellValue::Float(-0.5),
1117 CellValue::Int(-1),
1118 CellValue::Empty,
1119 CellValue::Text("a"),
1120 ];
1121 assert_eq!(values[0].cmp_total(&values[1]), Ordering::Greater);
1123 assert_eq!(values[2].cmp_total(&values[1]), Ordering::Equal);
1124 assert_eq!(values[1].cmp_total(&values[0]), Ordering::Less);
1125 assert_eq!(values[3].cmp_total(&values[0]), Ordering::Greater);
1126 assert_eq!(values[4].cmp_total(&values[5]), Ordering::Greater);
1127 let mut sorted = values;
1130 sorted.sort_by(|a, b| a.cmp_total(b));
1131 for window in sorted.windows(2) {
1132 assert_ne!(window[0].cmp_total(&window[1]), Ordering::Greater);
1133 }
1134 }
1135
1136 fn ladder() -> Vec<Vec<CellValue<'static>>> {
1142 use CellValue::{Bool, Empty, Float, Int, Text};
1143 let p53 = 1i64 << 53;
1144 let p63 = 9_223_372_036_854_775_808.0f64;
1145 vec![
1146 vec![Empty],
1147 vec![Bool(false)],
1148 vec![Bool(true)],
1149 vec![Float(f64::from_bits(0xFFF8_0000_0000_0000))],
1151 vec![Float(f64::from_bits(0xFFF0_0000_0000_0001))],
1152 vec![Float(f64::NEG_INFINITY)],
1153 vec![Float(f64::MIN)],
1154 vec![Float(-p63 - 2048.0)],
1155 vec![Int(i64::MIN), Float(-p63)],
1156 vec![Int(i64::MIN + 1)],
1157 vec![Int(-p53 - 1)],
1158 vec![Int(-p53), Float(-p53 as f64)],
1159 vec![Float(-1.5)],
1160 vec![Int(-1), Float(-1.0)],
1161 vec![Float(-0.5)],
1162 vec![Float(-f64::MIN_POSITIVE)],
1163 vec![Float(-f64::from_bits(1))],
1164 vec![Float(-0.0)],
1165 vec![Int(0), Float(0.0)],
1166 vec![Float(f64::from_bits(1))],
1167 vec![Float(0.5)],
1168 vec![Int(1), Float(1.0)],
1169 vec![Float(1.5)],
1170 vec![Int(p53 - 1), Float((p53 - 1) as f64)],
1171 vec![Int(p53), Float(p53 as f64)],
1172 vec![Int(p53 + 1)],
1173 vec![Int(p53 + 2), Float((p53 + 2) as f64)],
1174 vec![Int(i64::MAX - 1023), Float(p63 - 1024.0)],
1175 vec![Int(i64::MAX - 1)],
1176 vec![Int(i64::MAX)],
1177 vec![Float(p63)],
1178 vec![Float(f64::MAX)],
1179 vec![Float(f64::INFINITY)],
1180 vec![Float(f64::from_bits(0x7FF0_0000_0000_0001))],
1181 vec![Float(f64::from_bits(0x7FF8_0000_0000_0000))],
1182 vec![Text("")],
1183 vec![Text("a")],
1184 vec![Text("b")],
1185 ]
1186 }
1187
1188 fn check_total_order(a: &CellValue, b: &CellValue, c: &CellValue) {
1191 let (ab, bc, ac) = (a.cmp_total(b), b.cmp_total(c), a.cmp_total(c));
1192 assert_eq!(
1193 a.cmp_total(a),
1194 Ordering::Equal,
1195 "{a:?} is not equal to itself"
1196 );
1197 assert_eq!(b.cmp_total(a), ab.reverse(), "{a:?} and {b:?}");
1198 let expected = match (ab, bc) {
1199 (Ordering::Equal, _) => bc,
1200 (_, Ordering::Equal) => ab,
1201 _ if ab == bc => ab,
1202 _ => return,
1204 };
1205 assert_eq!(ac, expected, "{a:?}, {b:?}, {c:?}: {ab:?} then {bc:?}");
1206 }
1207
1208 #[test]
1209 fn test_cell_value_order_matches_ladder() {
1210 let ladder = ladder();
1211 for (rung, values) in ladder.iter().enumerate() {
1212 for (other_rung, others) in ladder.iter().enumerate() {
1213 for a in values {
1214 for b in others {
1215 assert_eq!(a.cmp_total(b), rung.cmp(&other_rung), "{a:?} against {b:?}");
1216 }
1217 }
1218 }
1219 }
1220
1221 let zero = CellValue::Int(0);
1224 assert_eq!(zero.cmp_total(&CellValue::Float(-0.0)), Ordering::Greater);
1225 assert_eq!(CellValue::Float(-0.0).cmp_total(&zero), Ordering::Less);
1226 assert_eq!(zero.cmp_total(&CellValue::Float(0.0)), Ordering::Equal);
1227 assert_eq!(
1228 CellValue::Int(-1).cmp_total(&CellValue::Float(-0.0)),
1229 Ordering::Less
1230 );
1231 }
1232
1233 #[test]
1234 fn test_cell_value_order_is_total() {
1235 let ladder: Vec<CellValue> = ladder().into_iter().flatten().collect();
1236 for a in &ladder {
1238 for b in &ladder {
1239 for c in &ladder {
1240 check_total_order(a, b, c);
1241 }
1242 }
1243 }
1244
1245 let mut rng = fastrand::Rng::with_seed(20);
1250 let random = |rng: &mut fastrand::Rng| match rng.u8(0..9) {
1251 0 => ladder[rng.usize(..ladder.len())],
1252 1 => CellValue::Int(rng.i64(-3..=3)),
1253 2 => CellValue::Float(f64::from(rng.i8(-6..=6)) / 2.0),
1254 3 => CellValue::Float([0.0, -0.0][rng.usize(..2)]),
1255 4 => {
1256 let edge = [0, 1 << 53, i64::MIN, i64::MAX][rng.usize(..4)];
1257 CellValue::Int(edge.saturating_add(rng.i64(-3..=3)))
1258 }
1259 5 => {
1260 let edge = [1u64 << 53, 1 << 63, 0][rng.usize(..3)] as f64;
1261 let bits = edge.to_bits().saturating_add_signed(rng.i64(-2..=2));
1262 let float = f64::from_bits(bits);
1263 CellValue::Float(if rng.bool() { float } else { -float })
1264 }
1265 6 => CellValue::Int(rng.i64(..)),
1266 7 => CellValue::Float(rng.i64(..) as f64),
1267 _ => CellValue::Float(f64::from_bits(rng.u64(..))),
1268 };
1269 for _ in 0..200_000 {
1270 let (a, b, c) = (random(&mut rng), random(&mut rng), random(&mut rng));
1271 check_total_order(&a, &b, &c);
1272 }
1273
1274 for _ in 0..100_000 {
1277 let int = match rng.u8(0..3) {
1278 0 => rng.i64(-4..=4),
1279 1 => rng.i64(-(1 << 53)..=1 << 53),
1280 _ => [0, 1 << 53, -(1 << 53)][rng.usize(..3)],
1281 };
1282 let float = match random(&mut rng) {
1283 CellValue::Float(float) => float,
1284 CellValue::Int(other) => other as f64,
1285 _ => -0.0,
1286 };
1287 assert_eq!(
1288 CellValue::Int(int).cmp_total(&CellValue::Float(float)),
1289 (int as f64).total_cmp(&float),
1290 "{int} against {float:?}"
1291 );
1292 }
1293 }
1294
1295 #[test]
1296 fn test_column_id() {
1297 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1298 enum Column {
1299 Price,
1300 }
1301
1302 impl GridColumns for Column {
1303 fn all() -> &'static [Self] {
1304 &[Column::Price]
1305 }
1306
1307 fn id(self) -> &'static str {
1308 "price"
1309 }
1310
1311 fn ix(self) -> ColumnIx {
1312 ColumnIx::new(0)
1313 }
1314 }
1315
1316 const PRICE: ColumnId = ColumnId::new_static("price");
1319 let from_schema = Column::Price.column_id();
1320 assert!(matches!(from_schema.0, Cow::Borrowed("price")));
1321 let from_file: ColumnId = serde_json::from_str("\"price\"").unwrap();
1322 assert!(matches!(from_file.0, Cow::Owned(_)));
1323 assert_eq!(from_schema, from_file);
1324 assert_eq!(from_schema, PRICE);
1325 assert_eq!(serde_json::to_string(&from_schema).unwrap(), "\"price\"");
1326
1327 let ids = rustc_hash::FxHashSet::from_iter([from_file.clone()]);
1330 assert!(ids.contains(&from_schema) && ids.contains("price"));
1331 let ordered =
1332 std::collections::BTreeMap::from([(ColumnId::new("b"), 2), (ColumnId::from("a"), 1)]);
1333 assert_eq!(
1334 ordered.keys().map(ColumnId::as_str).collect::<Vec<_>>(),
1335 ["a", "b"]
1336 );
1337 assert_eq!(ordered.get("b"), Some(&2));
1338
1339 assert_eq!(ColumnId::from(String::from("fee")), ColumnId::from("fee"));
1340 assert_eq!(from_file.to_string(), "price");
1341 assert_eq!(from_file.as_ref(), "price");
1342 }
1343
1344 #[test]
1345 fn test_display_text_in_a_number_format() {
1346 let mut buffer = String::new();
1347 let value = CellValue::Float(-0.489_999_999_999_994_9);
1348 for (decimals, expected) in [(0, "-0"), (1, "-0.5"), (2, "-0.49"), (4, "-0.4900")] {
1349 let format = NumberFormat::new().with_decimals(decimals);
1350 assert_eq!(display_text_in(value, format, &mut buffer), expected);
1351 assert_eq!(
1352 number_fraction_digits(value, format),
1353 Some(u32::from(decimals))
1354 );
1355 }
1356 let many = NumberFormat::new().with_decimals(40);
1358 assert_eq!(many.decimals(), NumberFormat::MAX_DECIMALS);
1359 assert_eq!(display_text_in(CellValue::Int(7), many, &mut buffer), "7");
1360 assert_eq!(
1361 display_text_in(CellValue::Text("1.5"), many, &mut buffer),
1362 "1.5"
1363 );
1364 assert_eq!(number_fraction_digits(CellValue::Int(7), many), Some(0));
1365 assert_eq!(NumberFormat::default(), NumberFormat::new());
1366 }
1367
1368 #[test]
1369 fn test_display_text() {
1370 let mut buffer = String::with_capacity(32);
1371 let storage = buffer.as_ptr();
1372 let cases = [
1373 (CellValue::Empty, ""),
1374 (CellValue::Bool(true), "✓"),
1375 (CellValue::Bool(false), ""),
1376 (CellValue::Int(0), "0"),
1377 (CellValue::Int(-42), "-42"),
1378 (CellValue::Int(i64::MAX), "9223372036854775807"),
1379 (CellValue::Float(12.5), "12.50"),
1380 (CellValue::Float(2.0 / 3.0), "0.67"),
1381 (CellValue::Float(-1234.567), "-1234.57"),
1382 (CellValue::Float(f64::NAN), "NaN"),
1383 (CellValue::Text("AAPL"), "AAPL"),
1384 (CellValue::Int(7), "7"),
1386 ];
1387 for (value, expected) in cases {
1388 assert_eq!(display_text(value, &mut buffer), expected, "{value:?}");
1389 }
1390 assert_eq!(buffer.as_ptr(), storage);
1392
1393 let row_text = String::from("Сбербанк");
1395 let text = display_text(CellValue::Text(&row_text), &mut buffer);
1396 assert!(std::ptr::eq(text, row_text.as_str()));
1397 }
1398
1399 #[test]
1400 fn test_number_text_alphabet() {
1401 let mut buffer = String::new();
1404 let mut check = |value: CellValue| {
1405 let text = display_text(value, &mut buffer);
1406 assert!(may_occur_in_number_text(text), "{value:?} shows as {text}");
1407 };
1408 for value in [i64::MIN, -1, 0, 7, i64::MAX] {
1409 check(CellValue::Int(value));
1410 }
1411 for value in [
1412 f64::NAN,
1413 -f64::NAN,
1414 f64::INFINITY,
1415 f64::NEG_INFINITY,
1416 f64::MAX,
1417 f64::MIN,
1418 f64::MIN_POSITIVE,
1419 -0.0,
1420 1e19,
1421 -1e300,
1422 ] {
1423 check(CellValue::Float(value));
1424 }
1425 let mut rng = fastrand::Rng::with_seed(3);
1426 for _ in 0..10_000 {
1427 check(CellValue::Int(rng.i64(..)));
1428 check(CellValue::Float(f64::from_bits(rng.u64(..))));
1429 }
1430
1431 for term in ["12.50", "-3", "nan", "INF", "1e", "", "12,5", "✓", "sber"] {
1432 let expected = matches!(term, "12.50" | "-3" | "nan" | "INF" | "");
1433 assert_eq!(may_occur_in_number_text(term), expected, "{term}");
1434 }
1435 }
1436
1437 #[test]
1438 fn test_number_fraction_digits() {
1439 #[derive(Debug)]
1441 struct Exported(Option<CellValue<'static>>);
1442
1443 impl GridValue for Exported {
1444 fn cmp_value(&self, _: &dyn GridValue) -> Ordering {
1445 Ordering::Equal
1446 }
1447
1448 fn write_text(&self, out: &mut String) {
1449 out.push_str("3M");
1450 }
1451
1452 fn export_value(&self) -> Option<CellValue<'_>> {
1453 self.0
1454 }
1455 }
1456
1457 static TENOR: Exported = Exported(Some(CellValue::Float(0.25)));
1458 static NAMED: Exported = Exported(None);
1459 static ALIAS: Exported = Exported(Some(CellValue::Custom(&TENOR)));
1460
1461 let mut buffer = String::new();
1463 let numbers = [
1464 CellValue::Int(0),
1465 CellValue::Int(i64::MIN),
1466 CellValue::Float(12.5),
1467 CellValue::Float(-1234.567),
1468 CellValue::Float(2.0 / 3.0),
1469 CellValue::Float(1e300),
1470 ]
1471 .into_iter();
1472 #[cfg(feature = "decimal")]
1473 let numbers = numbers.chain(
1474 [(1230, 2), (5, 0), (-5, 2), (0, 3), (1, 28)]
1475 .map(|(mantissa, scale)| CellValue::Decimal(Decimal::new(mantissa, scale))),
1476 );
1477 for value in numbers {
1478 let text = display_text(value, &mut buffer);
1479 let shown = text
1480 .split_once('.')
1481 .map_or(0, |(_, fraction)| fraction.len());
1482 assert_eq!(
1483 number_fraction_digits(value, NumberFormat::new()),
1484 Some(shown as u32),
1485 "{value:?} shows as {text}"
1486 );
1487 }
1488 assert_eq!(
1490 number_fraction_digits(CellValue::Float(f64::NAN), NumberFormat::new()),
1491 Some(2)
1492 );
1493
1494 assert_eq!(
1496 number_fraction_digits(CellValue::Custom(&TENOR), NumberFormat::new()),
1497 Some(2)
1498 );
1499 for value in [
1500 CellValue::Empty,
1501 CellValue::Bool(true),
1502 CellValue::Text("1.5"),
1503 CellValue::Timestamp(DateTime::<Utc>::UNIX_EPOCH),
1504 CellValue::Icon(IconValue::new("icons/check.svg", "1.5")),
1505 CellValue::Image(ImageValue::new("images/one.png", "1.5")),
1506 CellValue::Custom(&NAMED),
1507 CellValue::Custom(&ALIAS),
1508 ] {
1509 assert_eq!(
1510 number_fraction_digits(value, NumberFormat::new()),
1511 None,
1512 "{value:?}"
1513 );
1514 }
1515 }
1516
1517 fn hash_of(value: &OwnedValue) -> u64 {
1518 let mut hasher = rustc_hash::FxHasher::default();
1519 value.hash(&mut hasher);
1520 hasher.finish()
1521 }
1522
1523 #[test]
1524 fn test_owned_value() {
1525 let ladder: Vec<CellValue> = ladder().into_iter().flatten().collect();
1527 for value in &ladder {
1528 let owned = OwnedValue::from_cell(*value);
1529 let back = owned.as_cell();
1530 match (value, back) {
1531 (CellValue::Float(a), CellValue::Float(b)) => assert_eq!(a.to_bits(), b.to_bits()),
1532 _ => assert_eq!(*value, back),
1533 }
1534 assert_eq!(owned, owned.clone(), "{value:?} equals itself");
1535 }
1536
1537 let check = |a: &OwnedValue, b: &OwnedValue| {
1540 let equal = a.cmp_total(b).is_eq();
1541 assert_eq!(a == b, equal, "{a:?} and {b:?}");
1542 if equal {
1543 assert_eq!(hash_of(a), hash_of(b), "{a:?} and {b:?} hash apart");
1544 }
1545 };
1546 let owned: Vec<OwnedValue> = ladder.iter().map(|v| OwnedValue::from_cell(*v)).collect();
1547 for a in &owned {
1548 for b in &owned {
1549 check(a, b);
1550 }
1551 }
1552 let mut rng = fastrand::Rng::with_seed(26);
1553 let random = |rng: &mut fastrand::Rng| match rng.u8(0..6) {
1554 0 => owned[rng.usize(..owned.len())].clone(),
1555 1 => OwnedValue::Int(rng.i64(-3..=3)),
1556 2 => OwnedValue::Float(f64::from(rng.i8(-6..=6)) / 2.0),
1557 3 => OwnedValue::Float([0.0, -0.0, f64::NAN, -f64::NAN][rng.usize(..4)]),
1558 4 => OwnedValue::Text(["", "a", "b"][rng.usize(..3)].into()),
1559 _ => [OwnedValue::Empty, OwnedValue::Bool(rng.bool())][rng.usize(..2)].clone(),
1560 };
1561 for _ in 0..10_000 {
1562 let (a, b) = (random(&mut rng), random(&mut rng));
1563 check(&a, &b);
1564 }
1565
1566 use OwnedValue::{Float, Int};
1568 assert_eq!(Int(1), Float(1.0));
1569 assert_eq!(hash_of(&Int(1)), hash_of(&Float(1.0)));
1570 assert_eq!(Int(i64::MIN), Float(-9_223_372_036_854_775_808.0));
1571 assert_ne!(Float(-0.0), Float(0.0));
1572 assert_eq!(Int(0), Float(0.0));
1573 assert_ne!(Int(0), Float(-0.0));
1574 assert_eq!(Float(f64::NAN), Float(f64::NAN));
1575 assert_ne!(Float(f64::NAN), Float(-f64::NAN));
1576 assert_ne!(Int(1), Float(1.5));
1577 let zeros = [Int(0), Float(-0.0), Float(0.0)];
1579 for a in &zeros {
1580 for b in &zeros {
1581 for c in &zeros {
1582 if a == b && b == c {
1583 assert_eq!(a, c, "{a:?}, {b:?}, {c:?}");
1584 }
1585 }
1586 }
1587 }
1588 let set = rustc_hash::FxHashSet::from_iter(zeros);
1589 assert_eq!(set.len(), 2, "-0.0 and 0 are two keys");
1590 }
1591
1592 #[test]
1593 fn test_owned_value_serde() {
1594 use OwnedValue::{Bool, Empty, Float, Int, Text};
1595 for (value, json) in [
1596 (Empty, "null"),
1597 (Bool(true), "true"),
1598 (Int(-12), "-12"),
1599 (Int(i64::MAX), "9223372036854775807"),
1600 (Float(1.0), "1.0"),
1601 (Float(-0.0), "-0.0"),
1602 (Float(12.5), "12.5"),
1603 (Float(1e20), "1e+20"),
1604 (Text("AAPL".into()), r#""AAPL""#),
1605 ] {
1606 assert_eq!(serde_json::to_string(&value).unwrap(), json);
1607 let back: OwnedValue = serde_json::from_str(json).unwrap();
1608 assert_eq!(back, value, "{json}");
1609 assert_eq!(
1611 std::mem::discriminant(&back),
1612 std::mem::discriminant(&value),
1613 "{json}"
1614 );
1615 }
1616 assert!(matches!(serde_json::from_str("1"), Ok(Int(1))));
1617 assert!(matches!(serde_json::from_str("1.0"), Ok(Float(1.0))));
1618 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1621 assert!(serde_json::to_string(&Float(value)).is_err(), "{value}");
1622 }
1623 assert!(serde_json::from_str::<OwnedValue>("9223372036854775808").is_err());
1624 assert!(serde_json::from_str::<OwnedValue>("[1]").is_err());
1625 }
1626
1627 #[test]
1628 fn test_change_direction_default() {
1629 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1630 struct Value;
1631
1632 impl GridColumns for Value {
1633 fn all() -> &'static [Self] {
1634 &[Self]
1635 }
1636
1637 fn id(self) -> &'static str {
1638 "value"
1639 }
1640
1641 fn ix(self) -> ColumnIx {
1642 ColumnIx::new(0)
1643 }
1644 }
1645
1646 struct Row(CellValue<'static>);
1647
1648 impl GridRow for Row {
1649 type Key = u8;
1650 type Column = Value;
1651
1652 fn key(&self) -> &u8 {
1653 &0
1654 }
1655
1656 fn value(&self, _: Value) -> CellValue<'_> {
1657 self.0
1658 }
1659 }
1660
1661 use CellValue::{Bool, Empty, Float, Int, Text};
1662 use ChangeDirection::{Down, Up};
1663 for (before, after, expected) in [
1664 (Int(1), Int(2), Some(Up)),
1665 (Int(2), Int(1), Some(Down)),
1666 (Float(1.5), Float(0.5), Some(Down)),
1667 (Float(0.5), Float(1.5), Some(Up)),
1668 (Int(1), Float(1.5), Some(Up)),
1669 (Float(1.5), Int(1), Some(Down)),
1670 (Int(1), Float(1.0), None),
1671 (Int(3), Int(3), None),
1672 (Text("a"), Text("b"), None),
1673 (Bool(false), Bool(true), None),
1674 (Empty, Int(1), None),
1675 (Int(1), Text("2"), None),
1676 ] {
1677 assert_eq!(
1678 Row(after).change_direction(&Row(before), Value),
1679 expected,
1680 "{before:?} → {after:?}"
1681 );
1682 }
1683 assert_eq!(Row(Int(1)).parent_key(), None);
1684 }
1685
1686 #[cfg(feature = "decimal")]
1688 mod decimal_values {
1689 use super::*;
1690
1691 fn dec(mantissa: i128, scale: u32) -> Decimal {
1693 Decimal::from_i128_with_scale(mantissa, scale)
1694 }
1695
1696 fn negative_zero(scale: u32) -> Decimal {
1698 let mut zero = dec(0, scale);
1699 zero.set_sign_negative(true);
1700 zero
1701 }
1702
1703 fn decimal_ladder() -> Vec<Vec<CellValue<'static>>> {
1710 use CellValue::{Bool, Decimal, Empty, Float, Int, Text};
1711 let p53 = 1i64 << 53;
1712 let p63 = 9_223_372_036_854_775_808.0f64;
1713 let p96 = 79_228_162_514_264_337_593_543_950_336.0f64;
1714 let below_p96 = p96.next_down();
1715 let below_p96_int = (1i128 << 96) - (1 << 43);
1716 let max = rust_decimal::Decimal::MAX;
1717 vec![
1718 vec![Empty],
1719 vec![Bool(true)],
1720 vec![Float(f64::from_bits(0xFFF8_0000_0000_0000))],
1721 vec![Float(f64::NEG_INFINITY)],
1722 vec![Float(f64::MIN)],
1723 vec![Float(-p96)],
1724 vec![Decimal(rust_decimal::Decimal::MIN)],
1725 vec![Float(-below_p96), Decimal(dec(-below_p96_int, 0))],
1726 vec![Int(i64::MIN), Float(-p63), Decimal(dec(i64::MIN.into(), 0))],
1727 vec![Decimal(dec(i128::from(i64::MIN) * 10 + 5, 1))],
1728 vec![Int(i64::MIN + 1), Decimal(dec((i64::MIN + 1).into(), 0))],
1729 vec![Int(-p53 - 1), Decimal(dec(i128::from(-p53 - 1) * 100, 2))],
1730 vec![
1731 Int(-p53),
1732 Float(-p53 as f64),
1733 Decimal(dec((-p53).into(), 0)),
1734 ],
1735 vec![Float(-1.5), Decimal(dec(-150, 2))],
1736 vec![
1737 Int(-1),
1738 Float(-1.0),
1739 Decimal(dec(-1, 0)),
1740 Decimal(dec(-1000, 3)),
1741 ],
1742 vec![Float(-0.5), Decimal(dec(-5, 1)), Decimal(dec(-50, 2))],
1743 vec![Float(-0.1)],
1744 vec![Decimal(dec(-1, 1))],
1745 vec![Decimal(dec(-1, 28))],
1746 vec![Float(-1e-28)],
1748 vec![Float(-f64::MIN_POSITIVE)],
1749 vec![Float(-f64::from_bits(1))],
1750 vec![Float(-0.0)],
1751 vec![
1752 Int(0),
1753 Float(0.0),
1754 Decimal(dec(0, 0)),
1755 Decimal(dec(0, 2)),
1756 Decimal(-rust_decimal::Decimal::ZERO),
1757 Decimal(negative_zero(28)),
1758 ],
1759 vec![Float(f64::from_bits(1))],
1760 vec![Float(f64::MIN_POSITIVE)],
1761 vec![Float(1e-28)],
1762 vec![Decimal(dec(1, 28))],
1763 vec![Decimal(dec(1, 1)), Decimal(dec(10, 2))],
1764 vec![Float(0.1)],
1765 vec![Float(0.5), Decimal(dec(5, 1)), Decimal(dec(5000, 4))],
1766 vec![Float(1f64.next_down())],
1767 vec![Decimal(dec(9_999_999_999_999_999_999_999_999_999, 28))],
1768 vec![Int(1), Float(1.0), Decimal(dec(1, 0)), Decimal(dec(100, 2))],
1769 vec![Decimal(dec(10_000_000_000_000_000_000_000_000_001, 28))],
1770 vec![Float(1f64.next_up())],
1771 vec![Float(1.5), Decimal(dec(15, 1))],
1772 vec![Decimal(dec(max.mantissa(), 28))],
1773 vec![Decimal(dec(123, 1)), Decimal(dec(1230, 2))],
1774 vec![
1775 Int(p53 - 1),
1776 Float((p53 - 1) as f64),
1777 Decimal(dec((p53 - 1).into(), 0)),
1778 ],
1779 vec![
1780 Int(p53),
1781 Float(p53 as f64),
1782 Decimal(dec(i128::from(p53) * 10, 1)),
1783 ],
1784 vec![Decimal(dec(i128::from(p53) * 10 + 5, 1))],
1785 vec![Int(p53 + 1), Decimal(dec((p53 + 1).into(), 0))],
1786 vec![
1787 Int(p53 + 2),
1788 Float((p53 + 2) as f64),
1789 Decimal(dec((p53 + 2).into(), 0)),
1790 ],
1791 vec![Int(i64::MAX), Decimal(dec(i64::MAX.into(), 0))],
1792 vec![Decimal(dec(i128::from(i64::MAX) * 10 + 5, 1))],
1793 vec![
1794 Float(p63),
1795 Decimal(dec(1 << 63, 0)),
1796 Decimal(dec(10 << 63, 1)),
1797 ],
1798 vec![Decimal(dec((1 << 63) + 1, 0))],
1799 vec![Float(below_p96), Decimal(dec(below_p96_int, 0))],
1800 vec![Decimal(max)],
1801 vec![Float(p96)],
1802 vec![Float(f64::MAX)],
1803 vec![Float(f64::INFINITY)],
1804 vec![Float(f64::from_bits(0x7FF8_0000_0000_0000))],
1805 vec![Text("0")],
1806 ]
1807 }
1808
1809 fn random_decimal(rng: &mut fastrand::Rng) -> Decimal {
1814 match rng.u8(0..4) {
1815 0 => dec(rng.i64(-30..=30).into(), rng.u32(0..=2)),
1816 1 => negative_zero(rng.u32(0..=28)),
1817 2 => Decimal::from_f64_retain(f64::from(rng.i32(..)) / 1024.0).unwrap_or_default(),
1818 _ => {
1819 let bits = rng.u32(1..=96);
1820 let magnitude = (rng.u128(..) >> (128 - bits)) as i128;
1821 dec(
1822 if rng.bool() { -magnitude } else { magnitude },
1823 rng.u32(0..=28),
1824 )
1825 }
1826 }
1827 }
1828
1829 #[test]
1830 fn test_decimal_display_and_order() {
1831 let mut buffer = String::with_capacity(64);
1833 let storage = buffer.as_ptr();
1834 for (value, expected) in [
1835 (dec(1230, 2), "12.30"),
1836 (dec(-5, 2), "-0.05"),
1837 (dec(0, 2), "0.00"),
1838 (dec(7, 0), "7"),
1839 (Decimal::MAX, "79228162514264337593543950335"),
1840 (dec(1, 28), "0.0000000000000000000000000001"),
1841 (dec(-1, 0), "-1"),
1843 ] {
1844 let text = display_text(CellValue::Decimal(value), &mut buffer);
1845 assert_eq!(text, expected, "{value:?}");
1846 assert!(may_occur_in_number_text(text), "{text}");
1847 }
1848 assert_eq!(buffer.as_ptr(), storage);
1849 assert!(CellValue::Decimal(dec(1, 0)).is_number());
1850 assert_eq!(
1853 CellValue::Decimal(dec(10, 1)),
1854 CellValue::Decimal(dec(100, 2))
1855 );
1856 assert_ne!(CellValue::Decimal(dec(1, 0)), CellValue::Int(1));
1857
1858 let ladder = decimal_ladder();
1860 for (rung, values) in ladder.iter().enumerate() {
1861 for (other_rung, others) in ladder.iter().enumerate() {
1862 for a in values {
1863 for b in others {
1864 assert_eq!(
1865 a.cmp_total(b),
1866 rung.cmp(&other_rung),
1867 "{a:?} against {b:?}"
1868 );
1869 }
1870 }
1871 }
1872 }
1873 let values: Vec<CellValue> = ladder.into_iter().flatten().collect();
1874 for a in &values {
1875 for b in &values {
1876 for c in &values {
1877 check_total_order(a, b, c);
1878 }
1879 }
1880 }
1881
1882 let mut rng = fastrand::Rng::with_seed(0xdec1);
1885 let random = |rng: &mut fastrand::Rng| match rng.u8(0..8) {
1886 0 => values[rng.usize(..values.len())],
1887 1 => CellValue::Int(rng.i64(-3..=3)),
1888 2 => CellValue::Float(f64::from(rng.i8(-6..=6)) / 2.0),
1889 3 => CellValue::Float([0.0, -0.0][rng.usize(..2)]),
1890 4 => CellValue::Float(f64::from(rng.i32(..)) / 1024.0),
1891 5 => CellValue::Float(f64::from_bits(rng.u64(..))),
1892 _ => CellValue::Decimal(random_decimal(rng)),
1893 };
1894 for _ in 0..200_000 {
1895 let (a, b, c) = (random(&mut rng), random(&mut rng), random(&mut rng));
1896 check_total_order(&a, &b, &c);
1897 }
1898
1899 for _ in 0..20_000 {
1903 let float = f64::from(rng.u32(..)) * 10f64.powi(-rng.i32(10..=40));
1904 let float = if rng.bool() { float } else { -float };
1905 let Some(near) = Decimal::from_f64_retain(float) else {
1906 continue;
1907 };
1908 let near = CellValue::Decimal(near);
1909 for other in [float.next_down(), float.next_up()] {
1910 let (float, other) = (CellValue::Float(float), CellValue::Float(other));
1911 for (a, b, c) in [
1912 (&near, &float, &other),
1913 (&float, &near, &other),
1914 (&float, &other, &near),
1915 ] {
1916 check_total_order(a, b, c);
1917 }
1918 }
1919 }
1920
1921 let mut mix: Vec<CellValue> = (0..20_000).map(|_| random(&mut rng)).collect();
1923 mix.sort_by(|a, b| a.cmp_total(b));
1924 for pair in mix.windows(2) {
1925 assert_ne!(pair[0].cmp_total(&pair[1]), Ordering::Greater, "{pair:?}");
1926 }
1927 }
1928
1929 #[test]
1930 fn test_decimal_owned_value() {
1931 use OwnedValue::{Decimal as Dec, Float, Int};
1932 let check = |a: &OwnedValue, b: &OwnedValue| {
1933 let equal = a.cmp_total(b).is_eq();
1934 assert_eq!(a == b, equal, "{a:?} and {b:?}");
1935 if equal {
1936 assert_eq!(hash_of(a), hash_of(b), "{a:?} and {b:?} hash apart");
1937 }
1938 };
1939 let owned: Vec<OwnedValue> = decimal_ladder()
1940 .into_iter()
1941 .flatten()
1942 .map(OwnedValue::from_cell)
1943 .collect();
1944 for a in &owned {
1945 assert!(
1946 matches!((a, a.as_cell()), (Dec(a), CellValue::Decimal(b)) if a.scale() == b.scale())
1947 || !matches!(a, Dec(_)),
1948 "{a:?} keeps its scale"
1949 );
1950 for b in &owned {
1951 check(a, b);
1952 }
1953 }
1954 let mut rng = fastrand::Rng::with_seed(0xdec2);
1955 let random = |rng: &mut fastrand::Rng| match rng.u8(0..5) {
1956 0 => owned[rng.usize(..owned.len())].clone(),
1957 1 => Int(rng.i64(-30..=30)),
1958 2 => Float(f64::from(rng.i32(..)) / 1024.0),
1959 3 => Float(f64::from(rng.i8(-6..=6)) / 2.0),
1960 _ => Dec(random_decimal(rng)),
1961 };
1962 for _ in 0..50_000 {
1963 let (a, b) = (random(&mut rng), random(&mut rng));
1964 check(&a, &b);
1965 }
1966
1967 assert_eq!(Dec(dec(100, 2)), Int(1));
1970 assert_eq!(Dec(dec(100, 2)), Float(1.0));
1971 assert_eq!(Dec(dec(5, 1)), Float(0.5));
1972 assert_ne!(Dec(dec(1, 1)), Float(0.1));
1973 assert_eq!(hash_of(&Dec(dec(150, 2))), hash_of(&Dec(dec(15, 1))));
1974 let zeros = [
1975 Int(0),
1976 Float(0.0),
1977 Float(-0.0),
1978 Dec(dec(0, 0)),
1979 Dec(dec(0, 3)),
1980 Dec(negative_zero(2)),
1981 ];
1982 for a in &zeros {
1983 for b in &zeros {
1984 check(a, b);
1985 }
1986 }
1987 let set = rustc_hash::FxHashSet::from_iter(zeros);
1988 assert_eq!(set.len(), 2, "-0.0 and 0 are two keys");
1989 }
1990
1991 #[test]
1992 fn test_decimal_owned_value_serde() {
1993 for (value, json) in [
1994 (dec(1230, 2), r#"{"decimal":"12.30"}"#),
1995 (dec(-5, 2), r#"{"decimal":"-0.05"}"#),
1996 (dec(0, 3), r#"{"decimal":"0.000"}"#),
1997 (
1998 Decimal::MAX,
1999 r#"{"decimal":"79228162514264337593543950335"}"#,
2000 ),
2001 (
2002 dec(1, 28),
2003 r#"{"decimal":"0.0000000000000000000000000001"}"#,
2004 ),
2005 ] {
2006 let owned = OwnedValue::Decimal(value);
2007 assert_eq!(serde_json::to_string(&owned).unwrap(), json);
2008 let back: OwnedValue = serde_json::from_str(json).unwrap();
2009 assert!(
2011 matches!(back, OwnedValue::Decimal(back) if back.to_string() == value.to_string()),
2012 "{json}: {back:?}"
2013 );
2014 }
2015 let pretty: OwnedValue = serde_json::from_str(r#" { "decimal" : "1.50" } "#).unwrap();
2016 assert!(matches!(pretty, OwnedValue::Decimal(value) if value.to_string() == "1.50"));
2017 for json in [
2021 r#"{"decimal":12.3}"#,
2022 r#"{"decimal":"12,30"}"#,
2023 r#"{"decimal":"1e3"}"#,
2024 r#"{"decimal":"0.00000000000000000000000000001"}"#,
2025 r#"{"decimal":"79228162514264337593543950336"}"#,
2026 r#"{"money":"12.30"}"#,
2027 r#"{}"#,
2028 r#"{"decimal":"1","decimal":"2"}"#,
2029 ] {
2030 assert!(serde_json::from_str::<OwnedValue>(json).is_err(), "{json}");
2031 }
2032 assert!(matches!(serde_json::from_str("1"), Ok(OwnedValue::Int(1))));
2034 assert!(
2035 matches!(serde_json::from_str(r#""12.30""#), Ok(OwnedValue::Text(text)) if &*text == "12.30")
2036 );
2037 }
2038
2039 #[test]
2040 fn test_decimal_change_direction() {
2041 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2042 struct Amount;
2043
2044 impl GridColumns for Amount {
2045 fn all() -> &'static [Self] {
2046 &[Self]
2047 }
2048
2049 fn id(self) -> &'static str {
2050 "amount"
2051 }
2052
2053 fn ix(self) -> ColumnIx {
2054 ColumnIx::new(0)
2055 }
2056 }
2057
2058 struct Row(CellValue<'static>);
2059
2060 impl GridRow for Row {
2061 type Key = u8;
2062 type Column = Amount;
2063
2064 fn key(&self) -> &u8 {
2065 &0
2066 }
2067
2068 fn value(&self, _: Amount) -> CellValue<'_> {
2069 self.0
2070 }
2071 }
2072
2073 use CellValue::{Decimal as Dec, Float, Int, Text};
2074 use ChangeDirection::{Down, Up};
2075 for (before, after, expected) in [
2076 (Dec(dec(1230, 2)), Dec(dec(1231, 2)), Some(Up)),
2077 (Dec(dec(1230, 2)), Dec(dec(-1, 0)), Some(Down)),
2078 (Dec(dec(10, 1)), Dec(dec(100, 2)), None),
2079 (Int(1), Dec(dec(100, 2)), None),
2080 (Float(0.1), Dec(dec(1, 1)), Some(Down)),
2081 (Dec(dec(5, 1)), Float(0.75), Some(Up)),
2082 (Text("1"), Dec(dec(1, 0)), None),
2083 ] {
2084 assert_eq!(
2085 Row(after).change_direction(&Row(before), Amount),
2086 expected,
2087 "{before:?} → {after:?}"
2088 );
2089 }
2090 let mut changed = ColumnSet::EMPTY;
2092 Row(Dec(dec(100, 2))).changed_columns(&Row(Dec(dec(1, 0))), &mut changed);
2093 assert!(changed.is_empty());
2094 }
2095 }
2096
2097 mod new_values {
2099 use chrono::TimeZone as _;
2100
2101 use super::*;
2102
2103 #[derive(Debug)]
2105 struct Severity(u8);
2106
2107 impl GridValue for Severity {
2108 fn cmp_value(&self, other: &dyn GridValue) -> Ordering {
2109 match other.downcast_ref::<Severity>() {
2110 Some(other) => self.0.cmp(&other.0),
2111 None => type_order::<Self>(other),
2112 }
2113 }
2114
2115 fn write_text(&self, out: &mut String) {
2116 out.push_str(["Critical", "Warning", "Info"][usize::from(self.0)]);
2117 }
2118 }
2119
2120 #[derive(Debug)]
2123 struct Tenor(u32);
2124
2125 impl GridValue for Tenor {
2126 fn cmp_value(&self, other: &dyn GridValue) -> Ordering {
2127 match other.downcast_ref::<Tenor>() {
2128 Some(other) => self.0.cmp(&other.0),
2129 None => type_order::<Self>(other),
2130 }
2131 }
2132
2133 fn write_text(&self, out: &mut String) {
2134 let _ = write!(out, "{}M", self.0 / 30);
2135 }
2136
2137 fn export_value(&self) -> Option<CellValue<'_>> {
2138 Some(CellValue::Int(self.0.into()))
2139 }
2140
2141 fn to_owned_value(&self) -> OwnedValue {
2142 OwnedValue::Int(self.0.into())
2143 }
2144 }
2145
2146 fn type_order<T: GridValue>(other: &dyn GridValue) -> Ordering {
2148 std::any::TypeId::of::<T>().cmp(&other.as_any().type_id())
2149 }
2150
2151 static SEVERITY: [Severity; 4] = [Severity(0), Severity(1), Severity(1), Severity(2)];
2152 static TENOR: [Tenor; 2] = [Tenor(30), Tenor(90)];
2153
2154 fn at(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime<Utc> {
2155 Utc.with_ymd_and_hms(year, month, day, hour, min, sec)
2156 .unwrap()
2157 }
2158
2159 fn icon(path: &'static str, text: &'static str) -> CellValue<'static> {
2160 CellValue::Icon(IconValue::new(path, text))
2161 }
2162
2163 fn image(id: &'static str, text: &'static str) -> CellValue<'static> {
2164 CellValue::Image(ImageValue::new(id, text))
2165 }
2166
2167 fn new_values_ladder() -> Vec<Vec<CellValue<'static>>> {
2171 use CellValue::{Custom, Text, Timestamp};
2172 let mut ladder = ladder();
2173 let text_at = ladder
2174 .iter()
2175 .position(|rung| matches!(rung[0], Text(_)))
2176 .unwrap();
2177 for (offset, time) in [
2178 DateTime::<Utc>::MIN_UTC,
2179 at(1969, 12, 31, 23, 59, 59),
2180 DateTime::UNIX_EPOCH,
2181 at(2026, 11, 4, 12, 34, 56),
2182 at(2026, 11, 4, 12, 34, 56) + chrono::Duration::nanoseconds(1),
2183 DateTime::<Utc>::MAX_UTC,
2184 ]
2185 .into_iter()
2186 .enumerate()
2187 {
2188 ladder.insert(text_at + offset, vec![Timestamp(time)]);
2189 }
2190 for rung in &mut ladder {
2191 match rung[0] {
2192 Text("") => rung.push(icon("icons/blank.svg", "")),
2193 Text("a") => rung.extend([
2194 icon("icons/a.svg", "a"),
2195 icon("icons/b.svg", "a"),
2196 image("images/a.png", "a"),
2197 ]),
2198 _ => {}
2199 }
2200 }
2201 ladder.push(vec![icon("icons/z.svg", "z"), image("images/z.svg", "z")]);
2202 let severities = [
2203 vec![Custom(&SEVERITY[0])],
2204 vec![Custom(&SEVERITY[1]), Custom(&SEVERITY[2])],
2205 vec![Custom(&SEVERITY[3])],
2206 ];
2207 let tenors = [vec![Custom(&TENOR[0])], vec![Custom(&TENOR[1])]];
2208 if type_order::<Severity>(&TENOR[0]).is_lt() {
2210 ladder.extend(severities.into_iter().chain(tenors));
2211 } else {
2212 ladder.extend(tenors.into_iter().chain(severities));
2213 }
2214 ladder
2215 }
2216
2217 #[test]
2218 fn test_new_values_order() {
2219 let ladder = new_values_ladder();
2220 for (rung, values) in ladder.iter().enumerate() {
2221 for (other_rung, others) in ladder.iter().enumerate() {
2222 for a in values {
2223 for b in others {
2224 assert_eq!(
2225 a.cmp_total(b),
2226 rung.cmp(&other_rung),
2227 "{a:?} against {b:?}"
2228 );
2229 }
2230 }
2231 }
2232 }
2233 let values: Vec<CellValue> = ladder.into_iter().flatten().collect();
2234 for a in &values {
2235 for b in &values {
2236 for c in &values {
2237 check_total_order(a, b, c);
2238 }
2239 }
2240 }
2241 assert_eq!(
2243 icon("icons/z.svg", "a").cmp_total(&CellValue::Text("a")),
2244 Ordering::Equal
2245 );
2246 assert_eq!(
2247 icon("icons/a.svg", "b").cmp_total(&icon("icons/z.svg", "a")),
2248 Ordering::Greater
2249 );
2250 assert_eq!(
2252 image("images/z.png", "a").cmp_total(&CellValue::Text("a")),
2253 Ordering::Equal
2254 );
2255 assert_eq!(
2256 image("images/a.png", "b").cmp_total(&icon("icons/z.svg", "a")),
2257 Ordering::Greater
2258 );
2259 let mut rng = fastrand::Rng::with_seed(0x5e7);
2261 let mut mix: Vec<CellValue> = (0..5_000)
2262 .map(|_| values[rng.usize(..values.len())])
2263 .collect();
2264 mix.sort_by(|a, b| a.cmp_total(b));
2265 for pair in mix.windows(2) {
2266 assert_ne!(pair[0].cmp_total(&pair[1]), Ordering::Greater, "{pair:?}");
2267 }
2268 for value in [
2270 CellValue::Timestamp(DateTime::UNIX_EPOCH),
2271 icon("icons/a.svg", "1"),
2272 image("images/a.png", "1"),
2273 CellValue::Custom(&TENOR[0]),
2274 ] {
2275 assert!(!value.is_number(), "{value:?}");
2276 }
2277 }
2278
2279 #[test]
2280 fn test_new_values_text() {
2281 let mut buffer = String::with_capacity(32);
2282 let (storage, capacity) = (buffer.as_ptr(), buffer.capacity());
2283 for _ in 0..2 {
2284 for (value, expected) in [
2285 (
2286 CellValue::Timestamp(at(2026, 11, 4, 12, 34, 56)),
2287 "2026-11-04 12:34:56",
2288 ),
2289 (
2290 CellValue::Timestamp(at(2026, 11, 4, 0, 0, 0)),
2291 "2026-11-04 00:00:00",
2292 ),
2293 (
2294 CellValue::Timestamp(at(2024, 2, 29, 23, 59, 59)),
2295 "2024-02-29 23:59:59",
2296 ),
2297 (
2299 CellValue::Timestamp(
2300 at(1999, 12, 31, 23, 59, 59) + chrono::Duration::milliseconds(999),
2301 ),
2302 "1999-12-31 23:59:59",
2303 ),
2304 (icon("icons/check.svg", "Filled"), "Filled"),
2305 (image("images/avatar.png", "Ada"), "Ada"),
2306 (CellValue::Custom(&SEVERITY[0]), "Critical"),
2307 (CellValue::Custom(&TENOR[1]), "3M"),
2308 (CellValue::Custom(&SEVERITY[3]), "Info"),
2310 ] {
2311 assert_eq!(display_text(value, &mut buffer), expected, "{value:?}");
2312 }
2313 }
2314 assert_eq!((buffer.as_ptr(), buffer.capacity()), (storage, capacity));
2317
2318 let row_text = String::from("Отменена");
2320 let borrowed = CellValue::Icon(IconValue::new("icons/x.svg", &row_text));
2321 assert!(std::ptr::eq(
2322 display_text(borrowed, &mut buffer),
2323 row_text.as_str()
2324 ));
2325 let value = IconValue::new("icons/x.svg", "x");
2326 assert_eq!((value.path(), value.text()), ("icons/x.svg", "x"));
2327 let value = ImageValue::new("images/x.png", "x");
2328 assert_eq!((value.id(), value.text()), ("images/x.png", "x"));
2329 assert_eq!(
2330 OwnedValue::from_cell(CellValue::Image(value)),
2331 OwnedValue::Text("x".into())
2332 );
2333 }
2334
2335 #[test]
2336 fn test_new_values_eq_and_diff() {
2337 use CellValue::{Custom, Float, Int, Text, Timestamp};
2338 assert_ne!(Float(f64::NAN), Float(f64::NAN));
2340 assert_eq!(Float(-0.0), Float(0.0));
2341 assert_ne!(Int(1), Float(1.0));
2342 assert_eq!(Text("a"), Text("a"));
2343 assert_ne!(CellValue::Empty, CellValue::Bool(false));
2344 let noon = at(2026, 11, 4, 12, 0, 0);
2347 assert_eq!(Timestamp(noon), Timestamp(noon));
2348 assert_ne!(
2349 Timestamp(noon),
2350 Timestamp(noon + chrono::Duration::seconds(1))
2351 );
2352 assert_eq!(icon("icons/a.svg", "a"), icon("icons/a.svg", "a"));
2353 assert_ne!(icon("icons/a.svg", "a"), icon("icons/b.svg", "a"));
2354 assert_ne!(icon("icons/a.svg", "a"), icon("icons/a.svg", "b"));
2355 assert_ne!(icon("icons/a.svg", "a"), Text("a"));
2356 assert_eq!(Custom(&SEVERITY[1]), Custom(&SEVERITY[2]));
2357 assert_ne!(Custom(&SEVERITY[0]), Custom(&SEVERITY[1]));
2358 assert_ne!(Custom(&SEVERITY[0]), Custom(&TENOR[0]));
2359 assert_ne!(Custom(&SEVERITY[0]), Text("Critical"));
2360
2361 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2363 enum Column {
2364 Placed,
2365 Status,
2366 Severity,
2367 }
2368
2369 impl GridColumns for Column {
2370 fn all() -> &'static [Self] {
2371 &[Self::Placed, Self::Status, Self::Severity]
2372 }
2373
2374 fn id(self) -> &'static str {
2375 match self {
2376 Self::Placed => "placed",
2377 Self::Status => "status",
2378 Self::Severity => "severity",
2379 }
2380 }
2381
2382 fn ix(self) -> ColumnIx {
2383 ColumnIx::new(self as u16)
2384 }
2385 }
2386
2387 struct Row {
2388 placed: DateTime<Utc>,
2389 status: (&'static str, &'static str),
2390 severity: &'static Severity,
2391 }
2392
2393 impl GridRow for Row {
2394 type Key = u8;
2395 type Column = Column;
2396
2397 fn key(&self) -> &u8 {
2398 &0
2399 }
2400
2401 fn value(&self, column: Column) -> CellValue<'_> {
2402 match column {
2403 Column::Placed => Timestamp(self.placed),
2404 Column::Status => {
2405 CellValue::Icon(IconValue::new(self.status.0, self.status.1))
2406 }
2407 Column::Severity => Custom(self.severity),
2408 }
2409 }
2410 }
2411
2412 let before = Row {
2413 placed: noon,
2414 status: ("icons/clock.svg", "Working"),
2415 severity: &SEVERITY[1],
2416 };
2417 let changes = |after: Row| {
2418 let mut changed = ColumnSet::EMPTY;
2419 after.changed_columns(&before, &mut changed);
2420 Column::all()
2421 .iter()
2422 .copied()
2423 .filter(|column| changed.contains(column.ix()))
2424 .collect::<Vec<_>>()
2425 };
2426 assert_eq!(
2427 changes(Row {
2428 severity: &SEVERITY[2],
2429 ..before
2430 }),
2431 []
2432 );
2433 assert_eq!(
2434 changes(Row {
2435 placed: noon + chrono::Duration::milliseconds(1),
2436 ..before
2437 }),
2438 [Column::Placed]
2439 );
2440 assert_eq!(
2441 changes(Row {
2442 status: ("icons/check.svg", "Working"),
2443 ..before
2444 }),
2445 [Column::Status]
2446 );
2447 assert_eq!(
2448 changes(Row {
2449 status: ("icons/clock.svg", "Filled"),
2450 severity: &SEVERITY[0],
2451 ..before
2452 }),
2453 [Column::Status, Column::Severity]
2454 );
2455 let later = Row {
2457 placed: noon + chrono::Duration::hours(1),
2458 ..before
2459 };
2460 assert_eq!(later.change_direction(&before, Column::Placed), None);
2461 }
2462
2463 #[test]
2464 fn test_owned_timestamp_serde() {
2465 let time = at(2026, 11, 4, 12, 34, 56);
2466 for (value, json) in [
2467 (time, r#"{"timestamp":"2026-11-04T12:34:56Z"}"#),
2468 (
2469 time + chrono::Duration::milliseconds(250),
2470 r#"{"timestamp":"2026-11-04T12:34:56.250Z"}"#,
2471 ),
2472 (
2473 time + chrono::Duration::nanoseconds(1),
2474 r#"{"timestamp":"2026-11-04T12:34:56.000000001Z"}"#,
2475 ),
2476 (
2477 DateTime::UNIX_EPOCH,
2478 r#"{"timestamp":"1970-01-01T00:00:00Z"}"#,
2479 ),
2480 (
2481 at(0, 1, 1, 0, 0, 0),
2482 r#"{"timestamp":"0000-01-01T00:00:00Z"}"#,
2483 ),
2484 ] {
2485 let owned = OwnedValue::Timestamp(value);
2486 assert_eq!(serde_json::to_string(&owned).unwrap(), json);
2487 let back: OwnedValue = serde_json::from_str(json).unwrap();
2488 assert!(
2489 matches!(back, OwnedValue::Timestamp(back) if back == value),
2490 "{json}"
2491 );
2492 }
2493 let moscow: OwnedValue =
2495 serde_json::from_str(r#" { "timestamp" : "2026-11-04T15:34:56+03:00" } "#).unwrap();
2496 assert_eq!(moscow, OwnedValue::Timestamp(time));
2497 assert_eq!(hash_of(&moscow), hash_of(&OwnedValue::Timestamp(time)));
2498 assert!(matches!(
2500 serde_json::from_str(r#""2026-11-04T12:34:56Z""#),
2501 Ok(OwnedValue::Text(text)) if &*text == "2026-11-04T12:34:56Z"
2502 ));
2503 for json in [
2506 r#"{"timestamp":"x"}"#,
2507 r#"{"timestamp":"2026-11-04 12:34:56"}"#,
2508 r#"{"timestamp":"2026-11-04"}"#,
2509 r#"{"timestamp":1762259696}"#,
2510 r#"{"time":"2026-11-04T12:34:56Z"}"#,
2511 r#"{}"#,
2512 r#"{"timestamp":"2026-11-04T12:34:56Z","timestamp":"2026-11-04T12:34:56Z"}"#,
2513 ] {
2514 assert!(serde_json::from_str::<OwnedValue>(json).is_err(), "{json}");
2515 }
2516 for value in [DateTime::<Utc>::MAX_UTC, DateTime::<Utc>::MIN_UTC] {
2518 assert!(serde_json::to_string(&OwnedValue::Timestamp(value)).is_err());
2519 }
2520
2521 assert_eq!(
2524 OwnedValue::from_cell(CellValue::Timestamp(time)).as_cell(),
2525 CellValue::Timestamp(time)
2526 );
2527 assert!(matches!(
2528 OwnedValue::from_cell(icon("icons/check.svg", "Filled")),
2529 OwnedValue::Text(text) if &*text == "Filled"
2530 ));
2531 assert!(matches!(
2532 OwnedValue::from_cell(CellValue::Custom(&SEVERITY[0])),
2533 OwnedValue::Text(text) if &*text == "Critical"
2534 ));
2535 assert!(matches!(
2536 OwnedValue::from_cell(CellValue::Custom(&TENOR[1])),
2537 OwnedValue::Int(90)
2538 ));
2539 let owned: Vec<OwnedValue> = new_values_ladder()
2542 .into_iter()
2543 .flatten()
2544 .map(OwnedValue::from_cell)
2545 .collect();
2546 for a in &owned {
2547 for b in &owned {
2548 assert_eq!(a == b, a.cmp_total(b).is_eq(), "{a:?} and {b:?}");
2549 if a == b {
2550 assert_eq!(hash_of(a), hash_of(b), "{a:?} and {b:?} hash apart");
2551 }
2552 }
2553 }
2554 }
2555
2556 #[test]
2557 fn test_grid_value_downcast() {
2558 let value: &dyn GridValue = &TENOR[0];
2559 assert!(
2560 value
2561 .downcast_ref::<Tenor>()
2562 .is_some_and(|tenor| tenor.0 == 30)
2563 );
2564 assert!(value.downcast_ref::<Severity>().is_none());
2565 assert_eq!(value.as_any().type_id(), std::any::TypeId::of::<Tenor>());
2566 assert!(SEVERITY[0].export_value().is_none());
2568 }
2569 }
2570
2571 #[test]
2572 fn test_column_set_rank() {
2573 let mut set = ColumnSet::EMPTY;
2574 for ix in [1, 4, 127] {
2575 set.insert(ColumnIx::new(ix));
2576 }
2577 let ranks: Vec<usize> = [0, 1, 2, 4, 5, 127]
2578 .map(|ix| set.rank(ColumnIx::new(ix)))
2579 .to_vec();
2580 assert_eq!(ranks, [0, 0, 1, 1, 2, 2]);
2581 }
2582}