diff --git a/Cargo.toml b/Cargo.toml index f7b4e98b..f104abfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ hostname = "0.1" hex = "0.3" md5 = "0.3" try_from = "0.2" +decimal = "2.0.4" [dev-dependencies] assert_matches = "1.2" diff --git a/src/bson.rs b/src/bson.rs index ee652725..ee0aa90f 100644 --- a/src/bson.rs +++ b/src/bson.rs @@ -29,6 +29,7 @@ use chrono::{DateTime, Timelike, Utc}; use hex; use serde_json::Value; +use decimal128::Decimal128; use oid; use ordered::OrderedDocument; use spec::{BinarySubtype, ElementType}; @@ -72,6 +73,8 @@ pub enum Bson { UtcDatetime(DateTime), /// Symbol (Deprecated) Symbol(String), + /// [128-bit decimal floating point](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) + Decimal128(Decimal128), } /// Alias for `Vec`. @@ -111,6 +114,7 @@ impl Debug for Bson { Bson::ObjectId(ref id) => write!(f, "ObjectId({:?})", id), Bson::UtcDatetime(date_time) => write!(f, "UtcDatetime({:?})", date_time), Bson::Symbol(ref sym) => write!(f, "Symbol({:?})", sym), + Bson::Decimal128(ref d) => write!(f, "Decimal128({:?})", d), } } } @@ -152,6 +156,7 @@ impl Display for Bson { Bson::ObjectId(ref id) => write!(fmt, "ObjectId(\"{}\")", id), Bson::UtcDatetime(date_time) => write!(fmt, "Date(\"{}\")", date_time), Bson::Symbol(ref sym) => write!(fmt, "Symbol(\"{}\")", sym), + Bson::Decimal128(ref d) => write!(fmt, "Decimal128({})", d), } } } @@ -275,9 +280,7 @@ impl From for Bson { Value::String(x) => x.into(), Value::Bool(x) => x.into(), Value::Array(x) => Bson::Array(x.into_iter().map(Bson::from).collect()), - Value::Object(x) => { - Bson::from_extended_document(x.into_iter().map(|(k, v)| (k, v.into())).collect()) - } + Value::Object(x) => Bson::from_extended_document(x.into_iter().map(|(k, v)| (k, v.into())).collect()), Value::Null => Bson::Null, } } @@ -326,6 +329,7 @@ impl From for Value { }), // FIXME: Don't know what is the best way to encode Symbol type Bson::Symbol(v) => json!({ "$symbol": v }), + Bson::Decimal128(ref v) => json!({ "$numberDecimal": v.to_string() }), } } } @@ -350,6 +354,7 @@ impl Bson { Bson::ObjectId(..) => ElementType::ObjectId, Bson::UtcDatetime(..) => ElementType::UtcDatetime, Bson::Symbol(..) => ElementType::Symbol, + Bson::Decimal128(..) => ElementType::Decimal128Bit, } } @@ -429,6 +434,11 @@ impl Bson { "$symbol": v.to_owned(), } } + Bson::Decimal128(ref v) => { + doc! { + "$numberDecimal" => (v.to_string()) + } + } _ => panic!("Attempted conversion of invalid data type: {}", self), } } @@ -463,6 +473,8 @@ impl Bson { return Bson::UtcDatetime(Utc.timestamp(long / 1000, ((long % 1000) * 1000000) as u32)); } else if let Ok(sym) = values.get_str("$symbol") { return Bson::Symbol(sym.to_owned()); + } else if let Ok(dec) = values.get_str("$numberDecimal") { + return Bson::Decimal128(dec.parse::().unwrap()); } } diff --git a/src/decimal128.rs b/src/decimal128.rs new file mode 100644 index 00000000..2b1b6029 --- /dev/null +++ b/src/decimal128.rs @@ -0,0 +1,252 @@ +//! [BSON Decimal128](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) data type representation + +use std::fmt; +use std::str::FromStr; + +use decimal::d128; + +/// Decimal128 type +#[derive(Clone, PartialEq, PartialOrd)] +pub struct Decimal128 { + inner: d128, +} + +impl Decimal128 { + /// Construct a `Decimal128` from string. + /// + /// For example: + /// + /// * `NaN` + /// * `Infinity` or `Inf` + /// * `1.0`, `+37.0`, `0.73e-7`, `.5` + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let dec128 = Decimal128::from_str("1.05E+3"); + /// ``` + pub fn from_str(s: &str) -> Decimal128 { + Decimal128 { inner: s.parse::().expect("Invalid Decimal128 string"), } + } + + /// Construct a `Decimal128` from a `i32` number. + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let num: i32 = 23; + /// let dec128 = Decimal128::from_i32(num); + /// ``` + pub fn from_i32(d: i32) -> Decimal128 { + Decimal128 { inner: From::from(d) } + } + + /// Construct a `Decimal128` from a `u32` number. + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let num: u32 = 78; + /// let dec128 = Decimal128::from_u32(num); + /// ``` + pub fn from_u32(d: u32) -> Decimal128 { + Decimal128 { inner: From::from(d) } + } + + /// Construct a `Decimal128` from a `i32` number. + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let num: i32 = 23; + /// let dec128 = Decimal128::from_i32(num); + /// let int = dec128.into_i32(); + /// assert_eq!(int, num); + /// ``` + pub fn into_i32(&self) -> i32 { + Into::into(self.inner) + } + + /// Construct a `Decimal128` from a `i32` number. + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let num: u32 = 23; + /// let dec128 = Decimal128::from_u32(num); + /// let int = dec128.into_u32(); + /// assert_eq!(int, num); + /// ``` + pub fn into_u32(&self) -> u32 { + Into::into(self.inner) + } + + /// Create a new Decimal128 as `0`. + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let dec128 = Decimal128::zero(); + /// ``` + pub fn zero() -> Decimal128 { + Decimal128 { inner: d128::zero() } + } + + #[doc(hidden)] + pub unsafe fn from_raw_bytes_le(mut raw: [u8; 16]) -> Decimal128 { + if cfg!(target_endian = "big") { + raw.reverse(); + } + + Decimal128 { inner: d128::from_raw_bytes(raw), } + } + + #[doc(hidden)] + pub fn to_raw_bytes_le(&self) -> [u8; 16] { + let mut buf = self.inner.to_raw_bytes(); + if cfg!(target_endian = "big") { + buf.reverse(); + } + buf + } + + /// Check if value is `NaN` + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let num: u32 = 78; + /// let dec128 = Decimal128::from_u32(num); + /// assert!(!dec128.is_nan()); + /// ``` + pub fn is_nan(&self) -> bool { + self.inner.is_nan() + } + + /// Check if value is 0 + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let num: u32 = 0; + /// let dec128 = Decimal128::from_u32(num); + /// assert!(dec128.is_zero()); + /// ``` + pub fn is_zero(&self) -> bool { + self.inner.is_zero() + } +} + +impl fmt::Debug for Decimal128 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Decimal(\"{:?}\")", self.inner) + } +} + +impl fmt::Display for Decimal128 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.inner) + } +} + +impl fmt::LowerHex for Decimal128 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&self.inner, f) + } +} + +impl fmt::LowerExp for Decimal128 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&self.inner, f) + } +} + +impl FromStr for Decimal128 { + type Err = (); + fn from_str(s: &str) -> Result { + Ok(Decimal128::from_str(s)) + } +} + +impl Into for Decimal128 { + fn into(self) -> d128 { + self.inner + } +} + +impl From for Decimal128 { + fn from(d: d128) -> Decimal128 { + Decimal128 { inner: d } + } +} + +impl Default for Decimal128 { + fn default() -> Decimal128 { + Decimal128::zero() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn decimal128_string() { + assert!(Decimal128::from_str("0").is_zero()); + assert!(!Decimal128::from_str("12").is_nan()); + assert!(!Decimal128::from_str("-76").is_nan()); + assert!(!Decimal128::from_str("12.70").is_nan()); + assert!(!Decimal128::from_str("+0.003").is_nan()); + assert!(!Decimal128::from_str("017.").is_nan()); + assert!(!Decimal128::from_str(".5").is_nan()); + assert!(!Decimal128::from_str("4E+9").is_nan()); + assert!(!Decimal128::from_str("0.73e-7").is_nan()); + assert!(!Decimal128::from_str("Inf").is_nan()); + assert!(!Decimal128::from_str("-infinity").is_nan()); + assert!(Decimal128::from_str("NaN").is_nan()); + } + + #[test] + fn decimal128_i32() { + let num: i32 = 89; + let dec128 = Decimal128::from_i32(num); + + assert!(!dec128.is_nan()); + assert!(!dec128.is_zero()); + assert_eq!(dec128.into_i32(), num); + } + + #[test] + fn decimal128_u32() { + let num: u32 = 89; + let dec128 = Decimal128::from_u32(num); + + assert!(!dec128.is_nan()); + assert!(!dec128.is_zero()); + assert_eq!(dec128.into_u32(), num); + } + + #[test] + fn decimal128_0() { + let dec128 = Decimal128::zero(); + assert!(dec128.is_zero()); + } + + #[test] + fn decimal128_is_zero() { + let dec128 = Decimal128::from_i32(234); + assert!(!dec128.is_zero()); + + let dec128_0 = Decimal128::from_i32(0); + assert!(dec128_0.is_zero()); + } + + #[test] + fn decimal128_is_nan() { + let dec128 = Decimal128::from_str("NaN"); + assert!(dec128.is_nan()); + + let dec128 = Decimal128::from_i32(234); + assert!(!dec128.is_nan()); + } +} diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 18073250..18f8ded0 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -28,10 +28,12 @@ pub use self::error::{DecoderError, DecoderResult}; pub use self::serde::Decoder; use std::io::Read; +use std::mem; use byteorder::{LittleEndian, ReadBytesExt}; use chrono::offset::{LocalResult, TimeZone}; use chrono::Utc; +use decimal128::Decimal128; use bson::{Array, Bson, Document}; use oid; @@ -87,6 +89,14 @@ fn read_i64(reader: &mut R) -> DecoderResult { reader.read_i64::().map_err(From::from) } +#[inline] +fn read_f128(reader: &mut R) -> DecoderResult { + let mut local_buf: [u8; 16] = unsafe { mem::uninitialized() }; + try!(reader.read_exact(&mut local_buf)); + let val = unsafe { Decimal128::from_raw_bytes_le(local_buf) }; + Ok(val) +} + /// Attempt to decode a `Document` from a byte stream. pub fn decode_document(reader: &mut R) -> DecoderResult { let mut doc = Document::new(); @@ -222,6 +232,7 @@ fn decode_bson(reader: &mut R, tag: u8, utf8_lossy: bool) -> D } } Some(Symbol) => read_string(reader, utf8_lossy).map(Bson::Symbol), + Some(Decimal128Bit) => read_f128(reader).map(Bson::Decimal128), Some(Undefined) | Some(DbPointer) | Some(MaxKey) | Some(MinKey) | None => { Err(DecoderError::UnrecognizedElementType(tag)) } diff --git a/src/decoder/serde.rs b/src/decoder/serde.rs index 6c9dd519..98970e50 100644 --- a/src/decoder/serde.rs +++ b/src/decoder/serde.rs @@ -8,6 +8,7 @@ use serde::de::{Error, Unexpected}; use super::error::{DecoderError, DecoderResult}; use bson::{Bson, TimeStamp, UtcDateTime}; +use decimal128::Decimal128; use oid::ObjectId; use ordered::{OrderedDocument, OrderedDocumentIntoIterator, OrderedDocumentVisitor}; use spec::BinarySubtype; @@ -319,10 +320,8 @@ impl<'de> Deserializer<'de> for Decoder { // enums are encoded in json as maps with a single key:value pair match iter.next() { Some(_) => Err(DecoderError::InvalidType("expected a single key:value pair".to_owned())), - None => { - visitor.visit_enum(EnumDecoder { val: Bson::String(variant), - decoder: VariantDecoder { val: Some(value) }, }) - } + None => visitor.visit_enum(EnumDecoder { val: Bson::String(variant), + decoder: VariantDecoder { val: Some(value) }, }), } } @@ -602,6 +601,19 @@ impl<'de> Deserialize<'de> for TimeStamp { } } +impl<'de> Deserialize<'de> for Decimal128 { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + use serde::de::Error; + + match Bson::deserialize(deserializer)? { + Bson::Decimal128(d128) => Ok(d128), + _ => Err(D::Error::custom("expecting Decimal128")), + } + } +} + impl<'de> Deserialize<'de> for UtcDateTime { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index c026d75a..bf6e568c 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -35,6 +35,7 @@ use byteorder::{LittleEndian, WriteBytesExt}; use chrono::Timelike; use bson::Bson; +use decimal128::Decimal128; use serde::Serialize; fn write_string(writer: &mut W, s: &str) -> EncoderResult<()> { @@ -65,6 +66,12 @@ fn write_f64(writer: &mut W, val: f64) -> EncoderResult<()> { writer.write_f64::(val).map_err(From::from) } +#[inline] +fn write_f128(writer: &mut W, val: Decimal128) -> EncoderResult<()> { + let raw = val.to_raw_bytes_le(); + writer.write_all(&raw).map_err(From::from) +} + fn encode_array(writer: &mut W, arr: &[Bson]) -> EncoderResult<()> { let mut buf = Vec::new(); for (key, val) in arr.iter().enumerate() { @@ -133,6 +140,7 @@ fn encode_bson(writer: &mut W, key: &str, val: &Bson) -> Enco Bson::UtcDatetime(ref v) => write_i64(writer, (v.timestamp() * 1000) + (v.nanosecond() / 1_000_000) as i64), Bson::Null => Ok(()), Bson::Symbol(ref v) => write_string(writer, &v), + Bson::Decimal128(ref v) => write_f128(writer, v.clone()), } } diff --git a/src/encoder/serde.rs b/src/encoder/serde.rs index 21311dcc..8d92fb5d 100644 --- a/src/encoder/serde.rs +++ b/src/encoder/serde.rs @@ -1,7 +1,10 @@ -use serde::ser::{Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, - SerializeTupleStruct, SerializeTupleVariant, Serializer}; +use serde::ser::{ + Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, + SerializeTupleStruct, SerializeTupleVariant, Serializer, +}; -use bson::{Array, Bson, Document, UtcDateTime, TimeStamp}; +use bson::{Array, Bson, Document, TimeStamp, UtcDateTime}; +use decimal128::Decimal128; use oid::ObjectId; use try_from::TryFrom; @@ -258,7 +261,7 @@ impl Serializer for Encoder { #[inline] fn serialize_struct(self, _name: &'static str, _len: usize) -> EncoderResult { - Ok(StructSerializer { inner: Document::new(), }) + Ok(StructSerializer { inner: Document::new() }) } #[inline] @@ -436,6 +439,16 @@ impl Serialize for TimeStamp { } } +impl Serialize for Decimal128 { + #[inline] + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + let doc = Bson::Decimal128(self.clone()); + doc.serialize(serializer) + } +} + impl Serialize for UtcDateTime { #[inline] fn serialize(&self, serializer: S) -> Result diff --git a/src/lib.rs b/src/lib.rs index c7b672e8..0d864ea7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,11 +55,13 @@ extern crate serde_json; extern crate md5; extern crate time; extern crate try_from; +extern crate decimal; pub use self::bson::{Array, Bson, Document, TimeStamp, UtcDateTime}; pub use self::decoder::{decode_document, decode_document_utf8_lossy, from_bson, Decoder, DecoderError, DecoderResult}; pub use self::encoder::{encode_document, to_bson, Encoder, EncoderError, EncoderResult}; pub use self::ordered::{ValueAccessError, ValueAccessResult}; +pub use self::decimal128::Decimal128; #[macro_use] pub mod macros; @@ -70,3 +72,4 @@ mod encoder; pub mod oid; pub mod ordered; pub mod spec; +pub mod decimal128; diff --git a/src/ordered.rs b/src/ordered.rs index 9fdfabc4..cb414a80 100644 --- a/src/ordered.rs +++ b/src/ordered.rs @@ -12,6 +12,7 @@ use linked_hash_map::{self, LinkedHashMap}; use serde::de::{self, MapAccess, Visitor}; use bson::{Array, Bson, Document}; +use decimal128::Decimal128; use oid::ObjectId; use spec::BinarySubtype; @@ -206,6 +207,15 @@ impl OrderedDocument { } } + /// Get Decimal128 value for key, if it exists. + pub fn get_decimal128(&self, key: &str) -> ValueAccessResult<&Decimal128> { + match self.get(key) { + Some(&Bson::Decimal128(ref v)) => Ok(v), + Some(_) => Err(ValueAccessError::UnexpectedType), + None => Err(ValueAccessError::NotPresent), + } + } + /// Get a string slice this key if it exists and has the correct type. pub fn get_str(&self, key: &str) -> ValueAccessResult<&str> { match self.get(key) { @@ -391,7 +401,7 @@ pub struct OrderedDocumentVisitor { impl OrderedDocumentVisitor { pub fn new() -> OrderedDocumentVisitor { - OrderedDocumentVisitor { marker: PhantomData, } + OrderedDocumentVisitor { marker: PhantomData } } } diff --git a/src/spec.rs b/src/spec.rs index 09774d13..c60b3cfb 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -19,7 +19,7 @@ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -//! Constants derived from the [BSON Specification Version 1.0](http://bsonspec.org/spec.html). +//! Constants derived from the [BSON Specification Version 1.1](http://bsonspec.org/spec.html). use std::convert::From; @@ -41,6 +41,7 @@ pub const ELEMENT_TYPE_JAVASCRIPT_CODE_WITH_SCOPE: u8 = 0x0F; pub const ELEMENT_TYPE_32BIT_INTEGER: u8 = 0x10; pub const ELEMENT_TYPE_TIMESTAMP: u8 = 0x11; pub const ELEMENT_TYPE_64BIT_INTEGER: u8 = 0x12; +pub const ELEMENT_TYPE_128BIT_DECIMAL: u8 = 0x13; pub const ELEMENT_TYPE_MINKEY: u8 = 0xFF; pub const ELEMENT_TYPE_MAXKEY: u8 = 0x7F; @@ -55,30 +56,50 @@ pub const BINARY_SUBTYPE_MD5: u8 = 0x05; /// /// Not all element types are representable by the `Bson` type. #[repr(u8)] -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum ElementType { + /// 64-bit binary floating point FloatingPoint = ELEMENT_TYPE_FLOATING_POINT, + /// UTF-8 string Utf8String = ELEMENT_TYPE_UTF8_STRING, + /// Embedded document EmbeddedDocument = ELEMENT_TYPE_EMBEDDED_DOCUMENT, + /// Array Array = ELEMENT_TYPE_ARRAY, + /// Binary data Binary = ELEMENT_TYPE_BINARY, - /// Deprecated. + /// Deprecated. Undefined (value) Undefined = ELEMENT_TYPE_UNDEFINED, + /// [ObjectId](http://dochub.mongodb.org/core/objectids) ObjectId = ELEMENT_TYPE_OBJECT_ID, + /// Boolean value Boolean = ELEMENT_TYPE_BOOLEAN, + /// UTC datetime UtcDatetime = ELEMENT_TYPE_UTC_DATETIME, + /// Null value NullValue = ELEMENT_TYPE_NULL_VALUE, + /// Regular expression - The first cstring is the regex pattern, the second is the regex options string. + /// Options are identified by characters, which must be stored in alphabetical order. + /// Valid options are 'i' for case insensitive matching, 'm' for multiline matching, 'x' for verbose mode, + /// 'l' to make \w, \W, etc. locale dependent, 's' for dotall mode ('.' matches everything), and 'u' to + /// make \w, \W, etc. match unicode. RegularExpression = ELEMENT_TYPE_REGULAR_EXPRESSION, /// Deprecated. DbPointer = ELEMENT_TYPE_DBPOINTER, + /// JavaScript code JavaScriptCode = ELEMENT_TYPE_JAVASCRIPT_CODE, /// Deprecated. Symbol = ELEMENT_TYPE_SYMBOL, + /// JavaScript code w/ scope JavaScriptCodeWithScope = ELEMENT_TYPE_JAVASCRIPT_CODE_WITH_SCOPE, + /// 32-bit integer Integer32Bit = ELEMENT_TYPE_32BIT_INTEGER, + /// Timestamp TimeStamp = ELEMENT_TYPE_TIMESTAMP, + /// 64-bit integer Integer64Bit = ELEMENT_TYPE_64BIT_INTEGER, - + /// [128-bit decimal floating point](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) + Decimal128Bit = ELEMENT_TYPE_128BIT_DECIMAL, MaxKey = ELEMENT_TYPE_MAXKEY, MinKey = ELEMENT_TYPE_MINKEY, } @@ -107,6 +128,7 @@ impl ElementType { ELEMENT_TYPE_32BIT_INTEGER => Integer32Bit, ELEMENT_TYPE_TIMESTAMP => TimeStamp, ELEMENT_TYPE_64BIT_INTEGER => Integer64Bit, + ELEMENT_TYPE_128BIT_DECIMAL => Decimal128Bit, ELEMENT_TYPE_MAXKEY => MaxKey, ELEMENT_TYPE_MINKEY => MinKey, _ => return None, diff --git a/tests/lib.rs b/tests/lib.rs index 5e09b168..deaec308 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -5,5 +5,6 @@ extern crate bson; extern crate byteorder; extern crate chrono; extern crate hex; +extern crate decimal; mod modules; diff --git a/tests/modules/encoder_decoder.rs b/tests/modules/encoder_decoder.rs index 9c4e4ef7..7f72aa04 100644 --- a/tests/modules/encoder_decoder.rs +++ b/tests/modules/encoder_decoder.rs @@ -2,6 +2,7 @@ use bson::oid::ObjectId; use bson::spec::BinarySubtype; use bson::{decode_document, decode_document_utf8_lossy, encode_document, Bson}; use byteorder::{LittleEndian, WriteBytesExt}; +use bson::decimal128::Decimal128; use chrono::offset::TimeZone; use chrono::Utc; use std::io::{Cursor, Write}; @@ -311,9 +312,25 @@ fn test_decode_multiply_overflows_issue64() { assert!(decode_document(&mut Cursor::new(&buffer[..])).is_err()); } +#[test] +fn test_encode_decode_decimal128() { + let val = Bson::Decimal128(Decimal128::from_i32(0)); + let dst = vec![26, 0, 0, 0, 19, 107, 101, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, + 34, 0]; + + let doc = doc! { "key" => val }; + + let mut buf = Vec::new(); + encode_document(&mut buf, &doc).unwrap(); + + assert_eq!(buf, dst); + + let decoded = decode_document(&mut Cursor::new(buf)).unwrap(); + assert_eq!(decoded, doc); +} #[test] -fn test_illegal_size(){ +fn test_illegal_size() { let buffer = [0x06, 0xcc, 0xf9, 0x0a, 0x05, 0x00, 0x00, 0x03, 0x00, 0xff, 0xff]; assert!(decode_document(&mut Cursor::new(&buffer[..])).is_err()); } diff --git a/tests/modules/ordered.rs b/tests/modules/ordered.rs index c3b731f0..01f52370 100644 --- a/tests/modules/ordered.rs +++ b/tests/modules/ordered.rs @@ -1,7 +1,8 @@ -use bson::{Bson, Document}; -use bson::ValueAccessError; +use bson::decimal128::Decimal128; use bson::oid::ObjectId; use bson::spec::BinarySubtype; +use bson::ValueAccessError; +use bson::{Bson, Document}; use chrono::Utc; #[test] @@ -11,9 +12,7 @@ fn ordered_insert() { doc.insert("second".to_owned(), Bson::String("foo".to_owned())); doc.insert("alphanumeric".to_owned(), Bson::String("bar".to_owned())); - let expected_keys = vec!["first".to_owned(), - "second".to_owned(), - "alphanumeric".to_owned()]; + let expected_keys = vec!["first".to_owned(), "second".to_owned(), "alphanumeric".to_owned()]; let keys: Vec<_> = doc.iter().map(|(key, _)| key.to_owned()).collect(); assert_eq!(expected_keys, keys); @@ -26,9 +25,7 @@ fn ordered_insert_shorthand() { doc.insert("second", "foo"); doc.insert("alphanumeric", "bar".to_owned()); - let expected_keys = vec!["first".to_owned(), - "second".to_owned(), - "alphanumeric".to_owned()]; + let expected_keys = vec!["first".to_owned(), "second".to_owned(), "alphanumeric".to_owned()]; let keys: Vec<_> = doc.iter().map(|(key, _)| key.to_owned()).collect(); assert_eq!(expected_keys, keys); @@ -53,14 +50,12 @@ fn test_getters() { assert_eq!(None, doc.get("nonsense")); assert_eq!(Err(ValueAccessError::NotPresent), doc.get_str("nonsense")); - assert_eq!(Err(ValueAccessError::UnexpectedType), - doc.get_str("floating_point")); + assert_eq!(Err(ValueAccessError::UnexpectedType), doc.get_str("floating_point")); assert_eq!(Some(&Bson::FloatingPoint(10.0)), doc.get("floating_point")); assert_eq!(Ok(10.0), doc.get_f64("floating_point")); - assert_eq!(Some(&Bson::String("a value".to_string())), - doc.get("string")); + assert_eq!(Some(&Bson::String("a value".to_string())), doc.get("string")); assert_eq!(Ok("a value"), doc.get_str("string")); let array = vec![Bson::I32(10), Bson::I32(20), Bson::I32(30)]; @@ -89,8 +84,15 @@ fn test_getters() { assert_eq!(Some(&Bson::TimeStamp(100)), doc.get("timestamp")); assert_eq!(Ok(100i64), doc.get_time_stamp("timestamp")); - assert_eq!(Some(&Bson::UtcDatetime(datetime.clone())), - doc.get("datetime")); + assert_eq!(Some(&Bson::UtcDatetime(datetime.clone())), doc.get("datetime")); + assert_eq!(Ok(&datetime), doc.get_utc_datetime("datetime")); + + let dec = Decimal128::from_str("968E+1"); + doc.insert("decimal128".to_string(), Bson::Decimal128(dec.clone())); + assert_eq!(Some(&Bson::Decimal128(dec.clone())), doc.get("decimal128")); + assert_eq!(Ok(&dec), doc.get_decimal128("decimal128")); + + assert_eq!(Some(&Bson::UtcDatetime(datetime.clone())), doc.get("datetime")); assert_eq!(Ok(&datetime), doc.get_utc_datetime("datetime")); let object_id = ObjectId::new().unwrap(); diff --git a/tests/modules/ser.rs b/tests/modules/ser.rs index b42b5ad5..5ed305fc 100644 --- a/tests/modules/ser.rs +++ b/tests/modules/ser.rs @@ -1,7 +1,8 @@ -use bson::{from_bson, to_bson, Bson, EncoderError, EncoderResult}; +use bson::decimal128::Decimal128; use bson::oid::ObjectId; +use bson::{from_bson, to_bson, Bson, EncoderError, EncoderResult}; use std::collections::BTreeMap; -use std::{u8, u16, u32, u64}; +use std::{u16, u32, u64, u8}; #[test] fn floating_point() { @@ -54,6 +55,17 @@ fn int32() { assert_eq!(deser, obj); } +#[test] +fn dec128() { + let d128 = Decimal128::from_str("1.05E+3"); + let obj = Bson::Decimal128(d128.clone()); + let ser: Decimal128 = from_bson(obj.clone()).unwrap(); + assert_eq!(ser, d128); + + let deser: Bson = to_bson(&ser).unwrap(); + assert_eq!(deser, obj); +} + #[test] #[cfg_attr(feature = "u2i", ignore)] fn uint8() {