Skip to content

Commit ed1740a

Browse files
authored
RUST-445 Round trip decimal128 values (#177)
1 parent 899ea9f commit ed1740a

File tree

8 files changed

+80
-28
lines changed

8 files changed

+80
-28
lines changed

src/bson.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ use std::{
2929
use chrono::{DateTime, Datelike, SecondsFormat, TimeZone, Utc};
3030
use serde_json::{json, Value};
3131

32-
#[cfg(feature = "decimal128")]
33-
use crate::decimal128::Decimal128;
3432
pub use crate::document::Document;
3533
use crate::{
3634
oid::{self, ObjectId},
3735
spec::{BinarySubtype, ElementType},
36+
Decimal128,
3837
};
3938

4039
/// Possible BSON value types.
@@ -73,7 +72,6 @@ pub enum Bson {
7372
/// Symbol (Deprecated)
7473
Symbol(String),
7574
/// [128-bit decimal floating point](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst)
76-
#[cfg(feature = "decimal128")]
7775
Decimal128(Decimal128),
7876
/// Undefined value (Deprecated)
7977
Undefined,
@@ -139,8 +137,7 @@ impl Display for Bson {
139137
Bson::ObjectId(ref id) => write!(fmt, "ObjectId(\"{}\")", id),
140138
Bson::UtcDatetime(date_time) => write!(fmt, "Date(\"{}\")", date_time),
141139
Bson::Symbol(ref sym) => write!(fmt, "Symbol(\"{}\")", sym),
142-
#[cfg(feature = "decimal128")]
143-
Bson::Decimal128(ref d) => write!(fmt, "Decimal128({})", d),
140+
Bson::Decimal128(ref d) => write!(fmt, "{}", d),
144141
Bson::Undefined => write!(fmt, "undefined"),
145142
Bson::MinKey => write!(fmt, "MinKey"),
146143
Bson::MaxKey => write!(fmt, "MaxKey"),
@@ -331,6 +328,10 @@ impl From<Bson> for Value {
331328

332329
impl Bson {
333330
/// Converts the Bson value into its [relaxed extended JSON representation](https://docs.mongodb.com/manual/reference/mongodb-extended-json/).
331+
///
332+
/// Note: extended json encoding for `Decimal128` values is not supported without the
333+
/// "decimal128" feature flag. If this method is called on a case which contains a
334+
/// `Decimal128` value, it will panic.
334335
pub fn into_relaxed_extjson(self) -> Value {
335336
match self {
336337
Bson::FloatingPoint(v) if v.is_nan() => {
@@ -408,6 +409,11 @@ impl Bson {
408409
Bson::Symbol(v) => json!({ "$symbol": v }),
409410
#[cfg(feature = "decimal128")]
410411
Bson::Decimal128(ref v) => json!({ "$numberDecimal": v.to_string() }),
412+
#[cfg(not(feature = "decimal128"))]
413+
Bson::Decimal128(_) => panic!(
414+
"Decimal128 extended JSON not implemented yet. Use the decimal128 feature to \
415+
enable experimental support for it."
416+
),
411417
Bson::Undefined => json!({ "$undefined": true }),
412418
Bson::MinKey => json!({ "$minKey": 1 }),
413419
Bson::MaxKey => json!({ "$maxKey": 1 }),
@@ -426,6 +432,10 @@ impl Bson {
426432
}
427433

428434
/// Converts the Bson value into its [canonical extended JSON representation](https://docs.mongodb.com/manual/reference/mongodb-extended-json/).
435+
///
436+
/// Note: extended json encoding for `Decimal128` values is not supported without the
437+
/// "decimal128" feature flag. If this method is called on a case which contains a
438+
/// `Decimal128` value, it will panic.
429439
pub fn into_canonical_extjson(self) -> Value {
430440
match self {
431441
Bson::I32(i) => json!({ "$numberInt": i.to_string() }),
@@ -482,7 +492,6 @@ impl Bson {
482492
Bson::ObjectId(..) => ElementType::ObjectId,
483493
Bson::UtcDatetime(..) => ElementType::UtcDatetime,
484494
Bson::Symbol(..) => ElementType::Symbol,
485-
#[cfg(feature = "decimal128")]
486495
Bson::Decimal128(..) => ElementType::Decimal128Bit,
487496
Bson::Undefined => ElementType::Undefined,
488497
Bson::MaxKey => ElementType::MaxKey,

src/decimal128.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
//! [BSON Decimal128](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) data type representation
22
3-
use std::{fmt, str::FromStr};
4-
3+
#[cfg(feature = "decimal128")]
54
use decimal::d128;
6-
7-
/// Decimal128 type
5+
use std::fmt;
6+
7+
/// Decimal128 type.
8+
///
9+
/// Currently, this type does not have any functionality and can only be serialized and
10+
/// deserialized from existing documents that contain BSON decimal128s.
11+
///
12+
/// Experimental functionality can be enabled through the usage of the `"decimal128"`
13+
/// feature flag. Note that the API and behavior of such functionality are unstable and
14+
/// subject to change.
815
#[derive(Clone, PartialEq, PartialOrd)]
916
pub struct Decimal128 {
10-
inner: d128,
17+
#[cfg(not(feature = "decimal128"))]
18+
/// BSON bytes containing the decimal128. Stored for round tripping.
19+
pub(crate) bytes: [u8; 128 / 8],
20+
21+
#[cfg(feature = "decimal128")]
22+
inner: decimal::d128,
1123
}
1224

25+
#[cfg(feature = "decimal128")]
1326
impl Decimal128 {
1427
/// Construct a `Decimal128` from string.
1528
///
@@ -180,55 +193,74 @@ impl Decimal128 {
180193
}
181194

182195
impl fmt::Debug for Decimal128 {
196+
#[cfg(feature = "decimal128")]
183197
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184-
write!(f, "Decimal(\"{:?}\")", self.inner)
198+
write!(f, "Decimal128(\"{:?}\")", self.inner)
199+
}
200+
201+
#[cfg(not(feature = "decimal128"))]
202+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203+
write!(f, "Decimal128(...)")
185204
}
186205
}
187206

188207
impl fmt::Display for Decimal128 {
208+
#[cfg(feature = "decimal128")]
189209
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190210
write!(f, "{}", self.inner)
191211
}
212+
213+
#[cfg(not(feature = "decimal128"))]
214+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215+
write!(f, "{:?}", self)
216+
}
192217
}
193218

219+
#[cfg(feature = "decimal128")]
194220
impl fmt::LowerHex for Decimal128 {
195221
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196222
<d128 as fmt::LowerHex>::fmt(&self.inner, f)
197223
}
198224
}
199225

226+
#[cfg(feature = "decimal128")]
200227
impl fmt::LowerExp for Decimal128 {
201228
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202229
<d128 as fmt::LowerExp>::fmt(&self.inner, f)
203230
}
204231
}
205232

206-
impl FromStr for Decimal128 {
233+
#[cfg(feature = "decimal128")]
234+
impl std::str::FromStr for Decimal128 {
207235
type Err = ();
208236
fn from_str(s: &str) -> Result<Decimal128, ()> {
209237
Ok(Decimal128::from_str(s))
210238
}
211239
}
212240

241+
#[cfg(feature = "decimal128")]
213242
impl Into<d128> for Decimal128 {
214243
fn into(self) -> d128 {
215244
self.inner
216245
}
217246
}
218247

248+
#[cfg(feature = "decimal128")]
219249
impl From<d128> for Decimal128 {
220250
fn from(d: d128) -> Decimal128 {
221251
Decimal128 { inner: d }
222252
}
223253
}
224254

255+
#[cfg(feature = "decimal128")]
225256
impl Default for Decimal128 {
226257
fn default() -> Decimal128 {
227258
Decimal128::zero()
228259
}
229260
}
230261

231262
#[cfg(test)]
263+
#[cfg(feature = "decimal128")]
232264
mod test {
233265
use super::*;
234266

src/decoder/error.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl fmt::Display for DecoderError {
6767
element_type,
6868
} => write!(
6969
fmt,
70-
"unrecognized element type for key \"{}\": `{}`",
70+
"unrecognized element type for key \"{}\": `{:#x}`",
7171
key, element_type
7272
),
7373
DecoderError::SyntaxError { ref message } => message.fmt(fmt),
@@ -124,7 +124,6 @@ impl Bson {
124124
Bson::Symbol(_) => Unexpected::Other("symbol"),
125125
Bson::TimeStamp(_) => Unexpected::Other("timestamp"),
126126
Bson::UtcDatetime(_) => Unexpected::Other("datetime"),
127-
#[cfg(feature = "decimal128")]
128127
Bson::Decimal128(_) => Unexpected::Other("decimal128"),
129128
}
130129
}

src/decoder/mod.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,11 @@ use chrono::{
3737
Utc,
3838
};
3939

40-
#[cfg(feature = "decimal128")]
41-
use crate::decimal128::Decimal128;
4240
use crate::{
4341
bson::{Array, Binary, Bson, DbPointer, Document, JavaScriptCodeWithScope, Regex, TimeStamp},
4442
oid,
4543
spec::{self, BinarySubtype},
44+
Decimal128,
4645
};
4746

4847
use ::serde::de::{Deserialize, Error};
@@ -98,6 +97,16 @@ fn read_i64<R: Read + ?Sized>(reader: &mut R) -> DecoderResult<i64> {
9897
reader.read_i64::<LittleEndian>().map_err(From::from)
9998
}
10099

100+
/// Placeholder decoder for `Decimal128`. Reads 128 bits and just stores them, does no validation or
101+
/// parsing.
102+
#[cfg(not(feature = "decimal128"))]
103+
#[inline]
104+
fn read_f128<R: Read + ?Sized>(reader: &mut R) -> DecoderResult<Decimal128> {
105+
let mut buf = [0u8; 128 / 8];
106+
reader.read_exact(&mut buf)?;
107+
Ok(Decimal128 { bytes: buf })
108+
}
109+
101110
#[cfg(feature = "decimal128")]
102111
#[inline]
103112
fn read_f128<R: Read + ?Sized>(reader: &mut R) -> DecoderResult<Decimal128> {
@@ -219,7 +228,6 @@ pub(crate) fn decode_bson_kvp<R: Read + ?Sized>(
219228
}
220229
}
221230
Some(ElementType::Symbol) => read_string(reader, utf8_lossy).map(Bson::Symbol)?,
222-
#[cfg(feature = "decimal128")]
223231
Some(ElementType::Decimal128Bit) => read_f128(reader).map(Bson::Decimal128)?,
224232
Some(ElementType::Undefined) => Bson::Undefined,
225233
Some(ElementType::DbPointer) => {

src/encoder/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ pub(crate) fn encode_bson<W: Write + ?Sized>(
153153
),
154154
Bson::Null => Ok(()),
155155
Bson::Symbol(ref v) => write_string(writer, &v),
156+
#[cfg(not(feature = "decimal128"))]
157+
Bson::Decimal128(ref v) => {
158+
writer.write_all(&v.bytes)?;
159+
Ok(())
160+
}
156161
#[cfg(feature = "decimal128")]
157162
Bson::Decimal128(ref v) => write_f128(writer, v.clone()),
158163
Bson::Undefined => Ok(()),

src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,6 @@
186186
187187
#![allow(clippy::cognitive_complexity)]
188188

189-
#[cfg(feature = "decimal128")]
190-
pub use self::decimal128::Decimal128;
191189
pub use self::{
192190
bson::{
193191
Array,
@@ -200,6 +198,7 @@ pub use self::{
200198
TimeStamp,
201199
UtcDateTime,
202200
},
201+
decimal128::Decimal128,
203202
decoder::{from_bson, Decoder, DecoderError, DecoderResult},
204203
document::{ValueAccessError, ValueAccessResult},
205204
encoder::{to_bson, Encoder, EncoderError, EncoderResult},
@@ -209,7 +208,6 @@ pub use self::{
209208
mod macros;
210209
mod bson;
211210
pub mod compat;
212-
#[cfg(feature = "decimal128")]
213211
pub mod decimal128;
214212
mod decoder;
215213
pub mod document;

src/spec.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ pub enum ElementType {
101101
/// 64-bit integer
102102
Integer64Bit = ELEMENT_TYPE_64BIT_INTEGER,
103103
/// [128-bit decimal floating point](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst)
104-
#[cfg(feature = "decimal128")]
105104
Decimal128Bit = ELEMENT_TYPE_128BIT_DECIMAL,
106105
MaxKey = ELEMENT_TYPE_MAXKEY,
107106
MinKey = ELEMENT_TYPE_MINKEY,
@@ -131,7 +130,6 @@ impl ElementType {
131130
ELEMENT_TYPE_32BIT_INTEGER => Integer32Bit,
132131
ELEMENT_TYPE_TIMESTAMP => TimeStamp,
133132
ELEMENT_TYPE_64BIT_INTEGER => Integer64Bit,
134-
#[cfg(feature = "decimal128")]
135133
ELEMENT_TYPE_128BIT_DECIMAL => Decimal128Bit,
136134
ELEMENT_TYPE_MAXKEY => MaxKey,
137135
ELEMENT_TYPE_MINKEY => MinKey,

tests/spec/corpus.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ struct ParseError {
5252
}
5353

5454
fn run_test(test: TestFile) {
55-
if test.bson_type == "0x13" && !cfg!(feature = "decimal128") {
56-
return;
57-
}
58-
5955
for valid in test.valid {
6056
let description = format!("{}: {}", test.description, valid.description);
6157

@@ -71,6 +67,12 @@ fn run_test(test: TestFile) {
7167
.encode(&mut native_to_bson_bson_to_native_cv)
7268
.expect(&description);
7369

70+
// TODO RUST-36: Enable decimal128 tests.
71+
// extJSON not implemented for decimal128 without the feature flag, so we must stop here.
72+
if test.bson_type == "0x13" && !cfg!(feature = "decimal128") {
73+
continue;
74+
}
75+
7476
let cej: serde_json::Value =
7577
serde_json::from_str(&valid.canonical_extjson).expect(&description);
7678

@@ -109,7 +111,8 @@ fn run_test(test: TestFile) {
109111
}
110112
}
111113

112-
if test.bson_type == "0x13" {
114+
// TODO RUST-36: Enable decimal128 tests.
115+
if test.bson_type != "0x13" {
113116
assert_eq!(
114117
Bson::Document(bson_to_native_cb.clone()).into_canonical_extjson(),
115118
cej_updated_float,

0 commit comments

Comments
 (0)