Skip to content

Commit

Permalink
Serialize and deserialize proxy types transparently.
Browse files Browse the repository at this point in the history
This change serializes and deserializes `Proxy` transparently. No fields
or structure is written or read. Importantly, an intermediate type is
used with conversion to ensure that any constraints are still applied,
so it is not possible to deserialize an infinity into a `Finite`, for
example.
  • Loading branch information
olson-sean-k committed Sep 8, 2021
1 parent 6522d94 commit d93535c
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ optional = true

[dev-dependencies]
num = "^0.4.0"
serde_json = "1.0"
88 changes: 88 additions & 0 deletions src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,32 @@ use crate::{
#[cfg(feature = "std")]
use crate::{ForeignReal, N32, N64, R32, R64};

// TODO: By default, Serde serializes floating-point primitives representing
// `NaN` and infinities as `"null"`. Moreover, Serde cannot deserialize
// `"null"` as a floating-point primitive. This means that information is
// lost when serializing and deserializing is impossible for non-real
// values.
/// Serialization container.
///
/// This type is represented and serialized transparently as its inner type `T`.
/// `Proxy` uses this type for its own serialization and deserialization.
/// Importantly, this uses a conversion when deserializing that upholds the
/// constraints on proxy types, so it is not possible to deserialize a
/// floating-point value into a proxy type that does not support that value.
///
/// See the following for more context and details:
///
/// - https://github.com/serde-rs/serde/issues/642
/// - https://github.com/serde-rs/serde/issues/939
#[cfg(feature = "serialize-serde")]
#[derive(Deserialize, Serialize)]
#[serde(transparent)]
#[derive(Clone, Copy)]
#[repr(transparent)]
struct SerdeContainer<T> {
inner: T,
}

/// Floating-point proxy that provides a total ordering, equivalence, hashing,
/// and constraints.
///
Expand Down Expand Up @@ -61,6 +87,17 @@ use crate::{ForeignReal, N32, N64, R32, R64};
/// `Finite` type definitions. Note that `Total` uses a unit constraint, which
/// enforces no constraints at all and never panics.
#[cfg_attr(feature = "serialize-serde", derive(Deserialize, Serialize))]
#[cfg_attr(
feature = "serialize-serde",
serde(
bound(
deserialize = "T: serde::Deserialize<'de> + Float + Primitive, P: Constraint<T>",
serialize = "T: Float + Primitive + serde::Serialize, P: Constraint<T>"
),
from = "SerdeContainer<T>",
into = "SerdeContainer<T>"
)
)]
#[repr(transparent)]
pub struct Proxy<T, P> {
inner: T,
Expand Down Expand Up @@ -821,6 +858,30 @@ where
}
}

#[cfg(feature = "serialize-serde")]
impl<T, P> From<SerdeContainer<T>> for Proxy<T, P>
where
T: Float + Primitive,
P: Constraint<T>,
{
fn from(container: SerdeContainer<T>) -> Self {
Self::from_inner(container.inner)
}
}

#[cfg(feature = "serialize-serde")]
impl<T, P> From<Proxy<T, P>> for SerdeContainer<T>
where
T: Float + Primitive,
P: Constraint<T>,
{
fn from(proxy: Proxy<T, P>) -> Self {
SerdeContainer {
inner: proxy.into_inner(),
}
}
}

impl<P> From<Proxy<f32, P>> for f32
where
P: Constraint<f32>,
Expand Down Expand Up @@ -1956,4 +2017,31 @@ mod tests {
let z: Finite<f32> = 1.0.into();
format_args!("{0} {0:e} {0:E} {0:?} {0:#?}", z);
}

#[cfg(feature = "serialize-serde")]
#[test]
fn deserialize() {
assert_eq!(
R32::from_inner(1.0),
serde_json::from_str::<R32>("1.0").unwrap()
);
}

#[cfg(feature = "serialize-serde")]
#[test]
#[should_panic]
fn deserialize_panic_on_violation() {
// TODO: See `SerdeContainer`. This does not test a value that violates
// `N32`'s constraints; instead, this simply fails to deserialize
// `f32` from `"null"`.
let _: N32 = serde_json::from_str("null").unwrap();
}

#[cfg(feature = "serialize-serde")]
#[test]
fn serialize() {
assert_eq!("1.0", serde_json::to_string(&N32::from_inner(1.0)).unwrap());
// TODO: See `SerdeContainer`.
assert_eq!("null", serde_json::to_string(&N32::INFINITY).unwrap());
}
}

0 comments on commit d93535c

Please sign in to comment.