Skip to content

RUST-765 Ensure API meets Rust API guidelines #255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
[package]
name = "bson"
version = "1.2.0"
version = "2.0.0-beta"
authors = [
"Y. T. Chung <zonyitoo@gmail.com>",
"Kevin Yeh <kevinyeah@utexas.edu>",
"Saghm Rossi <saghmrossi@gmail.com>"
"Saghm Rossi <saghmrossi@gmail.com>",
"Patrick Freed <patrick.freed@mongodb.com>",
"Isabel Atkinson <isabel.atkinson@mongodb.com>",
]
description = "Encoding and decoding support for BSON in Rust"
license = "MIT"
readme = "README.md"
homepage = "https://github.com/mongodb/bson-rust"
documentation = "https://docs.rs/crate/bson"
repository = "https://github.com/mongodb/bson-rust"
edition = "2018"
keywords = ["bson", "mongodb", "serde", "serialization", "deserialization"]
categories = ["encoding"]

# By default cargo include everything git include
# cargo diet can help to manage what's not useful.
Expand Down Expand Up @@ -55,6 +58,7 @@ uuid = "0.8.1"
assert_matches = "1.2"
serde_bytes = "0.11"
pretty_assertions = "0.6.1"
chrono = { version = "0.4", features = ["serde"] }

[package.metadata.docs.rs]
features = ["decimal128"]
4 changes: 2 additions & 2 deletions examples/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ fn main() {

let arr = vec![
Bson::String("blah".to_string()),
Bson::DateTime(chrono::Utc::now()),
Bson::ObjectId(oid::ObjectId::with_bytes([
Bson::DateTime(chrono::Utc::now().into()),
Bson::ObjectId(oid::ObjectId::from_bytes([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
])),
];
Expand Down
125 changes: 62 additions & 63 deletions src/bson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@

//! BSON definition

use std::{
fmt::{self, Debug, Display},
ops::{Deref, DerefMut},
};
use std::fmt::{self, Debug, Display};

use chrono::{Datelike, SecondsFormat, TimeZone, Utc};
use serde_json::{json, Value};
Expand Down Expand Up @@ -68,7 +65,7 @@ pub enum Bson {
/// [ObjectId](http://dochub.mongodb.org/core/objectids)
ObjectId(oid::ObjectId),
/// UTC datetime
DateTime(chrono::DateTime<Utc>),
DateTime(crate::DateTime),
/// Symbol (Deprecated)
Symbol(String),
/// [128-bit decimal floating point](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst)
Expand Down Expand Up @@ -278,7 +275,7 @@ impl From<u64> for Bson {

impl From<[u8; 12]> for Bson {
fn from(a: [u8; 12]) -> Bson {
Bson::ObjectId(oid::ObjectId::with_bytes(a))
Bson::ObjectId(oid::ObjectId::from_bytes(a))
}
}

Expand All @@ -290,7 +287,7 @@ impl From<oid::ObjectId> for Bson {

impl From<chrono::DateTime<Utc>> for Bson {
fn from(a: chrono::DateTime<Utc>) -> Bson {
Bson::DateTime(a)
Bson::DateTime(DateTime(a))
}
}

Expand Down Expand Up @@ -373,15 +370,15 @@ impl Bson {
})
}
Bson::ObjectId(v) => json!({"$oid": v.to_hex()}),
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.year() <= 99999 => {
let seconds_format = if v.timestamp_subsec_millis() == 0 {
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.0.year() <= 99999 => {
let seconds_format = if v.0.timestamp_subsec_millis() == 0 {
SecondsFormat::Secs
} else {
SecondsFormat::Millis
};

json!({
"$date": v.to_rfc3339_opts(seconds_format, true),
"$date": v.0.to_rfc3339_opts(seconds_format, true),
})
}
Bson::DateTime(v) => json!({
Expand Down Expand Up @@ -539,15 +536,15 @@ impl Bson {
"$oid": v.to_string(),
}
}
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.year() <= 99999 => {
let seconds_format = if v.timestamp_subsec_millis() == 0 {
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.0.year() <= 99999 => {
let seconds_format = if v.0.timestamp_subsec_millis() == 0 {
SecondsFormat::Secs
} else {
SecondsFormat::Millis
};

doc! {
"$date": v.to_rfc3339_opts(seconds_format, true),
"$date": v.0.to_rfc3339_opts(seconds_format, true),
}
}
Bson::DateTime(v) => doc! {
Expand Down Expand Up @@ -607,7 +604,7 @@ impl Bson {
match keys.as_slice() {
["$oid"] => {
if let Ok(oid) = doc.get_str("$oid") {
if let Ok(oid) = ObjectId::with_string(oid) {
if let Ok(oid) = ObjectId::parse_str(oid) {
return Bson::ObjectId(oid);
}
}
Expand Down Expand Up @@ -738,12 +735,12 @@ impl Bson {

["$date"] => {
if let Ok(date) = doc.get_i64("$date") {
return Bson::DateTime(DateTime::from_i64(date).into());
return Bson::DateTime(DateTime::from_millis(date));
}

if let Ok(date) = doc.get_str("$date") {
if let Ok(date) = chrono::DateTime::parse_from_rfc3339(date) {
return Bson::DateTime(date.into());
return Bson::DateTime(date.with_timezone(&Utc).into());
}
}
}
Expand Down Expand Up @@ -888,7 +885,7 @@ impl Bson {
}

/// If `Bson` is `DateTime`, return its value. Returns `None` otherwise
pub fn as_datetime(&self) -> Option<&chrono::DateTime<Utc>> {
pub fn as_datetime(&self) -> Option<&crate::DateTime> {
match *self {
Bson::DateTime(ref v) => Some(v),
_ => None,
Expand All @@ -897,7 +894,7 @@ impl Bson {

/// If `Bson` is `DateTime`, return a mutable reference to its value. Returns `None`
/// otherwise
pub fn as_datetime_mut(&mut self) -> Option<&mut chrono::DateTime<Utc>> {
pub fn as_datetime_mut(&mut self) -> Option<&mut crate::DateTime> {
match *self {
Bson::DateTime(ref mut v) => Some(v),
_ => None,
Expand Down Expand Up @@ -973,76 +970,78 @@ impl Timestamp {
}
}

/// `DateTime` representation in struct for serde serialization
/// Struct representing a BSON datetime.
///
/// Is is recommended to use a [`chrono::DateTime`] for date operations
/// and to convert it to/from a [`crate::DateTime`] via the `From`/`Into` implementations.
///
/// ```
/// use chrono::prelude::*;
/// # fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
/// let chrono_dt: chrono::DateTime<Utc> = "2014-11-28T12:00:09Z".parse()?;
/// let bson_dt: bson::DateTime = chrono_dt.into();
/// let back_to_chrono: chrono::DateTime<Utc> = bson_dt.into();
/// # Ok(())
/// # }
/// ```
///
/// Just a helper for convenience
/// This type differs from [`chrono::DateTime`] in that it serializes to and deserializes from a
/// BSON datetime rather than an ISO-8601 formatted string. This means that in non-BSON formats, it
/// will serialize to and deserialize from that format's equivalent of the [extended JSON representation](https://docs.mongodb.com/manual/reference/mongodb-extended-json/) of a datetime. To serialize a
/// [`chrono::DateTime`] as a BSON datetime, you can use
/// [`serde_helpers::chrono_0_4_datetime_as_bson_datetime`].
///
/// ```rust
/// use serde::{Serialize, Deserialize};
/// use bson::DateTime;
///
/// #[derive(Serialize, Deserialize)]
/// struct Foo {
/// date_time: DateTime,
/// // serializes as a BSON datetime.
/// date_time: bson::DateTime,
///
/// // serializes as an ISO-8601 string.
/// chrono_datetime: chrono::DateTime<chrono::Utc>,
///
/// // serializes as a BSON datetime.
/// #[serde(with = "bson::serde_helpers::chrono_0_4_datetime_as_bson_datetime")]
/// chrono_as_bson: chrono::DateTime<chrono::Utc>,
/// }
/// ```
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
pub struct DateTime(pub chrono::DateTime<Utc>);

impl DateTime {
pub(crate) fn from_i64(date: i64) -> Self {
let mut num_secs = date / 1000;
let mut num_millis = date % 1000;

// The chrono API only lets us create a DateTime with an i64 number of seconds
// and a u32 number of nanoseconds. In the case of a negative timestamp, this
// means that we need to turn the negative fractional part into a positive and
// shift the number of seconds down. For example:
//
// date = -4300 ms
// num_secs = date / 1000 = -4300 / 1000 = -4
// num_millis = date % 1000 = -4300 % 1000 = -300
//
// Since num_millis is less than 0:
// num_secs = num_secs -1 = -4 - 1 = -5
// num_millis = num_nanos + 1000 = -300 + 1000 = 700
//
// Instead of -4 seconds and -300 milliseconds, we now have -5 seconds and +700
// milliseconds, which expresses the same timestamp, but in a way we can create
// a DateTime with.
if num_millis < 0 {
num_secs -= 1;
num_millis += 1000;
};
pub struct DateTime(chrono::DateTime<Utc>);

Utc.timestamp(num_secs, num_millis as u32 * 1_000_000)
.into()
impl crate::DateTime {
pub(crate) fn from_millis(date: i64) -> Self {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about making this public, but apparently too-large values here can cause panics (RUST-799), so I just left it private for now until we decide how / if we want to tackle this publicly.

Utc.timestamp_millis(date).into()
}
}

impl Deref for DateTime {
type Target = chrono::DateTime<Utc>;
/// Returns the number of non-leap-milliseconds since January 1, 1970 UTC.
pub fn timestamp_millis(&self) -> i64 {
self.0.timestamp_millis()
}

fn deref(&self) -> &Self::Target {
&self.0
/// If this DateTime is sub-millisecond precision. BSON only supports millisecond precision, so
/// this should be checked before serializing to BSON.
pub(crate) fn is_sub_millis_precision(&self) -> bool {
self.0.timestamp_subsec_micros() % 1000 != 0
}
}

impl DerefMut for DateTime {
fn deref_mut(&mut self) -> &mut chrono::DateTime<Utc> {
&mut self.0
impl Display for crate::DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}

impl From<DateTime> for chrono::DateTime<Utc> {
impl From<crate::DateTime> for chrono::DateTime<Utc> {
fn from(utc: DateTime) -> Self {
utc.0
}
}

impl From<chrono::DateTime<Utc>> for DateTime {
fn from(x: chrono::DateTime<Utc>) -> Self {
DateTime(x)
impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for crate::DateTime {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be more general since it could be a little annoying to convert from DateTimes with other timezones otherwise.

fn from(x: chrono::DateTime<T>) -> Self {
DateTime(x.with_timezone(&Utc))
}
}

Expand Down
23 changes: 11 additions & 12 deletions src/de/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ use crate::Bson;
#[non_exhaustive]
pub enum Error {
/// A [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) encountered while deserializing.
IoError(Arc<io::Error>),
Io(Arc<io::Error>),

/// A [`std::string::FromUtf8Error`](https://doc.rust-lang.org/std/string/struct.FromUtf8Error.html) encountered
/// while decoding a UTF-8 String from the input data.
FromUtf8Error(string::FromUtf8Error),
InvalidUtf8String(string::FromUtf8Error),

/// While decoding a `Document` from bytes, an unexpected or unsupported element type was
/// encountered.
#[non_exhaustive]
UnrecognizedDocumentElementType {
/// The key at which an unexpected/unsupported element type was encountered.
key: String,
Expand All @@ -25,13 +26,11 @@ pub enum Error {
element_type: u8,
},

/// There was an error with the syntactical structure of the BSON.
SyntaxError { message: String },

/// The end of the BSON input was reached too soon.
EndOfStream,

/// An invalid datetime was encountered while decoding.
#[non_exhaustive]
InvalidDateTime {
/// The key at which an unexpected/unsupported datetime was encountered.
key: String,
Expand All @@ -42,6 +41,7 @@ pub enum Error {

/// A general error encountered during deserialization.
/// See: https://docs.serde.rs/serde/de/trait.Error.html
#[non_exhaustive]
DeserializationError {
/// A message describing the error.
message: String,
Expand All @@ -50,21 +50,21 @@ pub enum Error {

impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::IoError(Arc::new(err))
Error::Io(Arc::new(err))
}
}

impl From<string::FromUtf8Error> for Error {
fn from(err: string::FromUtf8Error) -> Error {
Error::FromUtf8Error(err)
Error::InvalidUtf8String(err)
}
}

impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::IoError(ref inner) => inner.fmt(fmt),
Error::FromUtf8Error(ref inner) => inner.fmt(fmt),
Error::Io(ref inner) => inner.fmt(fmt),
Error::InvalidUtf8String(ref inner) => inner.fmt(fmt),
Error::UnrecognizedDocumentElementType {
ref key,
element_type,
Expand All @@ -73,7 +73,6 @@ impl fmt::Display for Error {
"unrecognized element type for key \"{}\": `{:#x}`",
key, element_type
),
Error::SyntaxError { ref message } => message.fmt(fmt),
Error::EndOfStream => fmt.write_str("end of stream"),
Error::DeserializationError { ref message } => message.fmt(fmt),
Error::InvalidDateTime { ref key, datetime } => {
Expand All @@ -86,8 +85,8 @@ impl fmt::Display for Error {
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::IoError(ref inner) => Some(inner.as_ref()),
Error::FromUtf8Error(ref inner) => Some(inner),
Error::Io(ref inner) => Some(inner.as_ref()),
Error::InvalidUtf8String(ref inner) => Some(inner),
_ => None,
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ pub(crate) fn deserialize_bson_kvp<R: Read + ?Sized>(
for x in &mut objid {
*x = read_u8(reader)?;
}
Bson::ObjectId(oid::ObjectId::with_bytes(objid))
Bson::ObjectId(oid::ObjectId::from_bytes(objid))
}
Some(ElementType::Boolean) => {
let val = read_u8(reader)?;
Expand Down Expand Up @@ -327,7 +327,7 @@ pub(crate) fn deserialize_bson_kvp<R: Read + ?Sized>(
let time = read_i64(reader)?;

match Utc.timestamp_millis_opt(time) {
LocalResult::Single(t) => Bson::DateTime(t),
LocalResult::Single(t) => Bson::DateTime(t.into()),
_ => {
return Err(Error::InvalidDateTime {
key,
Expand All @@ -345,7 +345,7 @@ pub(crate) fn deserialize_bson_kvp<R: Read + ?Sized>(
reader.read_exact(&mut objid)?;
Bson::DbPointer(DbPointer {
namespace,
id: oid::ObjectId::with_bytes(objid),
id: oid::ObjectId::from_bytes(objid),
})
}
Some(ElementType::MaxKey) => Bson::MaxKey,
Expand Down
Loading