Skip to content

Commit 8a1b94a

Browse files
authored
Disallow serde serialize/deserialize unsigned integer types (#72)
* [#70] Disallow serde serialize/deserialize unsigned integer types * add compat mod for backward compatibility * Added breaking changes in README
1 parent 3e76aad commit 8a1b94a

File tree

10 files changed

+187
-26
lines changed

10 files changed

+187
-26
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bson"
3-
version = "0.7.1"
3+
version = "0.8.0"
44
authors = [
55
"Y. T. Chung <[email protected]>",
66
"Kevin Yeh <[email protected]>"

README.md

+29-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ This crate works with Cargo and can be found on
1616

1717
```toml
1818
[dependencies]
19-
bson = "0.7"
19+
bson = "0.8"
2020
```
21+
2122
## Usage
2223
Link the library in _main.rs_:
2324

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

@@ -68,3 +69,29 @@ let person_document = mongoCollection.find_one(Some(doc! { "_id" => "12345" }),
6869
// Deserialize the document into a Person instance
6970
let person = bson::from_bson(bson::Bson::Document(person_document))?
7071
```
72+
73+
## Breaking Changes
74+
75+
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)
76+
77+
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:
78+
79+
```rust
80+
#[test]
81+
fn test_compat_u2f() {
82+
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
83+
struct Foo {
84+
#[serde(with = "bson::compat::u2f")]
85+
x: u32
86+
}
87+
88+
let foo = Foo { x: 20 };
89+
let b = bson::to_bson(&foo).unwrap();
90+
assert_eq!(b, Bson::Document(doc! { "x" => (Bson::FloatingPoint(20.0)) }));
91+
92+
let de_foo = bson::from_bson::<Foo>(b).unwrap();
93+
assert_eq!(de_foo, foo);
94+
}
95+
```
96+
97+
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.

src/bson.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ impl Debug for Bson {
8585
&Bson::FloatingPoint(p) => write!(f, "FloatingPoint({:?})", p),
8686
&Bson::String(ref s) => write!(f, "String({:?})", s),
8787
&Bson::Array(ref vec) => write!(f, "Array({:?})", vec),
88-
&Bson::Document(ref doc) => write!(f, "Document({})", doc),
88+
&Bson::Document(ref doc) => write!(f, "Document({:?})", doc),
8989
&Bson::Boolean(b) => write!(f, "Boolean({:?})", b),
9090
&Bson::Null => write!(f, "Null"),
9191
&Bson::RegExp(ref pat, ref opt) => write!(f, "RegExp(/{:?}/{:?})", pat, opt),

src/compat/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//! Backward compatibility
2+
3+
pub mod u2f;

src/compat/u2f.rs

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//! Convert unsigned types to/from `Bson::FloatingPoint`
2+
3+
use serde::{Serializer, Deserializer, Deserialize};
4+
5+
/// Converts primitive unsigned types to `f64`
6+
pub trait ToF64 {
7+
/// Converts to `f64` value
8+
fn to_f64(&self) -> f64;
9+
}
10+
11+
impl ToF64 for u8 {
12+
fn to_f64(&self) -> f64 {
13+
*self as f64
14+
}
15+
}
16+
17+
impl ToF64 for u16 {
18+
fn to_f64(&self) -> f64 {
19+
*self as f64
20+
}
21+
}
22+
23+
impl ToF64 for u32 {
24+
fn to_f64(&self) -> f64 {
25+
*self as f64
26+
}
27+
}
28+
29+
impl ToF64 for u64 {
30+
fn to_f64(&self) -> f64 {
31+
*self as f64
32+
}
33+
}
34+
35+
/// Serialize unsigned types to `Bson::FloatingPoint`
36+
pub fn serialize<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
37+
where T: ToF64,
38+
S: Serializer
39+
{
40+
s.serialize_f64(v.to_f64())
41+
}
42+
43+
/// Converts from `f64` value
44+
pub trait FromF64 {
45+
/// Converts from `f64` value
46+
fn from_f64(v: f64) -> Self;
47+
}
48+
49+
impl FromF64 for u8 {
50+
fn from_f64(v: f64) -> u8 {
51+
v as u8
52+
}
53+
}
54+
55+
impl FromF64 for u16 {
56+
fn from_f64(v: f64) -> u16 {
57+
v as u16
58+
}
59+
}
60+
61+
impl FromF64 for u32 {
62+
fn from_f64(v: f64) -> u32 {
63+
v as u32
64+
}
65+
}
66+
67+
impl FromF64 for u64 {
68+
fn from_f64(v: f64) -> u64 {
69+
v as u64
70+
}
71+
}
72+
73+
/// Deserialize unsigned types to `Bson::FloatingPoint`
74+
pub fn deserialize<'de, T, D>(d: D) -> Result<T, D::Error>
75+
where D: Deserializer<'de>,
76+
T: FromF64
77+
{
78+
f64::deserialize(d).map(T::from_f64)
79+
}

src/decoder/serde.rs

+39-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fmt;
33

44
use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess, SeqAccess, VariantAccess,
55
DeserializeSeed, EnumAccess};
6-
use serde::de::Unexpected;
6+
use serde::de::{Error, Unexpected};
77

88
use bson::{Bson, UtcDateTime};
99
use oid::ObjectId;
@@ -62,35 +62,66 @@ impl<'de> Visitor<'de> for BsonVisitor {
6262
}
6363

6464
#[inline]
65-
fn visit_bool<E>(self, value: bool) -> Result<Bson, E> {
65+
fn visit_bool<E>(self, value: bool) -> Result<Bson, E>
66+
where E: Error
67+
{
6668
Ok(Bson::Boolean(value))
6769
}
6870

6971
#[inline]
70-
fn visit_i8<E>(self, value: i8) -> Result<Bson, E> {
72+
fn visit_i8<E>(self, value: i8) -> Result<Bson, E>
73+
where E: Error
74+
{
7175
Ok(Bson::I32(value as i32))
7276
}
7377

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

7585
#[inline]
76-
fn visit_i16<E>(self, value: i16) -> Result<Bson, E> {
86+
fn visit_i16<E>(self, value: i16) -> Result<Bson, E>
87+
where E: Error
88+
{
7789
Ok(Bson::I32(value as i32))
7890
}
7991

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

8199
#[inline]
82-
fn visit_i32<E>(self, value: i32) -> Result<Bson, E> {
100+
fn visit_i32<E>(self, value: i32) -> Result<Bson, E>
101+
where E: Error
102+
{
83103
Ok(Bson::I32(value))
84104
}
85105

86106
#[inline]
87-
fn visit_i64<E>(self, value: i64) -> Result<Bson, E> {
107+
fn visit_u32<E>(self, value: u32) -> Result<Bson, E>
108+
where E: Error
109+
{
110+
Err(Error::invalid_type(Unexpected::Unsigned(value as u64), &"a signed integer"))
111+
}
112+
113+
#[inline]
114+
fn visit_i64<E>(self, value: i64) -> Result<Bson, E>
115+
where E: Error
116+
{
88117
Ok(Bson::I64(value))
89118
}
90119

91120
#[inline]
92-
fn visit_u64<E>(self, value: u64) -> Result<Bson, E> {
93-
Ok(Bson::I64(value as i64))
121+
fn visit_u64<E>(self, value: u64) -> Result<Bson, E>
122+
where E: Error
123+
{
124+
Err(Error::invalid_type(Unexpected::Unsigned(value), &"a signed integer"))
94125
}
95126

96127
#[inline]

src/encoder/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub enum EncoderError {
99
IoError(io::Error),
1010
InvalidMapKeyType(Bson),
1111
Unknown(String),
12+
UnsupportedUnsignedType,
1213
}
1314

1415
impl From<io::Error> for EncoderError {
@@ -25,6 +26,7 @@ impl fmt::Display for EncoderError {
2526
write!(fmt, "Invalid map key type: {:?}", bson)
2627
}
2728
&EncoderError::Unknown(ref inner) => inner.fmt(fmt),
29+
&EncoderError::UnsupportedUnsignedType => write!(fmt, "BSON does not support unsigned type"),
2830
}
2931
}
3032
}
@@ -35,6 +37,7 @@ impl error::Error for EncoderError {
3537
&EncoderError::IoError(ref inner) => inner.description(),
3638
&EncoderError::InvalidMapKeyType(_) => "Invalid map key type",
3739
&EncoderError::Unknown(ref inner) => inner,
40+
&EncoderError::UnsupportedUnsignedType => "BSON does not support unsigned type",
3841
}
3942
}
4043
fn cause(&self) -> Option<&error::Error> {

src/encoder/serde.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -87,38 +87,38 @@ impl Serializer for Encoder {
8787
}
8888

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

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

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

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

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

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

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

124124
#[inline]

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ mod bson;
6868
mod encoder;
6969
mod decoder;
7070
pub mod ordered;
71+
pub mod compat;

tests/serde.rs

+17
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,20 @@ fn test_ser_datetime() {
8080
let xfoo: Foo = bson::from_bson(x).unwrap();
8181
assert_eq!(xfoo, foo);
8282
}
83+
84+
85+
#[test]
86+
fn test_compat_u2f() {
87+
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
88+
struct Foo {
89+
#[serde(with = "bson::compat::u2f")]
90+
x: u32
91+
}
92+
93+
let foo = Foo { x: 20 };
94+
let b = bson::to_bson(&foo).unwrap();
95+
assert_eq!(b, Bson::Document(doc! { "x" => (Bson::FloatingPoint(20.0)) }));
96+
97+
let de_foo = bson::from_bson::<Foo>(b).unwrap();
98+
assert_eq!(de_foo, foo);
99+
}

0 commit comments

Comments
 (0)