Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serde support #47

Closed
wants to merge 1 commit into from
Closed

Serde support #47

wants to merge 1 commit into from

Conversation

radix
Copy link
Contributor

@radix radix commented Dec 27, 2017

Implement Serialize and Deserialize for Quantity, with tests.

Fixes #37.

@radix radix force-pushed the serde branch 3 times, most recently from 37554e0 to 2961a75 Compare December 27, 2017 23:10
Copy link
Owner

@iliekturtles iliekturtles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never used Serde before so I still want to go through their documentation, however the initial review looks good. When you get to uom's documentation lib.rs and README.md will need the same text explaining the serde feature. I don't have a good way to update both without copy-paste-fix-formatting currently.

Cargo.toml Outdated
@@ -23,6 +23,7 @@ maintenance = { status = "actively-developed" }
[dependencies]
num = "0.1"
typenum = "1.9.0"
serde = { version = "1.0", optional = true, features = ["derive"] }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move above typenum so dependencies are in alphabetical order.

src/lib.rs Outdated
@@ -155,6 +155,9 @@ compile_error!("A least one underlying storage type must be enabled. See the fea

#[doc(hidden)]
pub extern crate num;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a blank line just below num.

src/system.rs Outdated
U: Units<V> + ?Sized,
V: $crate::num::Num + $crate::Conversion<V> + $crate::serde::ser::Serialize,
{
fn serialize<S: $crate::serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put S's type bounds in a where clause instead of inline.

src/system.rs Outdated
where
De: $crate::serde::de::Deserializer<'de>,
{
let value: V = $crate::serde::de::Deserialize::deserialize(deserializer)?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blank line beneath this one.

src/lib.rs Outdated
@@ -155,6 +155,9 @@ compile_error!("A least one underlying storage type must be enabled. See the fea

#[doc(hidden)]
pub extern crate num;
#[doc(hidden)]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be #[cfg_attr(feature = "serde", doc(hidden)] so that the attribute only appears when serde is enabled. Didn't test with the compiler however.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested running cargo doc with and without the serde feature enabled and it seemed to work fine in both cases (not showing docs for serde in either case). However, of course, if you'd like me to specify a cfg_attr anyway I can do that.

@radix
Copy link
Contributor Author

radix commented Dec 28, 2017

I've addressed your comments, but I still need to write some tests. I should be able to get to that tomorrow! :)

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling 8e936c4 on radix:serde into 641ebe5 on iliekturtles:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling cd81039 on radix:serde into 6745365 on iliekturtles:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling 0ad4ace on radix:serde into 6745365 on iliekturtles:master.

@radix radix changed the title WIP: Serde support Serde support Dec 28, 2017
@radix
Copy link
Contributor Author

radix commented Dec 28, 2017

Okay, this is probably ready for another review pass. I've implemented simple tests. Note that I've also set up travis to run the serde tests in addition to the default-feature tests.

@radix
Copy link
Contributor Author

radix commented Dec 28, 2017

Oh, I'll also update CHANGELOG.md.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling ee6f509 on radix:serde into 6745365 on iliekturtles:master.

Copy link
Owner

@iliekturtles iliekturtles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include cargo test --verbose --features serde in .appveyor.yml. Comments for README.md also apply to lib.rs.

CHANGELOG.md Outdated
@@ -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`.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention that this functionality is disabled by default and can be enabled with the serde feature.

@@ -156,6 +159,13 @@ compile_error!("A least one underlying storage type must be enabled. See the fea
#[doc(hidden)]
pub extern crate num;

#[doc(hidden)]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like cfg_attr isn't necessary. The compiler is smart enough that #[doc(hidden)] doesn't "fall through" to the next item when the serde feature is not enabled.

src/lib.rs Outdated
#[cfg(feature = "serde")]
pub extern crate serde;

#[cfg(all(feature = "serde", test))]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change order so that test appears first. #[cfg(all(test, feature = "serde"))]. I also have the dev-dependencies in a separate section beneath the regular dependencies. Not sure if this should be maintained or if all dependencies should just be included in order. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to maintain the current style, I'll move it below.

src/tests.rs Outdated
@@ -7,6 +7,8 @@ use num::{Float, FromPrimitive, One, Saturating, Signed, Zero};
use quickcheck::TestResult;
use lib::fmt::Debug;
use lib::marker::PhantomData;
#[cfg(all(feature = "serde", test))]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test isn't necessary since this module is only compiled when test is set.

src/tests.rs Outdated
#[cfg(feature = "serde")]
#[allow(trivial_casts)]
fn serde_deserialize(v: A<V>) -> bool {
let json_f = serde_json::to_string(&*v).expect("Must be able to deserialize num");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the expect(...) should say serialize and not deserialize.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right you are!

src/tests.rs Outdated
#[allow(trivial_casts)]
fn serde_serialize(v: A<V>) -> bool {
let m = Length::new::<meter>((*v).clone());
let json_q = serde_json::to_string(&m).expect("Must be able to serialize Quantity");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put let json_q = ... after let json_f = ... to match comparison order below.

src/tests.rs Outdated
let length: Length = serde_json::from_str(&json_f)
.expect("Must be able to deserialize Quantity");

Test::approx_eq(&length.get(meter), &*v)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make &*v the first parameter.

README.md Outdated
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:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap at 100 characters.

README.md Outdated
@@ -88,12 +88,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.
* `serde` -- Feature to enable support for serialization and deserialization of quantities with
the [serde][serde] crate.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the feature is disabled by default. Also include the serde feature in the toml block above: "serde", # Serde support.

@radix radix force-pushed the serde branch 2 times, most recently from faca99e to a727657 Compare December 28, 2017 22:32
@radix
Copy link
Contributor Author

radix commented Dec 28, 2017

I believe I've addressed your comments, thanks for the review!

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling a727657 on radix:serde into 6745365 on iliekturtles:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling a727657 on radix:serde into 6745365 on iliekturtles:master.

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

FWIW, the appveyor failures are only in the old rem tests that occasionally fail, not the new serde code.

Copy link
Owner

@iliekturtles iliekturtles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these changes I think we'll be ready to merge!

.appveyor.yml Outdated
@@ -56,6 +56,7 @@ build: false
test_script:
- cargo build --verbose
- cargo test --verbose
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this line. The next line runs the same tests + serde tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. I should say that I did it this way intentionally, to make sure that the crate still builds and tests correctly without the serde feature enabled (making sure I set up my cfg attributes correctly etc). But with enough of these kinds of features it would lead to ridiculous test times, having to run through the test suite several times... so yeah, I can remove it :)

CHANGELOG.md Outdated
@@ -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 `serde` cargo feature.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just say "serde feature".

README.md Outdated
@@ -79,6 +79,7 @@ uom = {
"bigint", "biguint", # Arbitrary width integer storage types.
"rational", "rational32", "rational64", "bigrational", # Integer ratio storage types.
"f32", "f64", # Floating point storage types.
"serde", # Serde support.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this line didn't get indented.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, that was a sneaky tab character.

src/tests.rs Outdated
@@ -7,6 +7,8 @@ use num::{Float, FromPrimitive, One, Saturating, Signed, Zero};
use quickcheck::TestResult;
use lib::fmt::Debug;
use lib::marker::PhantomData;
#[cfg(all(feature = "serde"))]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all(...) isn't needed anymore.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling c0d8f78 on radix:serde into 6745365 on iliekturtles:master.

@iliekturtles
Copy link
Owner

I spoke a bit too soon about being ready to merge. I finally read through the Serde documentation and have a few more questions:

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

@iliekturtles no problem!

  1. Hmm, that's interesting. so, to get the first thing out of the way -- not serialize_struct, because we're not serializing a compound structure here, only one value (the bare number in the base unit).

    As for serialize_newtype_struct -- I believe what I implemented is equivalent to using serialize_newtype_struct, and honestly I'm not sure what the point of serialize_newtype_struct is. As the documentation says, implementors of serialization formats are supposed to just serialize the wrapped value, but I suppose it gives them the option of doing something different (against the advice of the documentation).
    I'm honestly not sure why I should use it -- I'm currently trying to get some advice from someone who is more familiar with it. It's worth noting that our struct isn't literally a newtype, since it has those other phantom fields. Looking at the implementations of backends, it seems like serializer.serialize_newtype_struct("Foobar", &self.0) is always equivalent to self.0.serialize(serializer).

  2. I've always just used serde_json personally, since it's simpler and more concise to use than serde_test. I think serde_test might be more useful when you want somewhat more specific tests (e.g. testing the order of serialization of fields and whatnot). However, I'd be happy to switch -- it should be simple enough.

  3. Good point. I'll see if there are any complications...

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

Okay, about point 1: https://github.com/ron-rs/ron serializes "serializes_newtype_struct" differently, so the name you pass is actually encoded into the output. I could also perhaps pass the name of the base unit as the newtype name, to make that output a bit more descriptive. I'll also have to deserialize with deserialize_newtype_struct. I'll make that change and let you know!

@iliekturtles
Copy link
Owner

Thanks for the information. Leave points 1 and 2 as-is for the moment. I'm going to do a bit more digging.

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

@iliekturtles Just FYI I was looking into using serialize_newtype_struct -- that's easy enough, but the deserialization side is more complicated. It needs a visitor to be passed, and I was trying to figure out how to generate one of those without too much work. But okay, I'll leave that alone and get back to disabling serde's std feature -- which has its own complications, because apparently our dependency on other crates is relying on the serde std feature. I'm digging into that some more.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling 0b75490 on radix:serde into 6745365 on iliekturtles:master.

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

Okay, I think I fixed the third point, but I was real confused for a while. Basically, there is a cargo bug when building uom directly with serde enabled: rust-lang/cargo#4664

Basically, because we have a dev-dependency on serde_json, which depends on serde (with default features), cargo commands run directly against uom still build serde with default features. However, when uom is used as a dependency, uom's dev-dependencies is ignored and so serde won't require std. I verified this manually by creating an example project that depends on my fork of uom, with serde enabled. I built it with --verbose and saw that it does not include serde's std feature.

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

btw, @iliekturtles , if you want to chat about any of these things in real-time, I am on the various rust-related channels on the mozilla IRC channels as M-radix (or radix)

@iliekturtles
Copy link
Owner

error[E0277]: the trait bound `num::BigInt: serde::Serialize` is not satisfied
   --> src\tests.rs:537:30
    |
537 |                 let json_f = serde_json::to_string(&*v).expect("Must be able to serialize num");
    |                              ^^^^^^^^^^^^^^^^^^^^^ the trait `serde::Serialize` is not implemented for `num::BigInt`
    |
    = note: required by `serde_json::to_string`

One more issue I found when running cargo test --all-features. BigInt and other complex types don't implement serde traits by default. Looks like there are features to include this functionality: https://github.com/rust-num/num/blob/master/Cargo.toml. Note that I just pushed a fix for --all-features not compiling because of num::Saturating.

Once this is resolved I'll merge for real. I still want to look at points 1 and 2 above, but will complete that before releasing v0.17.0.

I used to be on IRC via a very old laptop that was lost to a storm this fall. I'll see about getting connected again.

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

Ooookay. Unfortunately I think this is going to cause a bit of a pain, because of this bug: rust-lang/cargo#1286

I think what we want is to define a feature that both enables a dependency (serde) and enables features in other dependencies (num-bigint/serde, et al). But because of that linked bug, we cannot name this feature "serde", because all optional dependencies have names equal to the package name -- and sadly cargo doesn't just merge the features in the features section with the ones from the dependencies section.

So this means we need to bikeshed a name: what should it be? use_serde? enable_serde?

@radix
Copy link
Contributor Author

radix commented Dec 29, 2017

Ah! Even more confounding problems! 😠

Apparently the num crate has not been updated to serde 1.0. So there's no hope for us to provide serde support for its data types yet. rust-num/num#357

So I suppose we should just disable the tests for those backend types for now. However, I suppose we should still standardize/document a feature like use_serde instead of serde, so that we can later add num/serde to that feature once they update their support to serde 1.0.

So I'll

  1. add the use_serde feature and document it as the way to turn on serde support
  2. move the tests for serde into a new submodule that has everything enabled except the num datatypes.
  3. how about for now I also add a cargo build --all-features --tests to the travis/appveyor configs, so that we at least know the code compiles, if not passes tests?

@iliekturtles
Copy link
Owner

  1. use_serde sounds good.
  2. Move the serde tests into the op_assign module and rename to mod primitive.
  3. Good idea. Change AppVeyor and TravisCI to cargo build --all-features --verbose. As tests start getting fixed we can add additional features until build servers are running cargo test --all-features.

@radix
Copy link
Contributor Author

radix commented Jan 1, 2018

Okay, sorry for the delay @iliekturtles ! I think I've addressed the outstanding comments.

Oh, one minor difference which I'm not even sure was intentional on your part: I changed the build command to cargo build --verbose --all-features --tests instead of just cargo build --verbose --all-features. This means that the test code is at least compiled (but not necessarily run) for all features -- that's what I was originally suggesting in #47 (comment)

@coveralls
Copy link

Coverage Status

Coverage remained the same at 98.559% when pulling 8ff1d87 on radix:serde into d50651e on iliekturtles:master.

iliekturtles added a commit that referenced this pull request Jan 2, 2018
@iliekturtles
Copy link
Owner

Merged, thanks! I ended up fixing a formatting issue in tests.rs and re-organizing some comments. Also changed build script back to cargo build --all-features. Including --tests causes Cargo to do nothing for some reason (https://travis-ci.org/iliekturtles/uom/jobs/324219347#L578).

@radix
Copy link
Contributor Author

radix commented Jan 2, 2018

Hmm, well that's definitely strange. As you can see, other builds do show output, even from the same travis run as you linked. I wonder if there is just some caching going on that's causing cargo to realize it has nothing to do.

@iliekturtles
Copy link
Owner

I get the same thing locally which is where I found the issue originally:

uom (master)$ cargo clean && cargo build --all-features --tests
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants