use std::fmt;
use std::error;
use std::result;
use std::ops::Deref;
use std::borrow::Cow;
use bson::ValueAccessError;
use backtrace::Backtrace;
use typemap::{ DebugMap, Key };
#[allow(clippy::stutter)]
pub trait ErrorExt: error::Error {
fn reason(&self) -> Option<&(dyn ErrorExt + 'static)> {
None
}
fn backtrace(&self) -> Option<&Backtrace> {
self.reason().and_then(ErrorExt::backtrace)
}
fn kind(&self) -> ErrorKind;
fn as_std_error(&self) -> &(dyn error::Error + 'static);
}
pub trait ResultExt<T>: Sized {
fn chain<M: ErrMsg>(self, message: M) -> Result<T>;
}
pub trait ErrMsg: Sized {
fn into_message(self) -> Cow<'static, str>;
}
pub type Result<T> = result::Result<T, Error>;
impl<T, E> ResultExt<T> for result::Result<T, E> where E: ErrorExt + 'static {
fn chain<M: ErrMsg>(self, message: M) -> Result<T> {
self.map_err(|cause| Error::with_cause(message.into_message(), cause))
}
}
impl ErrMsg for &'static str {
fn into_message(self) -> Cow<'static, str> {
Cow::Borrowed(self)
}
}
impl<F> ErrMsg for F where F: FnOnce() -> String {
fn into_message(self) -> Cow<'static, str> {
Cow::Owned(self())
}
}
#[allow(clippy::stutter)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ErrorKind {
JsonTranscoding,
BsonEncoding,
BsonDecoding,
BsonNumberRepr,
MissingDocumentField,
IllTypedDocumentField,
MissingId,
ObjectIdGeneration,
MongoDbError,
MongoDbWriteException,
MongoDbBulkWriteException,
IntConversionUnderflow,
IntConversionOverflow,
BsonSchema,
}
impl ErrorKind {
pub fn as_str(self) -> &'static str {
use self::ErrorKind::*;
match self {
JsonTranscoding => "JSON transcoding error",
BsonEncoding => "BSON encoding error",
BsonDecoding => "BSON decoding error",
BsonNumberRepr => "number not i64 nor f64",
MissingDocumentField => "document field not found",
IllTypedDocumentField => "document field of unexpected type",
MissingId => "missing unique identifier",
ObjectIdGeneration => "an ObjectID could not be generated",
MongoDbError => "MongoDB error",
MongoDbWriteException => "MongoDB write exception",
MongoDbBulkWriteException => "MongoDB bulk write exception",
IntConversionUnderflow => "integer conversion underflowed",
IntConversionOverflow => "integer conversion overflowed",
BsonSchema => "error in BSON schema",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
message: Cow<'static, str>,
cause: Option<Box<dyn ErrorExt>>,
backtrace: Option<Backtrace>,
context: DebugMap,
}
impl Error {
pub fn new<S>(kind: ErrorKind, message: S) -> Self
where S: Into<Cow<'static, str>>
{
Error {
kind,
message: message.into(),
cause: None,
backtrace: Some(Backtrace::new()),
context: DebugMap::custom(),
}
}
pub fn with_cause<S, E>(message: S, cause: E) -> Self
where S: Into<Cow<'static, str>>,
E: ErrorExt + 'static
{
let kind = cause.kind();
let message = message.into();
let backtrace = if cause.backtrace().is_none() {
Some(Backtrace::new())
} else {
None
};
let cause: Option<Box<dyn ErrorExt>> = Some(Box::new(cause));
let context = DebugMap::custom();
Error { kind, message, cause, backtrace, context }
}
pub fn context<K: Key>(&self) -> Option<&K::Value>
where K::Value: fmt::Debug
{
self.context.get::<K>()
}
pub fn set_context<K: Key>(&mut self, value: K::Value) -> Option<K::Value>
where K::Value: fmt::Debug
{
self.context.insert::<K>(value)
}
pub fn with_context<K: Key>(mut self, value: K::Value) -> Self
where K::Value: fmt::Debug
{
self.set_context::<K>(value);
self
}
}
impl ErrorExt for Error {
fn reason(&self) -> Option<&(dyn ErrorExt + 'static)> {
self.cause.as_ref().map(Deref::deref)
}
#[allow(clippy::or_fun_call)]
fn backtrace(&self) -> Option<&Backtrace> {
self.reason().and_then(ErrorExt::backtrace).or(self.backtrace.as_ref())
}
fn kind(&self) -> ErrorKind {
self.kind
}
fn as_std_error(&self) -> &(dyn error::Error + 'static) {
self
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.kind, self.message)?;
if let Some(cause) = self.cause.as_ref() {
write!(f, ", caused by: {}", cause)?
}
if let Some(backtrace) = self.backtrace.as_ref() {
write!(f, "; {:#?}", backtrace)?
}
Ok(())
}
}
impl error::Error for Error {
fn description(&self) -> &str {
&self.message
}
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.reason().map(ErrorExt::as_std_error)
}
}
impl From<ValueAccessError> for Error {
fn from(error: ValueAccessError) -> Self {
let message = match error {
ValueAccessError::NotPresent => "missing value for key in Document",
ValueAccessError::UnexpectedType => "ill-typed value for key in Document",
};
Self::with_cause(message, error)
}
}
impl ErrorExt for ValueAccessError {
fn kind(&self) -> ErrorKind {
match *self {
ValueAccessError::NotPresent => ErrorKind::MissingDocumentField,
ValueAccessError::UnexpectedType => ErrorKind::IllTypedDocumentField,
}
}
fn as_std_error(&self) -> &(dyn error::Error + 'static) {
self
}
}
macro_rules! impl_error_type {
($ty:path, $kind:ident, $message:expr) => {
impl From<$ty> for Error {
fn from(error: $ty) -> Self {
Self::with_cause($message, error)
}
}
impl ErrorExt for $ty {
fn kind(&self) -> ErrorKind {
ErrorKind::$kind
}
fn as_std_error(&self) -> &(dyn error::Error + 'static) {
self
}
}
}
}
impl_error_type! { serde_json::Error, JsonTranscoding, "JSON transcoding error" }
impl_error_type! { bson::EncoderError, BsonEncoding, "BSON encoding error" }
impl_error_type! { bson::DecoderError, BsonDecoding, "BSON decoding error" }
impl_error_type! { bson::oid::Error, ObjectIdGeneration, "ObjectId generation error" }
impl_error_type! { mongodb::Error, MongoDbError, "MongoDB error" }
impl_error_type! {
mongodb::coll::error::WriteException,
MongoDbWriteException,
"MongoDB write exception"
}
impl_error_type! {
mongodb::coll::error::BulkWriteException,
MongoDbBulkWriteException,
"MongoDB bulk write exception"
}