From 9639459a32023b14f5bf0c7bfe4dc416f14833ba Mon Sep 17 00:00:00 2001 From: "Y. T. Chung" Date: Sat, 4 Feb 2017 21:33:18 +0800 Subject: [PATCH 1/4] [#53] supports decimal128 and add necessary documents --- Cargo.toml | 3 + src/bson.rs | 19 +++++ src/decimal128.rs | 140 +++++++++++++++++++++++++++++++ src/decoder/mod.rs | 11 +++ src/encoder/mod.rs | 8 ++ src/lib.rs | 3 + src/spec.rs | 32 +++++-- tests/lib.rs | 1 + tests/modules/encoder_decoder.rs | 18 ++++ 9 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 src/decimal128.rs diff --git a/Cargo.toml b/Cargo.toml index f7b4e98b..903af2cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,6 @@ try_from = "0.2" assert_matches = "1.2" serde_derive = "1.0" serde_bytes = "0.10" + +[dependencies.decimal] +git = "https://github.com/alkis/decimal" diff --git a/src/bson.rs b/src/bson.rs index ee652725..0d586c8c 100644 --- a/src/bson.rs +++ b/src/bson.rs @@ -32,6 +32,7 @@ use serde_json::Value; use oid; use ordered::OrderedDocument; use spec::{BinarySubtype, ElementType}; +use decimal128::Decimal128; /// Possible BSON value types. #[derive(Clone, PartialEq)] @@ -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, "{}", d), } } } @@ -326,6 +331,12 @@ 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) => { + let mut obj = json::Object::new(); + obj.insert("$numberDecimal".to_owned(), + json::Json::String(v.to_string())); + json::Json::Object(obj) + } } } } @@ -350,6 +361,7 @@ impl Bson { Bson::ObjectId(..) => ElementType::ObjectId, Bson::UtcDatetime(..) => ElementType::UtcDatetime, Bson::Symbol(..) => ElementType::Symbol, + Bson::Decimal128(..) => ElementType::Decimal128Bit, } } @@ -429,6 +441,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 +480,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..e8d4818e --- /dev/null +++ b/src/decimal128.rs @@ -0,0 +1,140 @@ +//! `Decimal128` data type representation +//! +//! Specification is https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst + +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` + pub fn from_str(s: &str) -> Decimal128 { + Decimal128 { inner: s.parse::().expect("Invalid Decimal128 string") } + } + + /// Construct a `Decimal128` from a `i32` number + pub fn from_i32(d: i32) -> Decimal128 { + Decimal128 { inner: From::from(d) } + } + + /// Construct a `Decimal128` from a `u32` number + pub fn from_u32(d: u32) -> Decimal128 { + Decimal128 { inner: From::from(d) } + } + + /// Get a `0` + 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` + pub fn is_nan(&self) -> bool { + self.inner.is_nan() + } + + /// Check if value is 0 + 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 test_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()); + } +} diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 2814626b..a7705d46 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; @@ -85,6 +87,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(); @@ -217,6 +227,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/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/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/spec.rs b/src/spec.rs index 09774d13..cd58d708 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,32 +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, - - MaxKey = ELEMENT_TYPE_MAXKEY, - MinKey = ELEMENT_TYPE_MINKEY, + /// [128-bit decimal floating point](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) + Decimal128Bit = ELEMENT_TYPE_128BIT_DECIMAL, } impl ElementType { @@ -107,6 +126,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 b899f46e..7d6fe365 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}; @@ -310,3 +311,20 @@ 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); +} From 245c7834e5eacc6a1b7b257295bfb77024e9df5b Mon Sep 17 00:00:00 2001 From: lrlna Date: Thu, 28 Feb 2019 14:05:20 +0100 Subject: [PATCH 2/4] add documentation for decimal128 methods --- Cargo.toml | 4 +-- src/bson.rs | 13 ++------ src/decimal128.rs | 82 +++++++++++++++++++++++++++++++++++------------ src/spec.rs | 4 ++- 4 files changed, 68 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 903af2cb..f104abfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,9 @@ hostname = "0.1" hex = "0.3" md5 = "0.3" try_from = "0.2" +decimal = "2.0.4" [dev-dependencies] assert_matches = "1.2" serde_derive = "1.0" serde_bytes = "0.10" - -[dependencies.decimal] -git = "https://github.com/alkis/decimal" diff --git a/src/bson.rs b/src/bson.rs index 0d586c8c..3ea8b1be 100644 --- a/src/bson.rs +++ b/src/bson.rs @@ -29,10 +29,10 @@ use chrono::{DateTime, Timelike, Utc}; use hex; use serde_json::Value; +use decimal128::Decimal128; use oid; use ordered::OrderedDocument; use spec::{BinarySubtype, ElementType}; -use decimal128::Decimal128; /// Possible BSON value types. #[derive(Clone, PartialEq)] @@ -280,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, } } @@ -331,12 +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) => { - let mut obj = json::Object::new(); - obj.insert("$numberDecimal".to_owned(), - json::Json::String(v.to_string())); - json::Json::Object(obj) - } + Bson::Decimal128(ref v) => json!({ "$numberDecimal": v.to_string() }), } } } diff --git a/src/decimal128.rs b/src/decimal128.rs index e8d4818e..9477a7a4 100644 --- a/src/decimal128.rs +++ b/src/decimal128.rs @@ -1,6 +1,4 @@ -//! `Decimal128` data type representation -//! -//! Specification is https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst +//! [BSON Decimal128](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) data type representation use std::fmt; use std::str::FromStr; @@ -10,7 +8,7 @@ use decimal::d128; /// Decimal128 type #[derive(Clone, PartialEq, PartialOrd)] pub struct Decimal128 { - inner: d128, + d128: d128, } impl Decimal128 { @@ -21,23 +19,49 @@ impl Decimal128 { /// * `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") } + Decimal128 { d128: s.parse::().expect("Invalid Decimal128 string"), } } - /// Construct a `Decimal128` from a `i32` number + /// 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) } + Decimal128 { d128: From::from(d) } } - /// Construct a `Decimal128` from a `u32` number + /// 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) } + Decimal128 { d128: From::from(d) } } - /// Get a `0` + /// Create a new Decimal128 as `0`. + /// + /// ```rust + /// use bson::decimal128::Decimal128; + /// + /// let dec128 = Decimal128::zero(); + /// ``` pub fn zero() -> Decimal128 { - Decimal128 { inner: d128::zero() } + Decimal128 { d128: d128::zero() } } #[doc(hidden)] @@ -46,12 +70,12 @@ impl Decimal128 { raw.reverse(); } - Decimal128 { inner: d128::from_raw_bytes(raw) } + Decimal128 { d128: d128::from_raw_bytes(raw), } } #[doc(hidden)] pub fn to_raw_bytes_le(&self) -> [u8; 16] { - let mut buf = self.inner.to_raw_bytes(); + let mut buf = self.d128.to_raw_bytes(); if cfg!(target_endian = "big") { buf.reverse(); } @@ -59,37 +83,53 @@ impl Decimal128 { } /// 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() + self.d128.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() + self.d128.is_zero() } } impl fmt::Debug for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Decimal(\"{:?}\")", self.inner) + write!(f, "Decimal(\"{:?}\")", self.d128) } } impl fmt::Display for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.inner) + write!(f, "{}", self.d128) } } impl fmt::LowerHex for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(&self.inner, f) + ::fmt(&self.d128, f) } } impl fmt::LowerExp for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(&self.inner, f) + ::fmt(&self.d128, f) } } @@ -102,13 +142,13 @@ impl FromStr for Decimal128 { impl Into for Decimal128 { fn into(self) -> d128 { - self.inner + self.d128 } } impl From for Decimal128 { fn from(d: d128) -> Decimal128 { - Decimal128 { inner: d } + Decimal128 { d128: d } } } diff --git a/src/spec.rs b/src/spec.rs index cd58d708..c60b3cfb 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -99,7 +99,9 @@ pub enum ElementType { /// 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, + Decimal128Bit = ELEMENT_TYPE_128BIT_DECIMAL, + MaxKey = ELEMENT_TYPE_MAXKEY, + MinKey = ELEMENT_TYPE_MINKEY, } impl ElementType { From 03776338a4fc4d1b67a26aead7f9c0792699be24 Mon Sep 17 00:00:00 2001 From: lrlna Date: Thu, 28 Feb 2019 17:24:39 +0100 Subject: [PATCH 3/4] add serialize + deserialize for decimal128 --- src/bson.rs | 2 +- src/decoder/serde.rs | 20 ++++++++++++++++---- src/encoder/serde.rs | 21 +++++++++++++++++---- src/ordered.rs | 12 +++++++++++- tests/modules/ordered.rs | 30 ++++++++++++++++-------------- tests/modules/ser.rs | 16 ++++++++++++++-- 6 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/bson.rs b/src/bson.rs index 3ea8b1be..ee0aa90f 100644 --- a/src/bson.rs +++ b/src/bson.rs @@ -156,7 +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, "{}", d), + Bson::Decimal128(ref d) => write!(fmt, "Decimal128({})", d), } } } 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/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/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/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() { From eaa81949ca21e9b36e30c63e4616dcb48bfc8b11 Mon Sep 17 00:00:00 2001 From: lrlna Date: Thu, 28 Feb 2019 17:25:37 +0100 Subject: [PATCH 4/4] add decimal128 tests + into_i32/_u32 methods --- src/decimal128.rs | 104 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/src/decimal128.rs b/src/decimal128.rs index 9477a7a4..2b1b6029 100644 --- a/src/decimal128.rs +++ b/src/decimal128.rs @@ -8,7 +8,7 @@ use decimal::d128; /// Decimal128 type #[derive(Clone, PartialEq, PartialOrd)] pub struct Decimal128 { - d128: d128, + inner: d128, } impl Decimal128 { @@ -26,7 +26,7 @@ impl Decimal128 { /// let dec128 = Decimal128::from_str("1.05E+3"); /// ``` pub fn from_str(s: &str) -> Decimal128 { - Decimal128 { d128: s.parse::().expect("Invalid Decimal128 string"), } + Decimal128 { inner: s.parse::().expect("Invalid Decimal128 string"), } } /// Construct a `Decimal128` from a `i32` number. @@ -38,7 +38,7 @@ impl Decimal128 { /// let dec128 = Decimal128::from_i32(num); /// ``` pub fn from_i32(d: i32) -> Decimal128 { - Decimal128 { d128: From::from(d) } + Decimal128 { inner: From::from(d) } } /// Construct a `Decimal128` from a `u32` number. @@ -50,7 +50,35 @@ impl Decimal128 { /// let dec128 = Decimal128::from_u32(num); /// ``` pub fn from_u32(d: u32) -> Decimal128 { - Decimal128 { d128: From::from(d) } + 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`. @@ -61,7 +89,7 @@ impl Decimal128 { /// let dec128 = Decimal128::zero(); /// ``` pub fn zero() -> Decimal128 { - Decimal128 { d128: d128::zero() } + Decimal128 { inner: d128::zero() } } #[doc(hidden)] @@ -70,12 +98,12 @@ impl Decimal128 { raw.reverse(); } - Decimal128 { d128: d128::from_raw_bytes(raw), } + Decimal128 { inner: d128::from_raw_bytes(raw), } } #[doc(hidden)] pub fn to_raw_bytes_le(&self) -> [u8; 16] { - let mut buf = self.d128.to_raw_bytes(); + let mut buf = self.inner.to_raw_bytes(); if cfg!(target_endian = "big") { buf.reverse(); } @@ -92,7 +120,7 @@ impl Decimal128 { /// assert!(!dec128.is_nan()); /// ``` pub fn is_nan(&self) -> bool { - self.d128.is_nan() + self.inner.is_nan() } /// Check if value is 0 @@ -105,31 +133,31 @@ impl Decimal128 { /// assert!(dec128.is_zero()); /// ``` pub fn is_zero(&self) -> bool { - self.d128.is_zero() + self.inner.is_zero() } } impl fmt::Debug for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Decimal(\"{:?}\")", self.d128) + write!(f, "Decimal(\"{:?}\")", self.inner) } } impl fmt::Display for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.d128) + write!(f, "{}", self.inner) } } impl fmt::LowerHex for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(&self.d128, f) + ::fmt(&self.inner, f) } } impl fmt::LowerExp for Decimal128 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(&self.d128, f) + ::fmt(&self.inner, f) } } @@ -142,13 +170,13 @@ impl FromStr for Decimal128 { impl Into for Decimal128 { fn into(self) -> d128 { - self.d128 + self.inner } } impl From for Decimal128 { fn from(d: d128) -> Decimal128 { - Decimal128 { d128: d } + Decimal128 { inner: d } } } @@ -163,7 +191,7 @@ mod test { use super::*; #[test] - fn test_decimal128_string() { + fn decimal128_string() { assert!(Decimal128::from_str("0").is_zero()); assert!(!Decimal128::from_str("12").is_nan()); assert!(!Decimal128::from_str("-76").is_nan()); @@ -177,4 +205,48 @@ mod test { 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()); + } }