Skip to content

Commit

Permalink
Updates IonData to work with Deref impls and improves docs
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt committed Apr 25, 2023
1 parent ccf5fc4 commit fc15cdf
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 73 deletions.
8 changes: 8 additions & 0 deletions src/element/annotations.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::element::iterators::SymbolsIterator;
use crate::ion_data::IonOrd;
use crate::Symbol;
use std::cmp::Ordering;

/// An ordered sequence of symbols that convey additional, application-specific information about
/// their associated Ion value.
Expand Down Expand Up @@ -122,3 +124,9 @@ impl<'a> IntoIterator for &'a Annotations {
SymbolsIterator::new(self.symbols.as_slice())
}
}

impl IonOrd for Annotations {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.symbols.ion_cmp(&other.symbols)
}
}
9 changes: 5 additions & 4 deletions src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub use self::bytes::Bytes;
pub use annotations::Annotations;
pub use lob::{Blob, Clob};

use crate::types::float;
pub use list::List;
pub use r#struct::Struct;
pub use sequence::Sequence;
Expand All @@ -50,13 +51,13 @@ impl IonEq for Value {
use Value::*;
match (self, other) {
(Null(this), Null(that)) => this == that,
(Bool(this), Bool(that)) => this == that,
(Int(this), Int(that)) => this.ion_eq(that),
(Float(this), Float(that)) => this.ion_eq(that),
(Float(this), Float(that)) => float::Float::ion_eq_f64(this, that),
(Decimal(this), Decimal(that)) => this.ion_eq(that),
(Timestamp(this), Timestamp(that)) => this.ion_eq(that),
(String(this), String(that)) => this.ion_eq(that),
(Symbol(this), Symbol(that)) => this.ion_eq(that),
(Bool(this), Bool(that)) => this.ion_eq(that),
(Blob(this), Blob(that)) => this.ion_eq(that),
(Clob(this), Clob(that)) => this.ion_eq(that),
(SExp(this), SExp(that)) => this.ion_eq(that),
Expand Down Expand Up @@ -95,13 +96,13 @@ impl IonOrd for Value {
Ordering::Less
}
}
Bool(this) => compare!(Bool(that) => this.cmp(that)),
Int(this) => compare!(Int(that) => this.ion_cmp(that)),
Float(this) => compare!(Float(that) => this.ion_cmp(that)),
Float(this) => compare!(Float(that) => float::Float::ion_cmp_f64(this, that)),
Decimal(this) => compare!(Decimal(that) => this.ion_cmp(that)),
Timestamp(this) => compare!(Timestamp(that) => this.ion_cmp(that)),
String(this) => compare!(String(that) => this.ion_cmp(that)),
Symbol(this) => compare!(Symbol(that) => this.ion_cmp(that)),
Bool(this) => compare!(Bool(that) => this.ion_cmp(that)),
Blob(this) => compare!(Blob(that) => this.ion_cmp(that)),
Clob(this) => compare!(Clob(that) => this.ion_cmp(that)),
SExp(this) => compare!(SExp(that) => this.ion_cmp(that)),
Expand Down
37 changes: 25 additions & 12 deletions src/element/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,22 +234,35 @@ impl IonEq for Struct {

impl IonOrd for Struct {
fn ion_cmp(&self, other: &Self) -> Ordering {
let mut these_fields = self.fields.by_index.clone();
let mut those_fields = other.fields.by_index.clone();
these_fields.sort_by(IonOrd::ion_cmp);
those_fields.sort_by(IonOrd::ion_cmp);
IonOrd::ion_cmp(&these_fields, &those_fields)
let mut these_fields = self.fields.by_index.iter().collect::<Vec<_>>();
let mut those_fields = other.fields.by_index.iter().collect::<Vec<_>>();
these_fields.sort_by(ion_cmp_field);
those_fields.sort_by(ion_cmp_field);

let mut i0 = these_fields.iter();
let mut i1 = those_fields.iter();
loop {
match [i0.next(), i1.next()] {
[None, Some(_)] => return Ordering::Less,
[None, None] => return Ordering::Equal,
[Some(_), None] => return Ordering::Greater,
[Some(a), Some(b)] => {
let ord = ion_cmp_field(a, b);
if ord != Ordering::Equal {
return ord;
}
}
}
}
}
}

impl IonOrd for (Symbol, Element) {
fn ion_cmp(&self, other: &Self) -> Ordering {
let ord = self.0.text().cmp(&other.0.text());
if !ord.is_eq() {
return ord;
}
IonOrd::ion_cmp(&self.1, &other.1)
fn ion_cmp_field(this: &&(Symbol, Element), that: &&(Symbol, Element)) -> Ordering {
let ord = this.0.ion_cmp(&that.0);
if !ord.is_eq() {
return ord;
}
IonOrd::ion_cmp(&this.1, &that.1)
}

#[cfg(test)]
Expand Down
41 changes: 19 additions & 22 deletions src/ion_data/ion_eq.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use num_traits::Zero;
use std::ops::Deref;

/// Determines whether two values are equal according to Ion's definition of equivalence.
///
Expand All @@ -22,39 +22,36 @@ use num_traits::Zero;
/// * `nan` and `nan` are Ion equivalent but not mathematically equivalent.
/// * `0.0e` and `-0.0e` are mathematically equivalent but not Ion equivalent.
/// * Decimal `0.0` and `-0.0` are mathematically equivalent but not Ion equivalent.
/// * Decimal `0.0` and `0.00` are mathematically equivalent but not Ion equivalent.
/// * Timestamps representing the same point in time at different precisions or at different
/// timezone offsets are not Ion equivalent.
///
pub trait IonEq {
fn ion_eq(&self, other: &Self) -> bool;
}

impl<T: IonEq> IonEq for &T {
fn ion_eq(&self, other: &Self) -> bool {
T::ion_eq(self, other)
}
}
// impl<T: IonEq> IonEq for &T {
// fn ion_eq(&self, other: &Self) -> bool {
// T::ion_eq(self, other)
// }
// }
//
// impl<T: IonEq> IonEq for Vec<T> {
// fn ion_eq(&self, other: &Self) -> bool {
// self.as_slice().ion_eq(other.as_slice())
// }
// }

impl IonEq for bool {
impl<R: Deref> IonEq for R
where
<R as Deref>::Target: IonEq,
{
fn ion_eq(&self, other: &Self) -> bool {
self == other
}
}

impl IonEq for f64 {
fn ion_eq(&self, other: &Self) -> bool {
if self.is_nan() {
return other.is_nan();
}
if self.is_zero() {
return other.is_zero() && self.is_sign_negative() == other.is_sign_negative();
}
// For all other values, fall back to mathematical equivalence
self == other
<Self as Deref>::Target::ion_eq(self, other)
}
}

impl<T: IonEq> IonEq for Vec<T> {
impl<T: IonEq> IonEq for [T] {
fn ion_eq(&self, other: &Self) -> bool {
if self.len() != other.len() {
return false;
Expand Down
36 changes: 7 additions & 29 deletions src/ion_data/ion_ord.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::element::Annotations;
use crate::IonType;
use std::cmp::Ordering;
use std::ops::Deref;

/// Trait used for delegating [Eq] and [PartialEq] in [IonData].

Check failure on line 4 in src/ion_data/ion_ord.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

unresolved link to `IonData`
/// Implementations of [IonOrd] must be consistent with [IonEq].

Check failure on line 5 in src/ion_data/ion_ord.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

unresolved link to `IonEq`
Expand All @@ -12,37 +11,16 @@ pub(crate) trait IonOrd {
fn ion_cmp(&self, other: &Self) -> Ordering;
}

impl<T: IonOrd> IonOrd for &T {
impl<R: Deref> IonOrd for R
where
<R as Deref>::Target: IonOrd,
{
fn ion_cmp(&self, other: &Self) -> Ordering {
T::ion_cmp(self, other)
<Self as Deref>::Target::ion_cmp(self, other)
}
}

impl IonOrd for IonType {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}

impl IonOrd for Annotations {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.iter().cmp(other.iter())
}
}

impl IonOrd for bool {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}

impl IonOrd for f64 {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.total_cmp(other)
}
}

impl<T: IonOrd> IonOrd for Vec<T> {
impl<T: IonOrd> IonOrd for [T] {
fn ion_cmp(&self, other: &Self) -> Ordering {
let mut i0 = self.iter();
let mut i1 = other.iter();
Expand Down
60 changes: 55 additions & 5 deletions src/ion_data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@ mod ion_ord;

use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::ops::Deref;

pub(crate) use ion_eq::IonEq;
pub(crate) use ion_ord::IonOrd;

/// A wrapper for lifting Ion compatible data into using Ion oriented comparisons (versus the Rust
/// A wrapper for lifting Ion compatible data into using Ion-oriented comparisons (versus the Rust
/// value semantics). This enables the default semantics to be what a Rust user expects for native
/// values, but allows a user to opt-in to Ion's structural equivalence/order.
///
/// Equivalence with respect to Ion values means that if two Ion values, `X` and `Y`, are equivalent,
/// they represent the same data and can be substituted for the other without loss of information.
/// (See [`IonEq`] for more information.)

Check warning on line 17 in src/ion_data/mod.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

public documentation for `IonData` links to private item `IonEq`
///
/// Some types, such as [`Element`] and [`Value`] cannot be used as the key of a map because they

Check failure on line 19 in src/ion_data/mod.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

unresolved link to `Element`

Check failure on line 19 in src/ion_data/mod.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

unresolved link to `Value`
/// adhere to Rust value semantics—these types cannot implement [`Eq`] because they include `NaN`
/// as a possible value.
///
/// For use cases that are concerned with preserving the original Ion data, it is necessary to use
/// Ion value equivalence. Many common use cases, such as writing unit tests for code that produces
/// Ion, can be handled with [`IonData::eq()`].
///
/// For other use cases, such as using Ion data as the key of a map or passing Ion data to an
/// algorithm that depends on [`Eq`], you can lift values using [`IonData::from()`].
///
/// Anything that implements [`IonEq`] can be converted into [`IonData`]. [`IonData`] itself does not

Check warning on line 30 in src/ion_data/mod.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

public documentation for `IonData` links to private item `IonEq`
/// implement [`IonEq`] so that it is impossible to create, for example, `IonData<IonData<Element>>`.

Check warning on line 31 in src/ion_data/mod.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

public documentation for `IonData` links to private item `IonEq`
///
Expand All @@ -20,13 +36,14 @@ pub(crate) use ion_ord::IonOrd;
/// __WARNING__—The Ion specification does _not_ define a total ordering over all Ion values. Do
/// not depend on getting any particular result from [Ord]. Use it only as an opaque total ordering
/// over all [IonData]. _The algorithm used for [Ord] may change between versions._
///
#[derive(Debug, Clone)]
pub struct IonData<T: IonEq>(T);
pub struct IonData<T>(T);

impl<T: IonEq> IonData<T> {
/// Checks if two values are equal according to Ion's structural equivalence.
pub fn eq<U: AsRef<T>>(a: U, b: U) -> bool {
T::ion_eq(a.as_ref(), b.as_ref())
pub fn eq<R: Deref<Target = T>>(a: R, b: R) -> bool {
T::ion_eq(a.deref(), b.deref())
}

/// Unwraps the value.
Expand Down Expand Up @@ -55,7 +72,7 @@ impl<T: IonEq> AsRef<T> for IonData<T> {
}
}

impl<T: IonEq + Display> Display for IonData<T> {
impl<T: Display> Display for IonData<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}

Check warning on line 78 in src/ion_data/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/ion_data/mod.rs#L76-L78

Added lines #L76 - L78 were not covered by tests
Expand All @@ -72,3 +89,36 @@ impl<T: IonEq + IonOrd> Ord for IonData<T> {
IonOrd::ion_cmp(&self.0, &other.0)
}
}

#[cfg(test)]
mod tests {
use crate::ion_data::{IonEq, IonOrd};
use crate::Element;
use crate::IonData;
use crate::Symbol;
use rstest::*;
use std::boxed::Box;
use std::fmt::Debug;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;

/// These tests exist primarily to ensure that we don't break any of trait implementations
/// needed to make this all work.
#[rstest]
#[case::value(|s| Element::read_one(s).unwrap().value().clone().into())]
#[case::symbol(|s| Symbol::from(s).into())]
#[case::element(|s| Element::read_one(s).unwrap().into() )]
#[case::rc_element(|s| Rc::new(Element::read_one(s).unwrap()).into() )]
#[case::vec_element(|s| Element::read_all(s).unwrap().into() )]
#[case::rc_vec_element(|s| Rc::new(Element::read_all(s).unwrap()).into() )]
#[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() )]
fn can_wrap_data<T: IonEq + IonOrd + Debug>(
#[case] the_fn: impl Fn(&'static str) -> IonData<T>,
) {
let id1: IonData<_> = the_fn("abc");
let id2: IonData<_> = the_fn("def");
assert_ne!(id1, id2);
assert!(id1 < id2);
}
}
45 changes: 45 additions & 0 deletions src/types/bool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::ion_data::{IonEq, IonOrd};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};

/// Represents an Ion `bool` value, for the purpose of implementing some Ion-related traits.
///
/// Most of the time, you can use [bool] instead of this type.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Bool(bool);

Check warning on line 9 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L8-L9

Added lines #L8 - L9 were not covered by tests

impl Display for Bool {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
<bool as Display>::fmt(&self.0, f)
}

Check warning on line 14 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L12-L14

Added lines #L12 - L14 were not covered by tests
}

impl From<bool> for Bool {
fn from(value: bool) -> Self {
Bool(value)
}

Check warning on line 20 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L18-L20

Added lines #L18 - L20 were not covered by tests
}

impl From<Bool> for bool {
fn from(value: Bool) -> Self {

Check warning on line 24 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L24

Added line #L24 was not covered by tests
value.0
}

Check warning on line 26 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L26

Added line #L26 was not covered by tests
}

impl PartialEq<bool> for Bool {
fn eq(&self, other: &bool) -> bool {
self.0 == *other
}

Check warning on line 32 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L30-L32

Added lines #L30 - L32 were not covered by tests
}

impl IonEq for Bool {
fn ion_eq(&self, other: &Self) -> bool {
self.0 == other.0
}

Check warning on line 38 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L36-L38

Added lines #L36 - L38 were not covered by tests
}

impl IonOrd for Bool {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}

Check warning on line 44 in src/types/bool.rs

View check run for this annotation

Codecov / codecov/patch

src/types/bool.rs#L42-L44

Added lines #L42 - L44 were not covered by tests
}

0 comments on commit fc15cdf

Please sign in to comment.