diff --git a/.evergreen/config.yml b/.evergreen/config.yml index fc992e4f..ff09d7ff 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -181,9 +181,9 @@ axes: - id: "extra-rust-versions" values: - id: "min" - display_name: "1.53 (minimum supported version)" + display_name: "1.56 (minimum supported version)" variables: - RUST_VERSION: "1.53.0" + RUST_VERSION: "1.56.0" MSRV: "true" - id: "nightly" display_name: "nightly" diff --git a/Cargo.toml b/Cargo.toml index db54bf7c..8329dcaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ uuid = { version = "1.1.2", features = ["serde", "v4"] } serde_bytes = "0.11.5" serde_with = { version = "1", optional = true } time = { version = "0.3.9", features = ["formatting", "parsing", "macros", "large-dates"] } +bitvec = "1.0.1" [dev-dependencies] assert_matches = "1.2" @@ -78,4 +79,4 @@ chrono = { version = "0.4", features = ["serde", "clock", "std"], default-featur [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] \ No newline at end of file +rustdoc-args = ["--cfg", "docsrs"] diff --git a/serde-tests/json.rs b/serde-tests/json.rs index 473b3f21..417ea6c9 100644 --- a/serde-tests/json.rs +++ b/serde-tests/json.rs @@ -48,7 +48,7 @@ fn all_types_json() { "undefined": { "$undefined": true }, "code": { "$code": code }, "code_w_scope": { "$code": code_w_scope.code, "$scope": scope_json }, - "decimal": { "$numberDecimalBytes": v.decimal.bytes() }, + "decimal": { "$numberDecimal": v.decimal.to_string() }, "symbol": { "$symbol": "ok" }, "min_key": { "$minKey": 1 }, "max_key": { "$maxKey": 1 }, diff --git a/src/bson.rs b/src/bson.rs index 3780b12e..92f93a8b 100644 --- a/src/bson.rs +++ b/src/bson.rs @@ -455,7 +455,7 @@ impl Bson { "$date": { "$numberLong": v.timestamp_millis().to_string() }, }), Bson::Symbol(v) => json!({ "$symbol": v }), - Bson::Decimal128(_) => panic!("Decimal128 extended JSON not implemented yet."), + Bson::Decimal128(v) => json!({ "$numberDecimal": v.to_string() }), Bson::Undefined => json!({ "$undefined": true }), Bson::MinKey => json!({ "$minKey": 1 }), Bson::MaxKey => json!({ "$maxKey": 1 }), @@ -474,9 +474,6 @@ impl Bson { } /// Converts the Bson value into its [canonical extended JSON representation](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/). - /// - /// Note: extended json encoding for `Decimal128` values is not supported. If this method is - /// called on a case which contains a `Decimal128` value, it will panic. pub fn into_canonical_extjson(self) -> Value { match self { Bson::Int32(i) => json!({ "$numberInt": i.to_string() }), @@ -705,6 +702,14 @@ impl Bson { _ => {} }, + ["$numberDecimal"] => { + if let Ok(d) = doc.get_str("$numberDecimal") { + if let Ok(d) = d.parse() { + return Bson::Decimal128(d); + } + } + } + ["$numberDecimalBytes"] => { if let Ok(bytes) = doc.get_binary_generic("$numberDecimalBytes") { if let Ok(b) = bytes.clone().try_into() { diff --git a/src/de/serde.rs b/src/de/serde.rs index a219ff82..0ab77187 100644 --- a/src/de/serde.rs +++ b/src/de/serde.rs @@ -466,10 +466,15 @@ impl<'de> Visitor<'de> for BsonVisitor { } "$numberDecimal" => { - return Err(Error::custom( - "deserializing decimal128 values from strings is not currently supported" - .to_string(), - )); + let string: String = visitor.next_value()?; + return Ok(Bson::Decimal128(string.parse::().map_err( + |_| { + V::Error::invalid_value( + Unexpected::Str(&string), + &"decimal128 as a string", + ) + }, + )?)); } "$numberDecimalBytes" => { diff --git a/src/decimal128.rs b/src/decimal128.rs index 533b10dd..eb0ee57d 100644 --- a/src/decimal128.rs +++ b/src/decimal128.rs @@ -2,6 +2,8 @@ use std::{convert::TryInto, fmt}; +use bitvec::prelude::*; + /// Struct representing a BSON Decimal128 type. /// /// Currently, this type can only be used to round-trip through BSON. See @@ -9,7 +11,7 @@ use std::{convert::TryInto, fmt}; #[derive(Copy, Clone, PartialEq)] pub struct Decimal128 { /// BSON bytes containing the decimal128. Stored for round tripping. - pub(crate) bytes: [u8; 128 / 8], + pub(crate) bytes: [u8; 16], } impl Decimal128 { @@ -39,6 +41,425 @@ impl fmt::Debug for Decimal128 { impl fmt::Display for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) + write!(f, "{}", ParsedDecimal128::new(self)) + } +} + +impl std::str::FromStr for Decimal128 { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + Ok(s.parse::()?.pack()) + } +} + +#[derive(Debug, Clone, PartialEq)] +struct ParsedDecimal128 { + sign: bool, + kind: Decimal128Kind, +} + +#[derive(Debug, Clone, PartialEq)] +enum Decimal128Kind { + NaN { + signalling: bool, + }, + Infinity, + Finite { + exponent: Exponent, + coefficient: Coefficient, + }, +} + +#[derive(Debug, Clone, PartialEq)] +struct Exponent([u8; 2]); + +impl Exponent { + /// The exponent is stored as an unsigned value; `BIAS` is subtracted to get the actual value. + const BIAS: i16 = 6176; + /// The minimum representable exponent. This is distinct from the specifications "min" value, + /// which marks the point at which exponents are considered subnormal. + const TINY: i16 = -6176; + /// The maximum representable exponent. + const MAX: i16 = 6111; + + /// The number of unused bits in the parsed representation. + const UNUSED_BITS: usize = 2; + /// The total number of bits in the packed representation. + const PACKED_WIDTH: usize = 14; + + fn from_bits(src_bits: &BitSlice) -> Self { + let mut bytes = [0u8; 2]; + bytes.view_bits_mut::()[Self::UNUSED_BITS..].copy_from_bitslice(src_bits); + Self(bytes) + } + + fn from_native(value: i16) -> Self { + let mut bytes = [0u8; 2]; + bytes.view_bits_mut::().store_be(value + Self::BIAS); + Self(bytes) + } + + fn bits(&self) -> &BitSlice { + &self.0.view_bits::()[Self::UNUSED_BITS..] + } + + fn raw(&self) -> u16 { + self.0.view_bits::().load_be::() + } + + fn value(&self) -> i16 { + (self.raw() as i16) - Self::BIAS + } +} + +#[derive(Debug, Clone, PartialEq)] +struct Coefficient([u8; 16]); + +impl Coefficient { + /// The number of unused bits in the parsed representation. + const UNUSED_BITS: usize = 14; + /// The maximum number of digits allowed in a base-10 string representation of the coefficient. + const MAX_DIGITS: usize = 34; + /// The maximum allowable value of a coefficient. + const MAX_VALUE: u128 = 9_999_999_999_999_999_999_999_999_999_999_999; + + fn from_bits( + src_prefix: &BitSlice, + src_suffix: &BitSlice, + ) -> Result { + let mut bytes = [0u8; 16]; + let bits = &mut bytes.view_bits_mut::()[Self::UNUSED_BITS..]; + let prefix_len = src_prefix.len(); + bits[0..prefix_len].copy_from_bitslice(src_prefix); + bits[prefix_len..].copy_from_bitslice(src_suffix); + let out = Self(bytes); + if out.value() > Self::MAX_VALUE { + Err(ParseError::Overflow) + } else { + Ok(out) + } + } + + fn from_native(value: u128) -> Self { + let mut bytes = [0u8; 16]; + bytes.view_bits_mut::().store_be(value); + Self(bytes) + } + + fn bits(&self) -> &BitSlice { + &self.0.view_bits::()[Self::UNUSED_BITS..] + } + + fn value(&self) -> u128 { + self.0.view_bits::().load_be::() + } +} + +impl ParsedDecimal128 { + fn new(source: &Decimal128) -> Self { + // BSON byte order is the opposite of the decimal128 spec byte order, so flip 'em. The rest + // of this method could be rewritten to not need this, but readability is helped by + // keeping the implementation congruent with the spec. + let tmp: [u8; 16] = { + let mut tmp = [0u8; 16]; + tmp.view_bits_mut::() + .store_be(source.bytes.view_bits::().load_le::()); + tmp + }; + let src_bits = tmp.view_bits::(); + + let sign = src_bits[0]; + let kind = if src_bits[1..5].all() { + // Special value + if src_bits[5] { + Decimal128Kind::NaN { + signalling: src_bits[6], + } + } else { + Decimal128Kind::Infinity + } + } else { + // Finite value + let exponent_offset; + let coeff_prefix; + if src_bits[1..3].all() { + exponent_offset = 3; + coeff_prefix = bits![static u8, Msb0; 1, 0, 0]; + } else { + exponent_offset = 1; + coeff_prefix = bits![static u8, Msb0; 0]; + } + let coeff_offset = exponent_offset + Exponent::PACKED_WIDTH; + + let exponent = Exponent::from_bits(&src_bits[exponent_offset..coeff_offset]); + let coefficient = match Coefficient::from_bits(coeff_prefix, &src_bits[coeff_offset..]) + { + Ok(c) => c, + // Invalid coefficients get silently replaced with zero. + Err(_) => Coefficient([0u8; 16]), + }; + Decimal128Kind::Finite { + exponent, + coefficient, + } + }; + ParsedDecimal128 { sign, kind } + } + + fn pack(&self) -> Decimal128 { + let mut tmp = [0u8; 16]; + let dest_bits = tmp.view_bits_mut::(); + + dest_bits.set(0, self.sign); + + match &self.kind { + Decimal128Kind::NaN { signalling } => { + dest_bits[1..6].copy_from_bitslice(bits![u8, Msb0; 1, 1, 1, 1, 1]); + dest_bits.set(6, *signalling); + } + Decimal128Kind::Infinity => { + dest_bits[1..6].copy_from_bitslice(bits![u8, Msb0; 1, 1, 1, 1, 0]); + } + Decimal128Kind::Finite { + exponent, + coefficient, + } => { + let mut coeff_bits = coefficient.bits(); + let exponent_offset; + if coeff_bits[0] { + dest_bits.set(1, true); + dest_bits.set(2, true); + coeff_bits = &coeff_bits[3..]; + exponent_offset = 3; + } else { + coeff_bits = &coeff_bits[1..]; + exponent_offset = 1; + }; + let coeff_offset = exponent_offset + Exponent::PACKED_WIDTH; + dest_bits[exponent_offset..coeff_offset].copy_from_bitslice(exponent.bits()); + dest_bits[coeff_offset..].copy_from_bitslice(coeff_bits); + } + } + + let mut bytes = [0u8; 16]; + bytes + .view_bits_mut::() + .store_le(tmp.view_bits::().load_be::()); + Decimal128 { bytes } + } +} + +impl fmt::Display for ParsedDecimal128 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // MongoDB diverges from the IEEE spec and requires no sign for NaN + if self.sign && !matches!(&self.kind, Decimal128Kind::NaN { .. }) { + write!(f, "-")?; + } + match &self.kind { + Decimal128Kind::NaN { + signalling: _signalling, + } => { + /* Likewise, MongoDB requires no 's' prefix for signalling. + if *signalling { + write!(f, "s")?; + } + */ + write!(f, "NaN")?; + } + Decimal128Kind::Infinity => write!(f, "Infinity")?, + Decimal128Kind::Finite { + exponent, + coefficient, + } => { + let coeff_str = format!("{}", coefficient.value()); + let exp_val = exponent.value(); + let adj_exp = exp_val + (coeff_str.len() as i16) - 1; + if exp_val <= 0 && adj_exp >= -6 { + // Plain notation + if exp_val == 0 { + write!(f, "{}", coeff_str)?; + } else { + let dec_charlen = exp_val.unsigned_abs() as usize; + if dec_charlen >= coeff_str.len() { + write!(f, "0.")?; + write!(f, "{}", "0".repeat(dec_charlen - coeff_str.len()))?; + write!(f, "{}", coeff_str)?; + } else { + let (pre, post) = coeff_str.split_at(coeff_str.len() - dec_charlen); + write!(f, "{}", pre)?; + write!(f, ".")?; + write!(f, "{}", post)?; + } + } + } else { + // Exponential notation + let (pre, post) = coeff_str.split_at(1); + write!(f, "{}", pre)?; + if !post.is_empty() { + write!(f, ".{}", post)?; + } + write!(f, "E")?; + if adj_exp > 0 { + write!(f, "+")?; + } + write!(f, "{}", adj_exp)?; + } + } + } + Ok(()) + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum ParseError { + EmptyExponent, + InvalidExponent(std::num::ParseIntError), + InvalidCoefficient(std::num::ParseIntError), + Overflow, + Underflow, + InexactRounding, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParseError::EmptyExponent => write!(f, "empty exponent"), + ParseError::InvalidExponent(e) => write!(f, "invalid exponent: {}", e), + ParseError::InvalidCoefficient(e) => write!(f, "invalid coefficient: {}", e), + ParseError::Overflow => write!(f, "overflow"), + ParseError::Underflow => write!(f, "underflow"), + ParseError::InexactRounding => write!(f, "inexact rounding"), + } + } +} + +impl std::error::Error for ParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ParseError::InvalidExponent(e) => Some(e), + ParseError::InvalidCoefficient(e) => Some(e), + _ => None, + } + } +} + +impl std::str::FromStr for ParsedDecimal128 { + type Err = ParseError; + + fn from_str(mut s: &str) -> Result { + let sign; + if let Some(rest) = s.strip_prefix(&['-', '+'][..]) { + sign = s.starts_with('-'); + s = rest; + } else { + sign = false; + } + let kind = match s.to_ascii_lowercase().as_str() { + "nan" => Decimal128Kind::NaN { signalling: false }, + "snan" => Decimal128Kind::NaN { signalling: true }, + "infinity" | "inf" => Decimal128Kind::Infinity, + finite_str => { + // Split into parts + let mut decimal_str; + let exp_str; + match finite_str.split_once('e') { + None => { + decimal_str = finite_str; + exp_str = "0"; + } + Some((_, "")) => return Err(ParseError::EmptyExponent), + Some((pre, post)) => { + decimal_str = pre; + exp_str = post; + } + } + let mut exp = exp_str + .parse::() + .map_err(ParseError::InvalidExponent)?; + + // Remove decimal point and adjust exponent + let joined_str; + if let Some((pre, post)) = decimal_str.split_once('.') { + let exp_adj = post.len().try_into().map_err(|_| ParseError::Underflow)?; + exp = exp.checked_sub(exp_adj).ok_or(ParseError::Underflow)?; + joined_str = format!("{}{}", pre, post); + decimal_str = &joined_str; + } + + // Strip leading zeros + let rest = decimal_str.trim_start_matches('0'); + decimal_str = if rest.is_empty() { "0" } else { rest }; + + // Check decimal precision + { + let len = decimal_str.len(); + if len > Coefficient::MAX_DIGITS { + decimal_str = round_decimal_str(decimal_str, Coefficient::MAX_DIGITS)?; + let exp_adj = (len - decimal_str.len()) + .try_into() + .map_err(|_| ParseError::Overflow)?; + exp = exp.checked_add(exp_adj).ok_or(ParseError::Overflow)?; + } + } + + // Check exponent limits + if exp < Exponent::TINY { + if decimal_str != "0" { + let delta = (Exponent::TINY - exp) + .try_into() + .map_err(|_| ParseError::Overflow)?; + let new_precision = decimal_str + .len() + .checked_sub(delta) + .ok_or(ParseError::Underflow)?; + decimal_str = round_decimal_str(decimal_str, new_precision)?; + } + exp = Exponent::TINY; + } + let padded_str; + if exp > Exponent::MAX { + if decimal_str != "0" { + let delta = (exp - Exponent::MAX) + .try_into() + .map_err(|_| ParseError::Overflow)?; + if decimal_str + .len() + .checked_add(delta) + .ok_or(ParseError::Overflow)? + > Coefficient::MAX_DIGITS + { + return Err(ParseError::Overflow); + } + padded_str = format!("{}{}", decimal_str, "0".repeat(delta)); + decimal_str = &padded_str; + } + exp = Exponent::MAX; + } + + // Assemble the final value + let exponent = Exponent::from_native(exp); + let coeff: u128 = decimal_str + .parse() + .map_err(ParseError::InvalidCoefficient)?; + let coefficient = Coefficient::from_native(coeff); + Decimal128Kind::Finite { + exponent, + coefficient, + } + } + }; + + Ok(Self { sign, kind }) + } +} + +fn round_decimal_str(s: &str, precision: usize) -> Result<&str, ParseError> { + let (pre, post) = s.split_at(precision); + // Any nonzero trimmed digits mean it would be an imprecise round. + if post.chars().any(|c| c != '0') { + return Err(ParseError::InexactRounding); } + Ok(pre) } diff --git a/src/extjson/de.rs b/src/extjson/de.rs index 0b97dd42..e4683391 100644 --- a/src/extjson/de.rs +++ b/src/extjson/de.rs @@ -112,6 +112,11 @@ impl TryFrom> for Bson { return Ok(Bson::Double(double.parse()?)); } + if obj.contains_key("$numberDecimal") { + let decimal: models::Decimal128 = serde_json::from_value(obj.into())?; + return Ok(Bson::Decimal128(decimal.parse()?)); + } + if obj.contains_key("$binary") { let binary: models::Binary = serde_json::from_value(obj.into())?; return Ok(Bson::Binary(binary.parse()?)); @@ -159,10 +164,6 @@ impl TryFrom> for Bson { return Ok(db_ptr.parse()?.into()); } - if obj.contains_key("$numberDecimal") { - return Err(Error::custom("decimal128 extjson support not implemented")); - } - if obj.contains_key("$undefined") { let undefined: models::Undefined = serde_json::from_value(obj.into())?; return undefined.parse(); diff --git a/src/extjson/models.rs b/src/extjson/models.rs index 667123c3..48775113 100644 --- a/src/extjson/models.rs +++ b/src/extjson/models.rs @@ -20,7 +20,7 @@ impl Int32 { let i: i32 = self.value.parse().map_err(|_| { extjson::de::Error::invalid_value( Unexpected::Str(self.value.as_str()), - &"expected i32 as a string", + &"i32 as a string", ) })?; Ok(i) @@ -39,7 +39,7 @@ impl Int64 { let i: i64 = self.value.parse().map_err(|_| { extjson::de::Error::invalid_value( Unexpected::Str(self.value.as_str()), - &"expected i64 as a string", + &"i64 as a string", ) })?; Ok(i) @@ -63,7 +63,7 @@ impl Double { let d: f64 = other.parse().map_err(|_| { extjson::de::Error::invalid_value( Unexpected::Str(other), - &"expected bson double as string", + &"bson double as string", ) })?; Ok(d) @@ -72,6 +72,24 @@ impl Double { } } +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct Decimal128 { + #[serde(rename = "$numberDecimal")] + value: String, +} + +impl Decimal128 { + pub(crate) fn parse(self) -> extjson::de::Result { + self.value.parse().map_err(|_| { + extjson::de::Error::invalid_value( + Unexpected::Str(&self.value), + &"bson decimal128 as string", + ) + }) + } +} + #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub(crate) struct ObjectId { diff --git a/src/lib.rs b/src/lib.rs index afe69bf4..d940dae9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ //! //! ## Installation //! ### Requirements -//! - Rust 1.53+ +//! - Rust 1.56+ //! //! ### Importing //! This crate is available on [crates.io](https://crates.io/crates/bson). To use it in your application, @@ -260,7 +260,7 @@ //! //! ## Minimum supported Rust version (MSRV) //! -//! The MSRV for this crate is currently 1.53.0. This will be rarely be increased, and if it ever is, +//! The MSRV for this crate is currently 1.56.0. This will be rarely be increased, and if it ever is, //! it will only happen in a minor or major version release. #![allow(clippy::cognitive_complexity, clippy::derive_partial_eq_without_eq)] diff --git a/src/ser/serde.rs b/src/ser/serde.rs index d4556bb9..e2866a54 100644 --- a/src/ser/serde.rs +++ b/src/ser/serde.rs @@ -695,9 +695,15 @@ impl Serialize for Decimal128 { where S: ser::Serializer, { - let mut state = serializer.serialize_struct("$numberDecimal", 1)?; - state.serialize_field("$numberDecimalBytes", serde_bytes::Bytes::new(&self.bytes))?; - state.end() + if serializer.is_human_readable() { + let mut state = serializer.serialize_map(Some(1))?; + state.serialize_entry("$numberDecimal", &self.to_string())?; + state.end() + } else { + let mut state = serializer.serialize_struct("$numberDecimal", 1)?; + state.serialize_field("$numberDecimalBytes", serde_bytes::Bytes::new(&self.bytes))?; + state.end() + } } } diff --git a/src/tests/spec/corpus.rs b/src/tests/spec/corpus.rs index 6ded2dd5..88d872ca 100644 --- a/src/tests/spec/corpus.rs +++ b/src/tests/spec/corpus.rs @@ -387,12 +387,6 @@ fn run_test(test: TestFile) { } } - // TODO RUST-36: Enable decimal128 tests. - // extJSON not implemented for decimal128, so we must stop here. - if test.bson_type == "0x13" { - continue; - } - let cej: serde_json::Value = serde_json::from_str(&valid.canonical_extjson).expect(&description); @@ -422,15 +416,12 @@ fn run_test(test: TestFile) { } } - // TODO RUST-36: Enable decimal128 tests. - if test.bson_type != "0x13" { - assert_eq!( - Bson::Document(documentfromreader_cb.clone()).into_canonical_extjson(), - cej_updated_float, - "{}", - description - ); - } + assert_eq!( + Bson::Document(documentfromreader_cb.clone()).into_canonical_extjson(), + cej_updated_float, + "{}", + description + ); // native_to_relaxed_extended_json( bson_to_native(cB) ) = cEJ @@ -468,15 +459,12 @@ fn run_test(test: TestFile) { .to_writer(&mut native_to_bson_json_to_native_cej) .unwrap(); - // TODO RUST-36: Enable decimal128 tests. - if test.bson_type != "0x13" { - assert_eq!( - hex::encode(native_to_bson_json_to_native_cej).to_lowercase(), - valid.canonical_bson.to_lowercase(), - "{}", - description, - ); - } + assert_eq!( + hex::encode(native_to_bson_json_to_native_cej).to_lowercase(), + valid.canonical_bson.to_lowercase(), + "{}", + description, + ); } if let Some(ref degenerate_extjson) = valid.degenerate_extjson { @@ -490,14 +478,11 @@ fn run_test(test: TestFile) { let native_to_canonical_extended_json_json_to_native_dej = json_to_native_dej.clone().into_canonical_extjson(); - // TODO RUST-36: Enable decimal128 tests. - if test.bson_type != "0x13" { - assert_eq!( - native_to_canonical_extended_json_json_to_native_dej, cej, - "{}", - description, - ); - } + assert_eq!( + native_to_canonical_extended_json_json_to_native_dej, cej, + "{}", + description, + ); // native_to_bson( json_to_native(dEJ) ) = cB (unless lossy) @@ -509,15 +494,12 @@ fn run_test(test: TestFile) { .to_writer(&mut native_to_bson_json_to_native_dej) .unwrap(); - // TODO RUST-36: Enable decimal128 tests. - if test.bson_type != "0x13" { - assert_eq!( - hex::encode(native_to_bson_json_to_native_dej).to_lowercase(), - valid.canonical_bson.to_lowercase(), - "{}", - description, - ); - } + assert_eq!( + hex::encode(native_to_bson_json_to_native_dej).to_lowercase(), + valid.canonical_bson.to_lowercase(), + "{}", + description, + ); } } @@ -571,23 +553,12 @@ fn run_test(test: TestFile) { } for parse_error in test.parse_errors { - // TODO RUST-36: Enable decimal128 tests. - if test.bson_type == "0x13" { - continue; - } - // no special support for dbref convention if parse_error.description.contains("DBRef") { continue; } - // TODO RUST-36: Enable decimal128 tests. - if parse_error.description.contains("$numberDecimal") { - continue; - } - - let json: serde_json::Value = - serde_json::from_str(parse_error.string.as_str()).expect(&parse_error.description); + let json: serde_json::Value = serde_json::Value::String(parse_error.string); if let Ok(bson) = Bson::try_from(json.clone()) { // if converting to bson succeeds, assert that translating that bson to bytes fails