Skip to content

RUST-1406 Add type-specific errors to standard error type #555

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

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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
41 changes: 10 additions & 31 deletions src/binary.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
#! Module containing functionality related to BSON binary values.

mod vector;

use crate::{base64, spec::BinarySubtype, Document, RawBinaryRef};
use std::{
convert::TryFrom,
error,
fmt::{self, Display},
};

use crate::{
base64,
error::{Error, Result},
spec::BinarySubtype,
Document,
RawBinaryRef,
};

pub use vector::{PackedBitVector, Vector};

/// Represents a BSON binary value.
Expand Down Expand Up @@ -38,7 +43,7 @@ impl Binary {
/// [`BinarySubtype::Generic`].
///
/// ```rust
/// # use bson::{Binary, binary::Result};
/// # use bson::{Binary, error::Result};
/// # fn example() -> Result<()> {
/// let input = base64::encode("hello");
/// let binary = Binary::from_base64(input, None)?;
Expand All @@ -51,9 +56,7 @@ impl Binary {
input: impl AsRef<str>,
subtype: impl Into<Option<BinarySubtype>>,
) -> Result<Self> {
let bytes = base64::decode(input.as_ref()).map_err(|e| Error::DecodingError {
message: e.to_string(),
})?;
let bytes = base64::decode(input.as_ref()).map_err(Error::binary)?;
let subtype = match subtype.into() {
Some(s) => s,
None => BinarySubtype::Generic,
Expand Down Expand Up @@ -97,27 +100,3 @@ impl Binary {
}
}
}

/// Possible errors that can arise during [`Binary`] construction.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
/// While trying to decode from base64, an error was returned.
DecodingError { message: String },

/// A [`Vector`]-related error occurred.
Vector { message: String },
}

impl error::Error for Error {}

impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::DecodingError { message } => fmt.write_str(message),
Error::Vector { message } => fmt.write_str(message),
}
}
}

pub type Result<T> = std::result::Result<T, Error>;
59 changes: 26 additions & 33 deletions src/binary/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const PACKED_BIT: u8 = 0x10;
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::{binary::{Result, Vector}, spec::ElementType};
/// # use bson::{binary::Vector, error::Result, spec::ElementType};
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// vector: Vector,
Expand Down Expand Up @@ -66,7 +66,7 @@ impl PackedBitVector {
/// single-bit elements in little-endian format. For example, the following vector:
///
/// ```rust
/// # use bson::binary::{Result, PackedBitVector};
/// # use bson::{binary::PackedBitVector, error::Result};
/// # fn main() -> Result<()> {
/// let packed_bits = vec![238, 224];
/// let vector = PackedBitVector::new(packed_bits, 0)?;
Expand All @@ -93,17 +93,14 @@ impl PackedBitVector {
pub fn new(vector: Vec<u8>, padding: impl Into<Option<u8>>) -> Result<Self> {
let padding = padding.into().unwrap_or(0);
if !(0..8).contains(&padding) {
return Err(Error::Vector {
message: format!("padding must be within 0-7 inclusive, got {}", padding),
});
return Err(Error::binary(format!(
"vector padding must be within 0-7 inclusive, got {padding}"
)));
}
if padding != 0 && vector.is_empty() {
return Err(Error::Vector {
message: format!(
"cannot specify non-zero padding if the provided vector is empty, got {}",
padding
),
});
return Err(Error::binary(format!(
"cannot specify non-zero padding if the provided vector is empty, got {padding}",
)));
}
Ok(Self { vector, padding })
}
Expand All @@ -117,24 +114,19 @@ impl Vector {
let bytes = bytes.as_ref();

if bytes.len() < 2 {
return Err(Error::Vector {
message: format!(
"the provided bytes must have a length of at least 2, got {}",
bytes.len()
),
});
return Err(Error::binary(format!(
"the provided vector bytes must have a length of at least 2, got {}",
bytes.len()
)));
}

let d_type = bytes[0];
let padding = bytes[1];
if d_type != PACKED_BIT && padding != 0 {
return Err(Error::Vector {
message: format!(
"padding can only be specified for a packed bit vector (data type {}), got \
type {}",
PACKED_BIT, d_type
),
});
return Err(Error::binary(format!(
"padding can only be specified for a packed bit vector (data type {}), got type {}",
PACKED_BIT, d_type
)));
}
let number_bytes = &bytes[2..];

Expand All @@ -151,11 +143,11 @@ impl Vector {

let mut vector = Vec::new();
for chunk in number_bytes.chunks(F32_BYTES) {
let bytes: [u8; F32_BYTES] = chunk.try_into().map_err(|_| Error::Vector {
message: format!(
let bytes: [u8; F32_BYTES] = chunk.try_into().map_err(|_| {
Error::binary(format!(
"f32 vector values must be {} bytes, got {:?}",
F32_BYTES, chunk,
),
))
})?;
vector.push(f32::from_le_bytes(bytes));
}
Expand All @@ -165,9 +157,9 @@ impl Vector {
let packed_bit_vector = PackedBitVector::new(number_bytes.to_vec(), padding)?;
Ok(Self::PackedBit(packed_bit_vector))
}
other => Err(Error::Vector {
message: format!("unsupported vector data type: {}", other),
}),
other => Err(Error::binary(format!(
"unsupported vector data type: {other}"
))),
}
}

Expand Down Expand Up @@ -230,9 +222,10 @@ impl TryFrom<&Binary> for Vector {

fn try_from(binary: &Binary) -> Result<Self> {
if binary.subtype != BinarySubtype::Vector {
return Err(Error::Vector {
message: format!("expected vector binary subtype, got {:?}", binary.subtype),
});
return Err(Error::binary(format!(
"expected vector binary subtype, got {:?}",
binary.subtype
)));
}
Self::from_bytes(&binary.bytes)
}
Expand Down
49 changes: 8 additions & 41 deletions src/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
//! Module containing functionality related to BSON DateTimes.
//! For more information, see the documentation for the [`DateTime`] type.
pub(crate) mod builder;

use std::{
convert::TryInto,
error,
fmt::{self, Display},
result,
time::{Duration, SystemTime},
};

pub(crate) mod builder;
pub use crate::datetime::builder::DateTimeBuilder;
use time::format_description::well_known::Rfc3339;

#[cfg(feature = "chrono-0_4")]
use chrono::{LocalResult, TimeZone, Utc};
#[cfg(all(
feature = "serde_with-3",
any(feature = "chrono-0_4", feature = "time-0_3")
))]
use serde::{Deserialize, Deserializer, Serialize};
use time::format_description::well_known::Rfc3339;

pub use crate::datetime::builder::DateTimeBuilder;
use crate::error::{Error, Result};

/// Struct representing a BSON datetime.
/// Note: BSON datetimes have millisecond precision.
Expand Down Expand Up @@ -390,19 +389,14 @@ impl crate::DateTime {
pub fn try_to_rfc3339_string(self) -> Result<String> {
self.to_time_0_3()
.format(&Rfc3339)
.map_err(|e| Error::CannotFormat {
message: e.to_string(),
})
.map_err(Error::cannot_format_datetime)
}

/// Convert the given RFC 3339 formatted string to a [`DateTime`], truncating it to millisecond
/// precision.
pub fn parse_rfc3339_str(s: impl AsRef<str>) -> Result<Self> {
let odt = time::OffsetDateTime::parse(s.as_ref(), &Rfc3339).map_err(|e| {
Error::InvalidTimestamp {
message: e.to_string(),
}
})?;
let odt = time::OffsetDateTime::parse(s.as_ref(), &Rfc3339)
.map_err(Error::invalid_datetime_value)?;
Ok(Self::from_time_0_3(odt))
}

Expand Down Expand Up @@ -547,30 +541,3 @@ impl serde_with::SerializeAs<time::OffsetDateTime> for crate::DateTime {
dt.serialize(serializer)
}
}

/// Errors that can occur during [`DateTime`] construction and generation.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
/// Error returned when an invalid datetime format is provided to a conversion method.
#[non_exhaustive]
InvalidTimestamp { message: String },
/// Error returned when a [`DateTime`] cannot be represented in a particular format.
#[non_exhaustive]
CannotFormat { message: String },
}

/// Alias for `Result<T, DateTime::Error>`
pub type Result<T> = result::Result<T, Error>;

impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::InvalidTimestamp { message } | Error::CannotFormat { message } => {
write!(fmt, "{}", message)
}
}
}
}

impl error::Error for Error {}
16 changes: 9 additions & 7 deletions src/datetime/builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use super::*;
use std::convert::TryFrom;

use time::Date;

use crate::{
datetime::DateTime,
error::{Error, Result},
};

/// Builder for constructing a BSON [`DateTime`]
pub struct DateTimeBuilder<Y = NoYear, M = NoMonth, D = NoDay> {
pub(crate) year: Y,
Expand Down Expand Up @@ -169,19 +174,16 @@ impl DateTimeBuilder<Year, Month, Day> {
///
/// Note: You cannot call `build()` before setting at least the year, month and day.
pub fn build(self) -> Result<DateTime> {
let err = |e: time::error::ComponentRange| Error::InvalidTimestamp {
message: e.to_string(),
};
let month = time::Month::try_from(self.month.0).map_err(err)?;
let month = time::Month::try_from(self.month.0).map_err(Error::invalid_datetime_value)?;
let dt = Date::from_calendar_date(self.year.0, month, self.day.0)
.map_err(err)?
.map_err(Error::invalid_datetime_value)?
.with_hms_milli(
self.hour.unwrap_or(0),
self.minute.unwrap_or(0),
self.second.unwrap_or(0),
self.millisecond.unwrap_or(0),
)
.map_err(err)?;
.map_err(Error::invalid_datetime_value)?;
Ok(DateTime::from_time_private(dt.assume_utc()))
}
}
Loading