Skip to content

Commit

Permalink
Disallow serde serialize/deserialize unsigned integer types (#72)
Browse files Browse the repository at this point in the history
* [#70] Disallow serde serialize/deserialize unsigned integer types

* add compat mod for backward compatibility

* Added breaking changes in README
  • Loading branch information
zonyitoo committed May 30, 2017
1 parent 3e76aad commit 8a1b94a
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bson"
version = "0.7.1"
version = "0.8.0"
authors = [
"Y. T. Chung <zonyitoo@gmail.com>",
"Kevin Yeh <kevinyeah@utexas.edu>"
Expand Down
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ This crate works with Cargo and can be found on

```toml
[dependencies]
bson = "0.7"
bson = "0.8"
```

## Usage
Link the library in _main.rs_:

Expand All @@ -34,7 +35,7 @@ pub struct Person {
#[serde(rename = "_id")] // Use MongoDB's special primary key field name when serializing
pub id: String,
pub name: String,
pub age: u32
pub age: i32
}
```

Expand Down Expand Up @@ -68,3 +69,29 @@ let person_document = mongoCollection.find_one(Some(doc! { "_id" => "12345" }),
// Deserialize the document into a Person instance
let person = bson::from_bson(bson::Bson::Document(person_document))?
```

## Breaking Changes

In the BSON specification, _unsigned integer types_ are unsupported; for example, `u32`. In the older version of this crate (< `v0.8.0`), if you uses `serde` to serialize _unsigned integer types_ into BSON, it will store them with `Bson::FloatingPoint` type. From `v0.8.0`, we removed this behavior and simply returned an error when you want to serialize _unsigned integer types_ to BSON. [#72](https://github.com/zonyitoo/bson-rs/pull/72)

For backward compatibility, we've provided a mod `bson::compat::u2f` to explicitly serialize _unsigned integer types_ into BSON's floating point value as follows:

```rust
#[test]
fn test_compat_u2f() {
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
struct Foo {
#[serde(with = "bson::compat::u2f")]
x: u32
}

let foo = Foo { x: 20 };
let b = bson::to_bson(&foo).unwrap();
assert_eq!(b, Bson::Document(doc! { "x" => (Bson::FloatingPoint(20.0)) }));

let de_foo = bson::from_bson::<Foo>(b).unwrap();
assert_eq!(de_foo, foo);
}
```

In this example, we added an attribute `#[serde(with = "bson::compat::u2f")]` on field `x`, which will tell `serde` to use the `bson::compat::u2f::serialize` and `bson::compat::u2f::deserialize` methods to process this field.
2 changes: 1 addition & 1 deletion src/bson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl Debug for Bson {
&Bson::FloatingPoint(p) => write!(f, "FloatingPoint({:?})", p),
&Bson::String(ref s) => write!(f, "String({:?})", s),
&Bson::Array(ref vec) => write!(f, "Array({:?})", vec),
&Bson::Document(ref doc) => write!(f, "Document({})", doc),
&Bson::Document(ref doc) => write!(f, "Document({:?})", doc),
&Bson::Boolean(b) => write!(f, "Boolean({:?})", b),
&Bson::Null => write!(f, "Null"),
&Bson::RegExp(ref pat, ref opt) => write!(f, "RegExp(/{:?}/{:?})", pat, opt),
Expand Down
3 changes: 3 additions & 0 deletions src/compat/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Backward compatibility

pub mod u2f;
79 changes: 79 additions & 0 deletions src/compat/u2f.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! Convert unsigned types to/from `Bson::FloatingPoint`

use serde::{Serializer, Deserializer, Deserialize};

/// Converts primitive unsigned types to `f64`
pub trait ToF64 {
/// Converts to `f64` value
fn to_f64(&self) -> f64;
}

impl ToF64 for u8 {
fn to_f64(&self) -> f64 {
*self as f64
}
}

impl ToF64 for u16 {
fn to_f64(&self) -> f64 {
*self as f64
}
}

impl ToF64 for u32 {
fn to_f64(&self) -> f64 {
*self as f64
}
}

impl ToF64 for u64 {
fn to_f64(&self) -> f64 {
*self as f64
}
}

/// Serialize unsigned types to `Bson::FloatingPoint`
pub fn serialize<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
where T: ToF64,
S: Serializer
{
s.serialize_f64(v.to_f64())
}

/// Converts from `f64` value
pub trait FromF64 {
/// Converts from `f64` value
fn from_f64(v: f64) -> Self;
}

impl FromF64 for u8 {
fn from_f64(v: f64) -> u8 {
v as u8
}
}

impl FromF64 for u16 {
fn from_f64(v: f64) -> u16 {
v as u16
}
}

impl FromF64 for u32 {
fn from_f64(v: f64) -> u32 {
v as u32
}
}

impl FromF64 for u64 {
fn from_f64(v: f64) -> u64 {
v as u64
}
}

/// Deserialize unsigned types to `Bson::FloatingPoint`
pub fn deserialize<'de, T, D>(d: D) -> Result<T, D::Error>
where D: Deserializer<'de>,
T: FromF64
{
f64::deserialize(d).map(T::from_f64)
}
47 changes: 39 additions & 8 deletions src/decoder/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt;

use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess, SeqAccess, VariantAccess,
DeserializeSeed, EnumAccess};
use serde::de::Unexpected;
use serde::de::{Error, Unexpected};

use bson::{Bson, UtcDateTime};
use oid::ObjectId;
Expand Down Expand Up @@ -62,35 +62,66 @@ impl<'de> Visitor<'de> for BsonVisitor {
}

#[inline]
fn visit_bool<E>(self, value: bool) -> Result<Bson, E> {
fn visit_bool<E>(self, value: bool) -> Result<Bson, E>
where E: Error
{
Ok(Bson::Boolean(value))
}

#[inline]
fn visit_i8<E>(self, value: i8) -> Result<Bson, E> {
fn visit_i8<E>(self, value: i8) -> Result<Bson, E>
where E: Error
{
Ok(Bson::I32(value as i32))
}

#[inline]
fn visit_u8<E>(self, value: u8) -> Result<Bson, E>
where E: Error
{
Err(Error::invalid_type(Unexpected::Unsigned(value as u64), &"a signed integer"))
}

#[inline]
fn visit_i16<E>(self, value: i16) -> Result<Bson, E> {
fn visit_i16<E>(self, value: i16) -> Result<Bson, E>
where E: Error
{
Ok(Bson::I32(value as i32))
}

#[inline]
fn visit_u16<E>(self, value: u16) -> Result<Bson, E>
where E: Error
{
Err(Error::invalid_type(Unexpected::Unsigned(value as u64), &"a signed integer"))
}

#[inline]
fn visit_i32<E>(self, value: i32) -> Result<Bson, E> {
fn visit_i32<E>(self, value: i32) -> Result<Bson, E>
where E: Error
{
Ok(Bson::I32(value))
}

#[inline]
fn visit_i64<E>(self, value: i64) -> Result<Bson, E> {
fn visit_u32<E>(self, value: u32) -> Result<Bson, E>
where E: Error
{
Err(Error::invalid_type(Unexpected::Unsigned(value as u64), &"a signed integer"))
}

#[inline]
fn visit_i64<E>(self, value: i64) -> Result<Bson, E>
where E: Error
{
Ok(Bson::I64(value))
}

#[inline]
fn visit_u64<E>(self, value: u64) -> Result<Bson, E> {
Ok(Bson::I64(value as i64))
fn visit_u64<E>(self, value: u64) -> Result<Bson, E>
where E: Error
{
Err(Error::invalid_type(Unexpected::Unsigned(value), &"a signed integer"))
}

#[inline]
Expand Down
3 changes: 3 additions & 0 deletions src/encoder/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum EncoderError {
IoError(io::Error),
InvalidMapKeyType(Bson),
Unknown(String),
UnsupportedUnsignedType,
}

impl From<io::Error> for EncoderError {
Expand All @@ -25,6 +26,7 @@ impl fmt::Display for EncoderError {
write!(fmt, "Invalid map key type: {:?}", bson)
}
&EncoderError::Unknown(ref inner) => inner.fmt(fmt),
&EncoderError::UnsupportedUnsignedType => write!(fmt, "BSON does not support unsigned type"),
}
}
}
Expand All @@ -35,6 +37,7 @@ impl error::Error for EncoderError {
&EncoderError::IoError(ref inner) => inner.description(),
&EncoderError::InvalidMapKeyType(_) => "Invalid map key type",
&EncoderError::Unknown(ref inner) => inner,
&EncoderError::UnsupportedUnsignedType => "BSON does not support unsigned type",
}
}
fn cause(&self) -> Option<&error::Error> {
Expand Down
28 changes: 14 additions & 14 deletions src/encoder/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,38 +87,38 @@ impl Serializer for Encoder {
}

#[inline]
fn serialize_i16(self, value: i16) -> EncoderResult<Bson> {
self.serialize_i32(value as i32)
fn serialize_u8(self, _value: u8) -> EncoderResult<Bson> {
Err(EncoderError::UnsupportedUnsignedType)
}

#[inline]
fn serialize_i32(self, value: i32) -> EncoderResult<Bson> {
Ok(Bson::I32(value))
fn serialize_i16(self, value: i16) -> EncoderResult<Bson> {
self.serialize_i32(value as i32)
}

#[inline]
fn serialize_i64(self, value: i64) -> EncoderResult<Bson> {
Ok(Bson::I64(value))
fn serialize_u16(self, _value: u16) -> EncoderResult<Bson> {
Err(EncoderError::UnsupportedUnsignedType)
}

#[inline]
fn serialize_u8(self, value: u8) -> EncoderResult<Bson> {
self.serialize_u64(value as u64)
fn serialize_i32(self, value: i32) -> EncoderResult<Bson> {
Ok(Bson::I32(value))
}

#[inline]
fn serialize_u16(self, value: u16) -> EncoderResult<Bson> {
self.serialize_u64(value as u64)
fn serialize_u32(self, _value: u32) -> EncoderResult<Bson> {
Err(EncoderError::UnsupportedUnsignedType)
}

#[inline]
fn serialize_u32(self, value: u32) -> EncoderResult<Bson> {
self.serialize_u64(value as u64)
fn serialize_i64(self, value: i64) -> EncoderResult<Bson> {
Ok(Bson::I64(value))
}

#[inline]
fn serialize_u64(self, value: u64) -> EncoderResult<Bson> {
Ok(Bson::FloatingPoint(value as f64))
fn serialize_u64(self, _value: u64) -> EncoderResult<Bson> {
Err(EncoderError::UnsupportedUnsignedType)
}

#[inline]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ mod bson;
mod encoder;
mod decoder;
pub mod ordered;
pub mod compat;
17 changes: 17 additions & 0 deletions tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,20 @@ fn test_ser_datetime() {
let xfoo: Foo = bson::from_bson(x).unwrap();
assert_eq!(xfoo, foo);
}


#[test]
fn test_compat_u2f() {
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
struct Foo {
#[serde(with = "bson::compat::u2f")]
x: u32
}

let foo = Foo { x: 20 };
let b = bson::to_bson(&foo).unwrap();
assert_eq!(b, Bson::Document(doc! { "x" => (Bson::FloatingPoint(20.0)) }));

let de_foo = bson::from_bson::<Foo>(b).unwrap();
assert_eq!(de_foo, foo);
}

0 comments on commit 8a1b94a

Please sign in to comment.