From 8ff1d871b4755edf33c95f768821d7b1e3ae77fc Mon Sep 17 00:00:00 2001 From: "radix@twistedmatrix.com" Date: Wed, 27 Dec 2017 16:36:17 -0600 Subject: [PATCH] Implement `serde::Serialize` and `serde::Deserialize`. Fixes #37. --- .appveyor.yml | 4 ++-- .travis.yml | 4 ++-- CHANGELOG.md | 2 ++ Cargo.toml | 8 ++++++++ README.md | 12 ++++++++---- src/lib.rs | 19 +++++++++++++++---- src/system.rs | 36 ++++++++++++++++++++++++++++++++++++ src/tests.rs | 28 ++++++++++++++++++++++++++-- 8 files changed, 99 insertions(+), 14 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3c815bad..ccf2db4c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -54,8 +54,8 @@ install: ## Build Script ## build: false test_script: - - cargo build --verbose - - cargo test --verbose + - cargo build --verbose --all-features --tests + - cargo test --verbose --features serde notifications: - provider: Email diff --git a/.travis.yml b/.travis.yml index 9823148d..45c696be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,8 +30,8 @@ before_script: | (test $(grep "cargo-travis" /home/travis/.cargo/.crates.toml | sed -r "s/\"cargo-travis ([^\ ]+).+/\1/") = $(cargo search cargo-travis --limit 1 | sed -r "s/cargo-travis \(([^\)]+)\).+/\1/") || cargoo install cargo-travis --force) script: | - cargo build --verbose && - (test "$TRAVIS_RUST_VERSION" == "1.20.0" || cargo test --verbose) + cargo build --verbose --all-features --tests && + (test "$TRAVIS_RUST_VERSION" == "1.20.0" || cargo test --verbose --features serde) after_success: | test "$TRAVIS_RUST_VERSION" != "stable" || cargo coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b51264a..b1d696af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ ### Added * [#26](https://github.com/iliekturtles/uom/issues/26) Implement `num::Zero`. * [#35](https://github.com/iliekturtles/uom/issues/35) Implement `num::Saturating`. + * [#37](https://github.com/iliekturtles/uom/issues/35) Implement `serde::Serialize` and + `serde::Deserialize`. Disabled by default. Enabled with the `use_serde` feature. ## [v0.16.0] — 2017-12-21 This release contains significant changes in order to support underlying storage types that diff --git a/Cargo.toml b/Cargo.toml index dec1457f..9c92b1fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,15 +22,23 @@ maintenance = { status = "actively-developed" } [dependencies] num = "0.1" +serde = { version = "1.0", optional = true, default-features = false } typenum = "1.9.0" [dev-dependencies] approx = "0.1.1" quickcheck = "0.5.0" +serde_json = "1.0" static_assertions = "0.2.5" [features] default = ["f32", "f64", "si", "std"] +# The `use_serde` feature exists so that, in the future, we can add other dependency features to +# it, like num/serde. However, num/serde is currently left out because it has not yet been updated +# to Serde 1.0. +# It is also necessary to call it something other than `serde` because of this cargo bug: +# https://github.com/rust-lang/cargo/issues/1286 +use_serde = ["serde"] usize = [] u8 = [] u16 = [] diff --git a/README.md b/README.md index 36997595..df36cd40 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,10 @@ See the [examples](examples) directory for more advanced usage: ## Features `uom` has multiple `Cargo` features for controlling available underlying storage types, the -inclusion of the pre-built [International System of Units][si] (SI), and `no_std` functionality. The -features are described below. `f32`, `f64`, `std`, and `si` are enabled by default. Features can be -cherry-picked by using the `--no-default-features` and `--features "..."` flags when compiling `uom` -or specifying features in Cargo.toml: +inclusion of the pre-built [International System of Units][si] (SI), support for [Serde][serde], +and `no_std` functionality. The features are described below. `f32`, `f64`, `std`, and `si` are +enabled by default. Features can be cherry-picked by using the `--no-default-features` and +`--features "..."` flags when compiling `uom` or specifying features in Cargo.toml: ```toml [dependencies] @@ -74,6 +74,7 @@ uom = { version = "0.16.0", default-features = false, features = [ + "use_serde", # Serde support. "usize", "u8", "u16", "u32", "u64", # Unsigned integer storage types. "isize", "i8", "i16", "i32", "i64", # Signed interger storage types. "bigint", "biguint", # Arbitrary width integer storage types. @@ -88,12 +89,15 @@ uom = { `rational`, `rational32`, `rational64`, `bigrational`, `f32`, `f64` -- Features to enable underlying storage types. At least one of these features must be enabled. `f32` and `f64` are enabled by default. + * `use_serde` -- Feature to enable support for serialization and deserialization of quantities + with the [serde][serde] crate. Disabled by default. * `si` -- Feature to include the pre-built [International System of Units][si] (SI). Enabled by default. * `std` -- Feature to compile with standard library support. Disabling this feature compiles `uom` with `no_std`. Enabled by default. [si]: http://jcgm.bipm.org/vim/en/1.16.html +[serde]: https://serde.rs/ ## Design Rather than working with [measurement units](http://jcgm.bipm.org/vim/en/1.9.html) (meter, diff --git a/src/lib.rs b/src/lib.rs index c1bd2869..fdf5129b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,10 +52,11 @@ //! //! ## Features //! `uom` has multiple `Cargo` features for controlling available underlying storage types, the -//! inclusion of the pre-built [International System of Units][si] (SI), and `no_std` functionality. -//! The features are described below. `f32`, `f64`, `std`, and `si` are enabled by default. Features -//! can be cherry-picked by using the `--no-default-features` and `--features "..."` flags when -//! compiling `uom` or specifying features in Cargo.toml: +//! inclusion of the pre-built [International System of Units][si] (SI), support for +//! [Serde][serde], and `no_std` functionality. The features are described below. `f32`, `f64`, +//! `std`, and `si` are enabled by default. Features can be cherry-picked by using the +//! `--no-default-features` and `--features "..."` flags when compiling `uom` or specifying +//! features in Cargo.toml: //! //! ```toml //! [dependencies] @@ -63,6 +64,7 @@ //! version = "0.16.0", //! default-features = false, //! features = [ +//! "use_serde", # Serde support. //! "usize", "u8", "u16", "u32", "u64", # Unsigned integer storage types. //! "isize", "i8", "i16", "i32", "i64", # Signed interger storage types. //! "bigint", "biguint", # Arbitrary width integer storage types. @@ -77,12 +79,15 @@ //! `rational`, `rational32`, `rational64`, `bigrational`, `f32`, `f64` -- Features to enable //! underlying storage types. At least one of these features must be enabled. `f32` and `f64` are //! enabled by default. +//! * `use_serde` -- Feature to enable support for serialization and deserialization of quantities +//! with the [serde][serde] crate. Disabled by default. //! * `si` -- Feature to include the pre-built [International System of Units][si] (SI). Enabled by //! default. //! * `std` -- Feature to compile with standard library support. Disabling this feature compiles //! `uom` with `no_std`. Enabled by default. //! //! [si]: http://jcgm.bipm.org/vim/en/1.16.html +//! [serde]: https://serde.rs/ //! //! ## Design //! Rather than working with [measurement units](http://jcgm.bipm.org/vim/en/1.9.html) (meter, @@ -156,6 +161,10 @@ compile_error!("A least one underlying storage type must be enabled. See the fea #[doc(hidden)] pub extern crate num; +#[doc(hidden)] +#[cfg(feature = "serde")] +pub extern crate serde; + #[doc(hidden)] pub extern crate typenum; @@ -165,6 +174,8 @@ extern crate approx; #[cfg(test)] #[macro_use] extern crate quickcheck; +#[cfg(all(test, feature = "serde"))] +extern crate serde_json; #[cfg(test)] #[macro_use] extern crate static_assertions; diff --git a/src/system.rs b/src/system.rs index 516a2d99..9fc8fe5d 100644 --- a/src/system.rs +++ b/src/system.rs @@ -873,6 +873,42 @@ macro_rules! system { } } + #[cfg(feature = "serde")] + impl $crate::serde::Serialize for Quantity + where + D: Dimension + ?Sized, + U: Units + ?Sized, + V: $crate::num::Num + $crate::Conversion + $crate::serde::Serialize, + { + fn serialize(&self, serializer: S) -> Result + where + S: $crate::serde::Serializer + { + self.value.serialize(serializer) + } + } + + #[cfg(feature = "serde")] + impl<'de, D, U, V> $crate::serde::Deserialize<'de> for Quantity + where + D: Dimension + ?Sized, + U: Units + ?Sized, + V: $crate::num::Num + $crate::Conversion + $crate::serde::Deserialize<'de>, + { + fn deserialize(deserializer: De) -> Result + where + De: $crate::serde::Deserializer<'de>, + { + let value: V = $crate::serde::Deserialize::deserialize(deserializer)?; + + Ok(Quantity { + dimension: $crate::lib::marker::PhantomData, + units: $crate::lib::marker::PhantomData, + value, + }) + } + } + /// Macro to implement [`quantity`](si/struct.Quantity.html) type aliases for a specific /// [system of units][units] and value storage type. /// diff --git a/src/tests.rs b/src/tests.rs index a41cbfae..96bb2e8f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -7,6 +7,8 @@ use num::{Float, FromPrimitive, One, Saturating, Signed, Zero}; use quickcheck::TestResult; use lib::fmt::Debug; use lib::marker::PhantomData; +#[cfg(feature = "serde")] +use serde_json; #[allow(unused_imports)] use typenum::{N1, P1, P2, P3, Z0}; @@ -527,7 +529,7 @@ mod system_macro { &(Length::new::((*l).clone()) % Length::new::((*r).clone())).get(meter))) } - } + } } mod prim_int { @@ -710,7 +712,7 @@ mod system_macro { } } - mod op_assign { + mod primitive { storage_types! { types: Float, PrimInt; @@ -781,6 +783,28 @@ mod system_macro { TestResult::from_bool(Test::approx_eq(&f, &v.get(meter))) } + + // These serde tests can't be run against num-backed numeric backends because the num + // crate hasn't been updated to Serde 1.0 yet. + #[cfg(feature = "serde")] + #[allow(trivial_casts)] + fn serde_serialize(v: A) -> bool { + let m = Length::new::((*v).clone()); + let json_f = serde_json::to_string(&*v).expect("Must be able to serialize num"); + let json_q = serde_json::to_string(&m).expect("Must be able to serialize Quantity"); + + json_f == json_q + } + + #[cfg(feature = "serde")] + #[allow(trivial_casts)] + fn serde_deserialize(v: A) -> bool { + let json_f = serde_json::to_string(&*v).expect("Must be able to serialize num"); + let length: Length = serde_json::from_str(&json_f) + .expect("Must be able to deserialize Quantity"); + + Test::approx_eq(&*v, &length.get(meter)) + } } } }