ion_rs/ion_data/
mod.rs

1mod ion_eq;
2mod ion_ord;
3
4use std::cmp::Ordering;
5use std::fmt::{Display, Formatter};
6use std::ops::Deref;
7
8pub(crate) use ion_eq::{ion_eq_bool, ion_eq_f64, IonEq};
9pub(crate) use ion_ord::{ion_cmp_bool, ion_cmp_f64, IonOrd};
10
11/// A wrapper for lifting Ion compatible data into using Ion-oriented comparisons (versus the Rust
12/// value semantics). This enables the default semantics to be what a Rust user expects for native
13/// values, but allows a user to opt-in to Ion's structural equivalence/order.
14///
15/// Equivalence with respect to Ion values means that if two Ion values, `X` and `Y`, are equivalent,
16/// they represent the same data and can be substituted for the other without loss of information.
17///
18/// Some types, such as [`Element`](crate::Element) and [`Value`](crate::element::Value) cannot be
19/// used as the key of a map because they adhere to Rust value semantics—these types cannot implement
20/// [`Eq`] because they include `NaN` as a possible value.
21///
22/// For use cases that are concerned with preserving the original Ion data, it is necessary to use
23/// Ion value equivalence. Many common use cases, such as writing unit tests for code that produces
24/// Ion, can be handled with [`IonData::eq()`].
25///
26/// For other use cases, such as using Ion data as the key of a map or passing Ion data to an
27/// algorithm that depends on [`Eq`], you can lift values using [`IonData::from()`].
28///
29/// Generally, anything that is treated as Ion data (e.g., [`Element`](crate::Element) and
30/// [`Value`](crate::element::Value)), can be converted to [`IonData`].
31///
32/// [`Hash`] and [`Ord`] are not guaranteed to be implemented for all [`IonData`], but when they are,
33/// they are required to be consistent with Ion structural equality (and [`Eq`]).
34///
35/// __WARNING__—The Ion specification does _not_ define a total ordering over all Ion values. Do
36/// not depend on getting any particular result from [Ord]. Use it only as an opaque total ordering
37/// over all [IonData]. _The algorithm used for [Ord] may change between versions._
38#[derive(Debug, Clone)]
39pub struct IonData<T>(T);
40
41impl<T: IonEq> IonData<T> {
42    /// Checks if two values are equal according to Ion's structural equivalence.
43    pub fn eq<R: Deref<Target = T>>(a: R, b: R) -> bool {
44        T::ion_eq(a.deref(), b.deref())
45    }
46
47    /// Unwraps the value.
48    pub fn into_inner(self) -> T {
49        self.0
50    }
51}
52
53impl<T: IonEq> PartialEq for IonData<T> {
54    fn eq(&self, other: &Self) -> bool {
55        self.0.ion_eq(&other.0)
56    }
57}
58
59impl<T: IonEq> Eq for IonData<T> {}
60
61impl<T: IonEq> From<T> for IonData<T> {
62    fn from(value: T) -> Self {
63        IonData(value)
64    }
65}
66
67impl<T: IonEq> AsRef<T> for IonData<T> {
68    fn as_ref(&self) -> &T {
69        &self.0
70    }
71}
72
73impl<T: Display> Display for IonData<T> {
74    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
75        Display::fmt(&self.0, f)
76    }
77}
78
79impl<T: IonEq + IonOrd> PartialOrd for IonData<T> {
80    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
81        Some(IonOrd::ion_cmp(&self.0, &other.0))
82    }
83}
84
85impl<T: IonEq + IonOrd> Ord for IonData<T> {
86    fn cmp(&self, other: &Self) -> Ordering {
87        IonOrd::ion_cmp(&self.0, &other.0)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use crate::ion_data::{IonEq, IonOrd};
94    use crate::{Element, IonData, Symbol};
95    use rstest::*;
96    use std::boxed::Box;
97    use std::fmt::Debug;
98    use std::pin::Pin;
99    use std::rc::Rc;
100    use std::sync::Arc;
101
102    /// These tests exist primarily to ensure that we don't break any trait implementations
103    /// needed to make this all work.
104    #[rstest]
105    #[case::value(|s| Element::read_one(s).unwrap().value().clone().into())]
106    #[case::symbol(|s| Symbol::from(s).into())]
107    #[case::element(|s| Element::read_one(s).unwrap().into() )]
108    #[case::rc_element(|s| Rc::new(Element::read_one(s).unwrap()).into() )]
109    #[case::vec_element(|s| Element::read_all(s).unwrap().into() )]
110    #[case::rc_vec_element(|s| Rc::new(Element::read_all(s).unwrap()).into() )]
111    #[case::box_pin_rc_vec_box_arc_element(|s| Box::new(Pin::new(Rc::new(vec![Box::new(Arc::new(Element::read_one(s).unwrap()))]))).into() )]
112    fn can_wrap_data<T: IonEq + IonOrd + Debug>(
113        #[case] the_fn: impl Fn(&'static str) -> IonData<T>,
114    ) {
115        let id1: IonData<_> = the_fn("nan");
116        let id2: IonData<_> = the_fn("nan");
117        assert_eq!(id1, id2);
118
119        let id1: IonData<_> = the_fn("1.00");
120        let id2: IonData<_> = the_fn("1.0");
121        assert_ne!(id1, id2); // Checks `Eq`
122        assert!(id1 > id2); // Checks `Ord`
123    }
124}