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

Merged
merged 13 commits into from
Jun 18, 2025
41 changes: 10 additions & 31 deletions src/binary.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
#! Module containing functionality related to BSON binary values.

mod vector;

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

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

pub use vector::{PackedBitVector, Vector};

/// Represents a BSON binary value.
@@ -37,7 +42,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)?;
@@ -50,9 +55,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,
@@ -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
@@ -23,7 +23,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,
@@ -64,7 +64,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)?;
@@ -91,17 +91,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 })
}
@@ -115,24 +112,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..];

@@ -149,11 +141,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));
}
@@ -163,9 +155,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}"
))),
}
}

@@ -228,9 +220,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)
}
50 changes: 7 additions & 43 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.
@@ -388,21 +387,13 @@ impl crate::DateTime {

/// Convert this [`DateTime`] to an RFC 3339 formatted string.
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(),
})
self.to_time_0_3().format(&Rfc3339).map_err(Error::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::datetime)?;
Ok(Self::from_time_0_3(odt))
}

@@ -543,30 +534,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,
@@ -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::datetime)?;
let dt = Date::from_calendar_date(self.year.0, month, self.day.0)
.map_err(err)?
.map_err(Error::datetime)?
.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::datetime)?;
Ok(DateTime::from_time_private(dt.assume_utc()))
}
}
110 changes: 39 additions & 71 deletions src/decimal128.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! [BSON Decimal128](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) data type representation
use std::{convert::TryInto, fmt};
use std::{convert::TryInto, fmt, num::ParseIntError};

use bitvec::prelude::*;

use crate::error::{Decimal128ErrorKind, Error, Result};

/// Struct representing a BSON Decimal128 type.
///
/// This type supports conversion to and from human-readable strings via the [std::fmt::Display] and
@@ -60,9 +62,9 @@ impl fmt::Display for Decimal128 {
}

impl std::str::FromStr for Decimal128 {
type Err = ParseError;
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
fn from_str(s: &str) -> Result<Self> {
Ok(s.parse::<ParsedDecimal128>()?.pack())
}
}
@@ -138,18 +140,15 @@ impl Coefficient {
/// 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<u8, Msb0>,
src_suffix: &BitSlice<u8, Msb0>,
) -> Result<Self, ParseError> {
fn from_bits(src_prefix: &BitSlice<u8, Msb0>, src_suffix: &BitSlice<u8, Msb0>) -> Result<Self> {
let mut bytes = [0u8; 16];
let bits = &mut bytes.view_bits_mut::<Msb0>()[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)
Err(Error::decimal128(Decimal128ErrorKind::Overflow {}))
} else {
Ok(out)
}
@@ -325,46 +324,10 @@ impl fmt::Display for ParsedDecimal128 {
}
}

#[derive(Debug)]
#[non_exhaustive]
pub enum ParseError {
EmptyExponent,
InvalidExponent(std::num::ParseIntError),
InvalidCoefficient(std::num::ParseIntError),
Overflow,
Underflow,
InexactRounding,
Unparseable,
}

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"),
ParseError::Unparseable => write!(f, "unparseable"),
}
}
}

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;
type Err = Error;

fn from_str(mut s: &str) -> Result<Self, Self::Err> {
fn from_str(mut s: &str) -> Result<Self> {
let sign;
if let Some(rest) = s.strip_prefix(&['-', '+'][..]) {
sign = s.starts_with('-');
@@ -385,21 +348,28 @@ impl std::str::FromStr for ParsedDecimal128 {
decimal_str = finite_str;
exp_str = "0";
}
Some((_, "")) => return Err(ParseError::EmptyExponent),
Some((_, "")) => {
return Err(Error::decimal128(Decimal128ErrorKind::EmptyExponent {}))
}
Some((pre, post)) => {
decimal_str = pre;
exp_str = post;
}
}
let mut exp = exp_str
.parse::<i16>()
.map_err(ParseError::InvalidExponent)?;
let mut exp = exp_str.parse::<i16>().map_err(|e| {
Error::decimal128(Decimal128ErrorKind::InvalidExponent {}).with_message(e)
})?;

// 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)?;
let exp_adj = post
.len()
.try_into()
.map_err(|_| Error::decimal128(Decimal128ErrorKind::Underflow {}))?;
exp = exp
.checked_sub(exp_adj)
.ok_or_else(|| Error::decimal128(Decimal128ErrorKind::Underflow {}))?;
joined_str = format!("{}{}", pre, post);
decimal_str = &joined_str;
}
@@ -415,8 +385,10 @@ impl std::str::FromStr for ParsedDecimal128 {
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)?;
.map_err(|_| Error::decimal128(Decimal128ErrorKind::Overflow {}))?;
exp = exp
.checked_add(exp_adj)
.ok_or_else(|| Error::decimal128(Decimal128ErrorKind::Overflow {}))?;
}
}

@@ -425,11 +397,11 @@ impl std::str::FromStr for ParsedDecimal128 {
if decimal_str != "0" {
let delta = (Exponent::TINY - exp)
.try_into()
.map_err(|_| ParseError::Overflow)?;
.map_err(|_| Error::decimal128(Decimal128ErrorKind::Overflow {}))?;
let new_precision = decimal_str
.len()
.checked_sub(delta)
.ok_or(ParseError::Underflow)?;
.ok_or_else(|| Error::decimal128(Decimal128ErrorKind::Overflow {}))?;
decimal_str = round_decimal_str(decimal_str, new_precision)?;
}
exp = Exponent::TINY;
@@ -439,14 +411,14 @@ impl std::str::FromStr for ParsedDecimal128 {
if decimal_str != "0" {
let delta = (exp - Exponent::MAX)
.try_into()
.map_err(|_| ParseError::Overflow)?;
.map_err(|_| Error::decimal128(Decimal128ErrorKind::Overflow {}))?;
if decimal_str
.len()
.checked_add(delta)
.ok_or(ParseError::Overflow)?
.ok_or_else(|| Error::decimal128(Decimal128ErrorKind::Overflow {}))?
> Coefficient::MAX_DIGITS
{
return Err(ParseError::Overflow);
return Err(Error::decimal128(Decimal128ErrorKind::Overflow {}));
}
padded_str = format!("{}{}", decimal_str, "0".repeat(delta));
decimal_str = &padded_str;
@@ -456,9 +428,9 @@ impl std::str::FromStr for ParsedDecimal128 {

// Assemble the final value
let exponent = Exponent::from_native(exp);
let coeff: u128 = decimal_str
.parse()
.map_err(ParseError::InvalidCoefficient)?;
let coeff: u128 = decimal_str.parse().map_err(|e: ParseIntError| {
Error::decimal128(Decimal128ErrorKind::InvalidCoefficient {}).with_message(e)
})?;
let coefficient = Coefficient::from_native(coeff);
Decimal128Kind::Finite {
exponent,
@@ -471,17 +443,13 @@ impl std::str::FromStr for ParsedDecimal128 {
}
}

fn round_decimal_str(s: &str, precision: usize) -> Result<&str, ParseError> {
// TODO: In 1.80+ there's split_at_checked to make sure the split doesn't
// panic if the index doesn't falls at a codepoint boundary, until then
// we can check it with s.is_char_boundary(precision)
if !s.is_char_boundary(precision) {
return Err(ParseError::Unparseable);
}
let (pre, post) = s.split_at(precision);
fn round_decimal_str(s: &str, precision: usize) -> Result<&str> {
let (pre, post) = s
.split_at_checked(precision)
.ok_or_else(|| Error::decimal128(Decimal128ErrorKind::Unparseable {}))?;
// Any nonzero trimmed digits mean it would be an imprecise round.
if post.chars().any(|c| c != '0') {
return Err(ParseError::InexactRounding);
return Err(Error::decimal128(Decimal128ErrorKind::InexactRounding {}));
}
Ok(pre)
}
158 changes: 77 additions & 81 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
mod decimal128;
mod oid;
mod uuid;
mod value_access;

use thiserror::Error;

use crate::spec::ElementType;
pub use decimal128::Decimal128ErrorKind;
pub use oid::ObjectIdErrorKind;
pub use uuid::UuidErrorKind;
pub use value_access::ValueAccessErrorKind;

pub type Result<T> = std::result::Result<T, Error>;

/// An error that can occur in the `bson` crate.
#[derive(Debug, Error)]
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub struct Error {
/// The kind of error that occurred.
pub kind: ErrorKind,

/// An optional message describing the error.
pub message: Option<String>,

/// The document key associated with the error, if any.
pub key: Option<String>,

@@ -20,28 +31,69 @@ pub struct Error {

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BSON error")?;

if let Some(key) = self.key.as_deref() {
write!(f, "Error at key \"{key}\": ")?;
write!(f, " at key \"{key}\"")?;
} else if let Some(index) = self.index {
write!(f, "Error at array index {index}: ")?;
write!(f, " at array index {index}")?;
}

write!(f, "{}", self.kind)
write!(f, ". Kind: {}", self.kind)?;
if let Some(ref message) = self.message {
write!(f, ". Message: {}", message)?;
}
write!(f, ".")
}
}

/// The types of errors that can occur in the `bson` crate.
#[derive(Debug, Error)]
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum ErrorKind {
/// An error related to the [`Binary`](crate::Binary) type occurred.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is exposing how arbitrary it is which of our errors carry a string and which carry a subkind enum; that's got a bit of a code smell to it but I can't decide if it's actually a problem worth addressing. Do you have thoughts here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I don't love this design; I agree that it's arbitrary and inconsistent. My concern, however, is that users who do have some kind of programmatic use for the values in these errors will be less inclined to migrate to the new version, so my instinct here is to err on the side of portability for lower-priority changes. Certainly open to other designs here if you had something else in mind though!

Copy link
Contributor

Choose a reason for hiding this comment

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

Sketch of a proposal:

  • Hoist message to an optional field of Error, with corresponding code in the Display impl to append it if it's present.
  • All variants of ErrorKind are required to be #[non_exhaustive] struct variants, even if they're empty (which a lot now will be with message stripped out)
  • Any sub-Kind types follow the same pattern (sub-Kinds also don't need to have message since they're ultimately attached to an Error!)

This makes the error space a lot more uniform, gives us a lot of flexibility for future error evolution, and doesn't introduce substantial migration burden.

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 like this a lot more - updated with your suggestions!

#[error("A Binary-related error occurred")]
#[non_exhaustive]
Binary {},

/// An error related to the [`DateTime`](crate::DateTime) type occurred.
#[error("A DateTime-related error occurred")]
#[non_exhaustive]
DateTime {},

/// An error related to the [`Decimal128`](crate::Decimal128) type occurred.
#[error("A Decimal128-related error occurred: {kind}")]
#[non_exhaustive]
Decimal128 {
/// The kind of error that occurred.
kind: Decimal128ErrorKind,
},

/// Malformed BSON bytes were encountered.
#[error("Malformed BSON: {message}")]
#[error("Malformed BSON bytes")]
#[non_exhaustive]
MalformedValue { message: String },
MalformedBytes {},

/// An error related to the [`ObjectId`](crate::oid::ObjectId) type occurred.
#[error("An ObjectId-related error occurred: {kind}")]
#[non_exhaustive]
ObjectId {
/// The kind of error that occurred.
kind: ObjectIdErrorKind,
},

/// Invalid UTF-8 bytes were encountered.
#[error("Invalid UTF-8")]
Utf8Encoding,
#[non_exhaustive]
Utf8Encoding {},

/// An error related to the [`Uuid`](crate::uuid::Uuid) type occurred.
#[error("A UUID-related error occurred: {kind}")]
#[non_exhaustive]
Uuid {
/// The kind of error that occurred.
kind: UuidErrorKind,
},

/// An error occurred when attempting to access a value in a document.
#[error("An error occurred when attempting to access a document value: {kind}")]
@@ -52,8 +104,9 @@ pub enum ErrorKind {
},

/// A [`std::io::Error`] occurred.
#[error("An IO error occurred: {0}")]
Io(std::io::Error),
#[error("An IO error occurred")]
#[non_exhaustive]
Io {},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

switched this over to a stringified version of the error - std::io::Error is not Clone, and although I'm not worried about the stability of a standard library error, keeping external types out of our API allows for more flexibility in situations like RUST-1798

Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to carry the std::io::ErrorKind but that can be added later if it's asked for :)


/// A wrapped deserialization error.
/// TODO RUST-1406: collapse this
@@ -68,13 +121,14 @@ impl From<ErrorKind> for Error {
kind,
key: None,
index: None,
message: None,
}
}
}

impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
ErrorKind::Io(value).into()
Error::from(ErrorKind::Io {}).with_message(value)
}
}

@@ -85,31 +139,6 @@ impl From<crate::de::Error> for Error {
}
}

/// The types of errors that can occur when attempting to access a value in a document.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ValueAccessErrorKind {
/// No value for the specified key was present in the document.
#[error("The key was not present in the document")]
NotPresent,

/// The type of the value in the document did not match the requested type.
#[error("Expected type {expected:?}, got type {actual:?}")]
#[non_exhaustive]
UnexpectedType {
/// The actual type of the value.
actual: ElementType,

/// The expected type of the value.
expected: ElementType,
},

/// An error occurred when attempting to parse the document's BSON bytes.
#[error("{message}")]
#[non_exhaustive]
InvalidBson { message: String },
}

impl Error {
pub(crate) fn with_key(mut self, key: impl Into<String>) -> Self {
self.key = Some(key.into());
@@ -121,58 +150,25 @@ impl Error {
self
}

pub(crate) fn value_access_not_present() -> Self {
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::NotPresent,
}
.into()
}

pub(crate) fn value_access_unexpected_type(actual: ElementType, expected: ElementType) -> Self {
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::UnexpectedType { actual, expected },
}
.into()
}

pub(crate) fn value_access_invalid_bson(message: String) -> Self {
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::InvalidBson { message },
}
.into()
pub(crate) fn with_message(mut self, message: impl ToString) -> Self {
self.message = Some(message.to_string());
self
}

pub(crate) fn malformed_value(message: impl ToString) -> Self {
ErrorKind::MalformedValue {
message: message.to_string(),
}
.into()
pub(crate) fn binary(message: impl ToString) -> Self {
Self::from(ErrorKind::Binary {}).with_message(message)
}

#[cfg(test)]
pub(crate) fn is_value_access_not_present(&self) -> bool {
matches!(
self.kind,
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::NotPresent,
..
}
)
pub(crate) fn datetime(message: impl ToString) -> Self {
Self::from(ErrorKind::DateTime {}).with_message(message)
}

#[cfg(test)]
pub(crate) fn is_value_access_unexpected_type(&self) -> bool {
matches!(
self.kind,
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::UnexpectedType { .. },
..
}
)
pub(crate) fn malformed_bytes(message: impl ToString) -> Self {
Self::from(ErrorKind::MalformedBytes {}).with_message(message)
}

#[cfg(all(test, feature = "serde"))]
pub(crate) fn is_malformed_value(&self) -> bool {
matches!(self.kind, ErrorKind::MalformedValue { .. },)
pub(crate) fn is_malformed_bytes(&self) -> bool {
matches!(self.kind, ErrorKind::MalformedBytes { .. },)
}
}
59 changes: 59 additions & 0 deletions src/error/decimal128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use thiserror::Error as ThisError;

use crate::error::{Error, ErrorKind};

/// The kinds of errors that can occur when working with the [`Decimal128`](crate::Decimal128) type.
#[derive(Clone, Debug, ThisError)]
#[non_exhaustive]
pub enum Decimal128ErrorKind {
/// Empty exponent.
#[error("empty exponent")]
#[non_exhaustive]
EmptyExponent {},

/// Invalid exponent.
#[error("invalid exponent")]
#[non_exhaustive]
InvalidExponent {},

/// Invalid coefficient.
#[error("invalid coefficient")]
#[non_exhaustive]
InvalidCoefficient {},

/// Overflow.
#[error("overflow")]
#[non_exhaustive]
Overflow {},

/// Underflow.
#[error("underflow")]
#[non_exhaustive]
Underflow {},

/// Inexact rounding.
#[error("inexact rounding")]
#[non_exhaustive]
InexactRounding {},

/// Unparseable.
#[error("unparseable")]
#[non_exhaustive]
Unparseable {},
}

impl Error {
pub(crate) fn decimal128(kind: Decimal128ErrorKind) -> Self {
ErrorKind::Decimal128 { kind }.into()
}

#[cfg(test)]
pub(crate) fn is_decimal128_unparseable(&self) -> bool {
matches!(
self.kind,
ErrorKind::Decimal128 {
kind: Decimal128ErrorKind::Unparseable {},
}
)
}
}
52 changes: 52 additions & 0 deletions src/error/oid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use hex::FromHexError;
use thiserror::Error as ThisError;

use crate::error::{Error, ErrorKind};

/// The kinds of errors that can occur when working with the [`ObjectId`](crate::oid::ObjectId)
/// type.
#[derive(Clone, Debug, ThisError)]
#[non_exhaustive]
pub enum ObjectIdErrorKind {
/// An invalid character was found in the provided hex string. Valid characters are: `0...9`,
/// `a...f`, or `A...F`.
#[error("invalid character '{c}' encountered at index {index}")]
#[non_exhaustive]
InvalidHexStringCharacter {
/// The invalid character.
c: char,

/// The index at which the invalid character was encountered.
index: usize,
},

/// An `ObjectId` with an invalid length was encountered.
#[error("invalid hex string length {length}")]
#[non_exhaustive]
InvalidHexStringLength {
/// The length of the invalid hex string.
length: usize,
},
}

impl Error {
// This method is not a From implementation so that it is not part of the public API.
pub(crate) fn from_hex_error(error: FromHexError, length: usize) -> Self {
let kind = match error {
FromHexError::InvalidHexCharacter { c, index } => {
ObjectIdErrorKind::InvalidHexStringCharacter { c, index }
}
FromHexError::InvalidStringLength | FromHexError::OddLength => {
ObjectIdErrorKind::InvalidHexStringLength { length }
}
};
ErrorKind::ObjectId { kind }.into()
}

pub(crate) fn oid_invalid_length(length: usize) -> Self {
ErrorKind::ObjectId {
kind: ObjectIdErrorKind::InvalidHexStringLength { length },
}
.into()
}
}
74 changes: 74 additions & 0 deletions src/error/uuid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use thiserror::Error as ThisError;

use crate::{
error::{Error, ErrorKind},
spec::BinarySubtype,
UuidRepresentation,
};

/// The kinds of errors that can occur when working with the [`Uuid`](crate::uuid::Uuid) type.
#[derive(Clone, Debug, ThisError)]
#[non_exhaustive]
pub enum UuidErrorKind {
/// An invalid string was used to construct a UUID.
#[error("invalid UUID string")]
#[non_exhaustive]
InvalidString {},

/// The requested `UuidRepresentation` does not match the binary subtype of a `Binary`
/// value.
#[error(
"expected binary subtype {expected_binary_subtype:?} for representation \
{requested_representation:?}, got {actual_binary_subtype:?}"
)]
#[non_exhaustive]
RepresentationMismatch {
/// The subtype that was expected given the requested representation.
expected_binary_subtype: BinarySubtype,

/// The actual subtype of the binary value.
actual_binary_subtype: BinarySubtype,

/// The requested representation.
requested_representation: UuidRepresentation,
},

/// An invalid length of bytes was used to construct a UUID value.
#[error("expected length of 16 bytes, got {length}")]
#[non_exhaustive]
InvalidLength {
/// The actual length of the data.
length: usize,
},
}

impl Error {
pub(crate) fn invalid_uuid_string(message: impl ToString) -> Self {
Self::from(ErrorKind::Uuid {
kind: UuidErrorKind::InvalidString {},
})
.with_message(message)
}

pub(crate) fn uuid_representation_mismatch(
requested_representation: UuidRepresentation,
actual_binary_subtype: BinarySubtype,
expected_binary_subtype: BinarySubtype,
) -> Self {
ErrorKind::Uuid {
kind: UuidErrorKind::RepresentationMismatch {
expected_binary_subtype,
actual_binary_subtype,
requested_representation,
},
}
.into()
}

pub(crate) fn invalid_uuid_length(length: usize) -> Self {
ErrorKind::Uuid {
kind: UuidErrorKind::InvalidLength { length },
}
.into()
}
}
77 changes: 77 additions & 0 deletions src/error/value_access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use thiserror::Error as ThisError;

use crate::{
error::{Error, ErrorKind},
spec::ElementType,
};

/// The types of errors that can occur when attempting to access a value in a document.
#[derive(Clone, Debug, ThisError)]
#[non_exhaustive]
pub enum ValueAccessErrorKind {
/// No value for the specified key was present in the document.
#[error("the key was not present in the document")]
#[non_exhaustive]
NotPresent {},

/// The type of the value in the document did not match the requested type.
#[error("expected type {expected:?}, got type {actual:?}")]
#[non_exhaustive]
UnexpectedType {
/// The actual type of the value.
actual: ElementType,

/// The expected type of the value.
expected: ElementType,
},

/// An error occurred when attempting to parse the document's BSON bytes.
#[error("invalid BSON bytes")]
#[non_exhaustive]
InvalidBson {},
}

impl Error {
pub(crate) fn value_access_not_present() -> Self {
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::NotPresent {},
}
.into()
}

pub(crate) fn value_access_unexpected_type(actual: ElementType, expected: ElementType) -> Self {
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::UnexpectedType { actual, expected },
}
.into()
}

pub(crate) fn value_access_invalid_bson(message: String) -> Self {
Self::from(ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::InvalidBson {},
})
.with_message(message)
}

#[cfg(test)]
pub(crate) fn is_value_access_not_present(&self) -> bool {
matches!(
self.kind,
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::NotPresent {},
..
}
)
}

#[cfg(test)]
pub(crate) fn is_value_access_unexpected_type(&self) -> bool {
matches!(
self.kind,
ErrorKind::ValueAccess {
kind: ValueAccessErrorKind::UnexpectedType { .. },
..
}
)
}
}
10 changes: 2 additions & 8 deletions src/extjson/de.rs
Original file line number Diff line number Diff line change
@@ -25,14 +25,14 @@ use std::convert::{TryFrom, TryInto};

use serde::de::{Error as _, Unexpected};

use crate::{extjson::models, oid, Bson, Document};
use crate::{extjson::models, Bson, Document};

#[derive(Clone, Debug)]
#[non_exhaustive]
/// Error cases that can occur during deserialization from [extended JSON](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/).
pub enum Error {
/// Errors that can occur during OID construction and generation from the input data.
InvalidObjectId(oid::Error),
InvalidObjectId(crate::error::Error),

/// A general error encountered during deserialization.
/// See: <https://docs.serde.rs/serde/de/trait.Error.html>
@@ -69,12 +69,6 @@ impl From<serde_json::Error> for Error {
}
}

impl From<oid::Error> for Error {
fn from(err: oid::Error) -> Self {
Self::InvalidObjectId(err)
}
}

pub type Result<T> = std::result::Result<T, Error>;

/// This converts from the input JSON object as if it were [MongoDB Extended JSON v2](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/).
3 changes: 2 additions & 1 deletion src/extjson/models.rs
Original file line number Diff line number Diff line change
@@ -100,7 +100,8 @@ pub(crate) struct ObjectId {

impl ObjectId {
pub(crate) fn parse(self) -> extjson::de::Result<oid::ObjectId> {
let oid = oid::ObjectId::parse_str(self.oid.as_str())?;
let oid = oid::ObjectId::parse_str(self.oid.as_str())
.map_err(extjson::de::Error::InvalidObjectId)?;
Ok(oid)
}
}
73 changes: 7 additions & 66 deletions src/oid.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
//! Module containing functionality related to BSON ObjectIds.
//! For more information, see the documentation for the [`ObjectId`] type.
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use std::{convert::TryInto, time::SystemTime};
use std::{
error,
fmt,
result,
str::FromStr,
sync::atomic::{AtomicUsize, Ordering},
};

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use std::{convert::TryInto, time::SystemTime};

use hex::{self, FromHexError};
use once_cell::sync::Lazy;
use rand::{random, rng, Rng};

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

const TIMESTAMP_SIZE: usize = 4;
const PROCESS_ID_SIZE: usize = 5;
const COUNTER_SIZE: usize = 3;
@@ -29,49 +27,6 @@ const MAX_U24: usize = 0xFF_FFFF;
static OID_COUNTER: Lazy<AtomicUsize> =
Lazy::new(|| AtomicUsize::new(rng().random_range(0..=MAX_U24)));

/// Errors that can occur during [`ObjectId`] construction and generation.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
/// An invalid character was found in the provided hex string. Valid characters are: `0...9`,
/// `a...f`, or `A...F`.
#[non_exhaustive]
InvalidHexStringCharacter { c: char, index: usize, hex: String },

/// An [`ObjectId`]'s hex string representation must be an exactly 12-byte (24-char)
/// hexadecimal string.
#[non_exhaustive]
InvalidHexStringLength { length: usize, hex: String },
}

/// Alias for Result<T, oid::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::InvalidHexStringCharacter { c, index, hex } => {
write!(
fmt,
"invalid character '{}' was found at index {} in the provided hex string: \
\"{}\"",
c, index, hex
)
}
Error::InvalidHexStringLength { length, hex } => {
write!(
fmt,
"provided hex string representation must be exactly 12 bytes, instead got: \
\"{}\", length {}",
hex, length
)
}
}
}
}

impl error::Error for Error {}

/// A wrapper around a raw 12-byte ObjectId.
///
/// ## `serde` integration
@@ -198,24 +153,10 @@ impl ObjectId {
pub fn parse_str(s: impl AsRef<str>) -> Result<ObjectId> {
let s = s.as_ref();

let bytes: Vec<u8> = hex::decode(s.as_bytes()).map_err(|e| match e {
FromHexError::InvalidHexCharacter { c, index } => Error::InvalidHexStringCharacter {
c,
index,
hex: s.to_string(),
},
FromHexError::InvalidStringLength | FromHexError::OddLength => {
Error::InvalidHexStringLength {
length: s.len(),
hex: s.to_string(),
}
}
})?;
let bytes: Vec<u8> =
hex::decode(s.as_bytes()).map_err(|e| Error::from_hex_error(e, s.len()))?;
if bytes.len() != 12 {
Err(Error::InvalidHexStringLength {
length: s.len(),
hex: s.to_string(),
})
Err(Error::oid_invalid_length(bytes.len()))
} else {
let mut byte_array: [u8; 12] = [0; 12];
byte_array[..].copy_from_slice(&bytes[..]);
28 changes: 14 additions & 14 deletions src/raw.rs
Original file line number Diff line number Diff line change
@@ -173,7 +173,7 @@ fn f64_from_slice(val: &[u8]) -> Result<f64> {
.get(0..8)
.and_then(|s| s.try_into().ok())
.ok_or_else(|| {
Error::malformed_value(format!(
Error::malformed_bytes(format!(
"expected 8 bytes to read double, instead got {}",
val.len()
))
@@ -188,7 +188,7 @@ fn i32_from_slice(val: &[u8]) -> Result<i32> {
.get(0..4)
.and_then(|s| s.try_into().ok())
.ok_or_else(|| {
Error::malformed_value(format!(
Error::malformed_bytes(format!(
"expected 4 bytes to read i32, instead got {}",
val.len()
))
@@ -203,7 +203,7 @@ fn i64_from_slice(val: &[u8]) -> Result<i64> {
.get(0..8)
.and_then(|s| s.try_into().ok())
.ok_or_else(|| {
Error::malformed_value(format!(
Error::malformed_bytes(format!(
"expected 8 bytes to read i64, instead got {}",
val.len()
))
@@ -216,7 +216,7 @@ fn u8_from_slice(val: &[u8]) -> Result<u8> {
.get(0..1)
.and_then(|s| s.try_into().ok())
.ok_or_else(|| {
Error::malformed_value(format!(
Error::malformed_bytes(format!(
"expected 1 byte to read u8, instead got {}",
val.len()
))
@@ -227,7 +227,7 @@ fn u8_from_slice(val: &[u8]) -> Result<u8> {
pub(crate) fn bool_from_slice(val: &[u8]) -> Result<bool> {
let val = u8_from_slice(val)?;
if val > 1 {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"boolean must be stored as 0 or 1, got {}",
val
)));
@@ -238,7 +238,7 @@ pub(crate) fn bool_from_slice(val: &[u8]) -> Result<bool> {

fn read_len(buf: &[u8]) -> Result<usize> {
if buf.len() < 4 {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"expected buffer with string to contain at least 4 bytes, but it only has {}",
buf.len()
)));
@@ -248,22 +248,22 @@ fn read_len(buf: &[u8]) -> Result<usize> {
let end = checked_add(usize_try_from_i32(length)?, 4)?;

if end < MIN_BSON_STRING_SIZE as usize {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"BSON length encoded string needs to be at least {} bytes, instead got {}",
MIN_BSON_STRING_SIZE, end
)));
}

if buf.len() < end {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"expected buffer to contain at least {} bytes, but it only has {}",
end,
buf.len()
)));
}

if buf[end - 1] != 0 {
return Err(Error::malformed_value(
return Err(Error::malformed_bytes(
"expected string to be null-terminated",
));
}
@@ -283,16 +283,16 @@ fn read_lenencode(buf: &[u8]) -> Result<&str> {
}

fn try_to_str(data: &[u8]) -> Result<&str> {
simdutf8::basic::from_utf8(data).map_err(|_| ErrorKind::Utf8Encoding.into())
simdutf8::basic::from_utf8(data).map_err(|_| ErrorKind::Utf8Encoding {}.into())
}

fn usize_try_from_i32(i: i32) -> Result<usize> {
usize::try_from(i).map_err(Error::malformed_value)
usize::try_from(i).map_err(Error::malformed_bytes)
}

fn checked_add(lhs: usize, rhs: usize) -> Result<usize> {
lhs.checked_add(rhs)
.ok_or_else(|| Error::malformed_value("attempted to add with overflow"))
.ok_or_else(|| Error::malformed_bytes("attempted to add with overflow"))
}

pub(crate) fn reader_to_vec<R: Read>(mut reader: R) -> Result<Vec<u8>> {
@@ -301,7 +301,7 @@ pub(crate) fn reader_to_vec<R: Read>(mut reader: R) -> Result<Vec<u8>> {
let length = i32::from_le_bytes(buf);

if length < MIN_BSON_DOCUMENT_SIZE {
return Err(Error::malformed_value("document size too small"));
return Err(Error::malformed_bytes("document size too small"));
}

let mut bytes = Vec::with_capacity(length as usize);
@@ -319,7 +319,7 @@ pub(crate) fn write_string(buf: &mut Vec<u8>, s: &str) {

pub(crate) fn write_cstring(buf: &mut Vec<u8>, s: &str) -> Result<()> {
if s.contains('\0') {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"cstring with interior null: {:?}",
s
)));
10 changes: 5 additions & 5 deletions src/raw/document.rs
Original file line number Diff line number Diff line change
@@ -96,17 +96,17 @@ impl RawDocument {
let data = data.as_ref();

if data.len() < 5 {
return Err(Error::malformed_value("document too short"));
return Err(Error::malformed_bytes("document too short"));
}

let length = i32_from_slice(data)?;

if data.len() as i32 != length {
return Err(Error::malformed_value("document length incorrect"));
return Err(Error::malformed_bytes("document length incorrect"));
}

if data[data.len() - 1] != 0 {
return Err(Error::malformed_value("document not null-terminated"));
return Err(Error::malformed_bytes("document not null-terminated"));
}

Ok(RawDocument::new_unchecked(data))
@@ -497,11 +497,11 @@ impl RawDocument {
let mut splits = buf.splitn(2, |x| *x == 0);
let value = splits
.next()
.ok_or_else(|| RawError::malformed_value("no value"))?;
.ok_or_else(|| RawError::malformed_bytes("no value"))?;
if splits.next().is_some() {
Ok(value)
} else {
Err(RawError::malformed_value("expected null terminator"))
Err(RawError::malformed_bytes("expected null terminator"))
}
}

18 changes: 9 additions & 9 deletions src/raw/iter.rs
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ impl<'a> RawIter<'a> {
fn verify_enough_bytes(&self, start: usize, num_bytes: usize) -> Result<()> {
let end = checked_add(start, num_bytes)?;
if self.doc.as_bytes().get(start..end).is_none() {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"length exceeds remaining length of buffer: {} vs {}",
num_bytes,
self.doc.as_bytes().len() - start
@@ -94,7 +94,7 @@ impl<'a> RawIter<'a> {
let size = i32_from_slice(&self.doc.as_bytes()[starting_at..])? as usize;

if size < MIN_BSON_DOCUMENT_SIZE as usize {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"document too small: {} bytes",
size
)));
@@ -103,7 +103,7 @@ impl<'a> RawIter<'a> {
self.verify_enough_bytes(starting_at, size)?;

if self.doc.as_bytes()[starting_at + size - 1] != 0 {
return Err(Error::malformed_value("not null terminated"));
return Err(Error::malformed_bytes("not null terminated"));
}
Ok(size)
}
@@ -317,7 +317,7 @@ impl<'a> RawElement<'a> {
}

fn malformed_error(&self, e: impl ToString) -> Error {
Error::malformed_value(e).with_key(self.key)
Error::malformed_bytes(e).with_key(self.key)
}

pub(crate) fn slice(&self) -> &'a [u8] {
@@ -344,7 +344,7 @@ impl<'a> RawElement<'a> {
Ok(ObjectId::from_bytes(
self.doc.as_bytes()[start_at..(start_at + 12)]
.try_into()
.map_err(|e| Error::malformed_value(e).with_key(self.key))?,
.map_err(|e| Error::malformed_bytes(e).with_key(self.key))?,
))
}
}
@@ -353,7 +353,7 @@ impl RawIter<'_> {
fn get_next_length_at(&self, start_at: usize) -> Result<usize> {
let len = i32_from_slice(&self.doc.as_bytes()[start_at..])?;
if len < 0 {
Err(Error::malformed_value("lengths can't be negative"))
Err(Error::malformed_bytes("lengths can't be negative"))
} else {
Ok(len as usize)
}
@@ -363,7 +363,7 @@ impl RawIter<'_> {
let element_type = match ElementType::from(self.doc.as_bytes()[self.offset]) {
Some(et) => et,
None => {
return Err(Error::malformed_value(format!(
return Err(Error::malformed_bytes(format!(
"invalid tag: {}",
self.doc.as_bytes()[self.offset]
)));
@@ -417,11 +417,11 @@ impl<'a> Iterator for RawIter<'a> {
return None;
} else {
self.valid = false;
return Some(Err(Error::malformed_value("document not null terminated")));
return Some(Err(Error::malformed_bytes("document not null terminated")));
}
} else if self.offset >= self.doc.as_bytes().len() {
self.valid = false;
return Some(Err(Error::malformed_value("iteration overflowed document")));
return Some(Err(Error::malformed_bytes("iteration overflowed document")));
}

let key = match self.doc.read_cstring_at(self.offset + 1) {
8 changes: 3 additions & 5 deletions src/raw/test.rs
Original file line number Diff line number Diff line change
@@ -16,13 +16,11 @@ use crate::{

#[test]
fn test_decimal128_doesnt_panic_on_bad_codepoint_boundary() {
use crate::decimal128::ParseError;
use std::str::FromStr;
// idx 34 (Coefficient::MAX_DIGITS) on this string isn't a valid codepoint boundary
assert!(matches!(
Decimal128::from_str("111111111111111111111111111111111❤"),
Err(ParseError::Unparseable)
))
assert!(Decimal128::from_str("111111111111111111111111111111111❤")
.unwrap_err()
.is_decimal128_unparseable());
}

#[test]
4 changes: 2 additions & 2 deletions src/tests/modules/ser.rs
Original file line number Diff line number Diff line change
@@ -169,11 +169,11 @@ fn cstring_null_bytes_error() {
let result = doc.encode_to_vec();
assert!(result.is_err(), "unexpected success");
let err = result.unwrap_err();
assert!(err.is_malformed_value(), "unexpected error: {:?}", err);
assert!(err.is_malformed_bytes(), "unexpected error: {:?}", err);
let result = serialize_to_vec(&doc);
assert!(result.is_err(), "unexpected success");
match result.unwrap_err().strip_path() {
ser::Error::Crate(inner) if inner.is_malformed_value() => (),
ser::Error::Crate(inner) if inner.is_malformed_bytes() => (),
err => panic!("unexpected error: {:?}", err),
};
}
98 changes: 18 additions & 80 deletions src/uuid.rs
Original file line number Diff line number Diff line change
@@ -137,7 +137,12 @@ mod test;

use std::fmt::{self, Display};

use crate::{spec::BinarySubtype, Binary, Bson};
use crate::{
error::{Error, Result},
spec::BinarySubtype,
Binary,
Bson,
};

/// Special type name used in the [`Uuid`] serialization implementation to indicate a BSON
/// UUID is being serialized or deserialized. The BSON serializers/deserializers will handle this
@@ -185,9 +190,7 @@ impl Uuid {

/// Creates a [`Uuid`] from the provided hex string.
pub fn parse_str(input: impl AsRef<str>) -> Result<Self> {
let uuid = uuid::Uuid::parse_str(input.as_ref()).map_err(|e| Error::InvalidUuidString {
message: e.to_string(),
})?;
let uuid = uuid::Uuid::parse_str(input.as_ref()).map_err(Error::invalid_uuid_string)?;
Ok(Self::from_external_uuid(uuid))
}

@@ -393,25 +396,23 @@ impl Binary {
pub fn to_uuid_with_representation(&self, rep: UuidRepresentation) -> Result<Uuid> {
// If representation is non-standard, then its subtype must be UuidOld
if rep != UuidRepresentation::Standard && self.subtype != BinarySubtype::UuidOld {
return Err(Error::RepresentationMismatch {
requested_representation: rep,
actual_binary_subtype: self.subtype,
expected_binary_subtype: BinarySubtype::UuidOld,
});
return Err(Error::uuid_representation_mismatch(
rep,
self.subtype,
BinarySubtype::UuidOld,
));
}
// If representation is standard, then its subtype must be Uuid
if rep == UuidRepresentation::Standard && self.subtype != BinarySubtype::Uuid {
return Err(Error::RepresentationMismatch {
requested_representation: rep,
actual_binary_subtype: self.subtype,
expected_binary_subtype: BinarySubtype::Uuid,
});
return Err(Error::uuid_representation_mismatch(
rep,
self.subtype,
BinarySubtype::UuidOld,
));
}
// Must be 16 bytes long
if self.bytes.len() != 16 {
return Err(Error::InvalidLength {
length: self.bytes.len(),
});
return Err(Error::invalid_uuid_length(self.bytes.len()));
}
let mut buf = [0u8; 16];
buf.copy_from_slice(&self.bytes);
@@ -480,66 +481,3 @@ macro_rules! trait_impls {
};
}
trait_impls!(feature = "uuid-1", uuid::Uuid);

/// Errors that can occur during [`Uuid`] construction and generation.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
/// Error returned when an invalid string is provided to [`Uuid::parse_str`].
#[non_exhaustive]
InvalidUuidString { message: String },

/// Error returned when the representation specified does not match the underlying
/// [`crate::Binary`] value in [`crate::Binary::to_uuid_with_representation`].
#[non_exhaustive]
RepresentationMismatch {
/// The subtype that was expected given the requested representation.
expected_binary_subtype: BinarySubtype,

/// The actual subtype of the binary value.
actual_binary_subtype: BinarySubtype,

/// The requested representation.
requested_representation: UuidRepresentation,
},

/// Error returned from [`crate::Binary::to_uuid`] if the underling data is not 16 bytes long.
#[non_exhaustive]
InvalidLength {
/// The actual length of the data.
length: usize,
},
}

/// Alias for `Result<T, bson::uuid::Error>`.
pub type Result<T> = std::result::Result<T, Error>;

impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::InvalidUuidString { message } => {
write!(fmt, "{}", message)
}
Error::RepresentationMismatch {
expected_binary_subtype,
actual_binary_subtype,
requested_representation,
} => {
write!(
fmt,
"expected {:?} when converting to UUID with {:?}, isntead got {:?}",
expected_binary_subtype, requested_representation, actual_binary_subtype
)
}
Error::InvalidLength { length } => {
write!(
fmt,
"expected UUID to contain 16 bytes, instead got {}",
length
)
}
}
}
}

impl std::error::Error for Error {}