mongo_driver 0.16.3

Mongo Rust driver built on top of the Mongo C driver
use core::ffi::c_char;
use std::error;
use std::fmt;
use std::borrow::Cow;
use std::ffi::CStr;

use std::ffi::NulError;

use bson::{de,ser,Document};
use bson::document::ValueAccessError;

use crate::mongoc::bindings;

/// Wrapper for all errors that can occur in the driver.
pub enum MongoError {
    /// Error in the underlying C driver.
    Bsonc(BsoncError),
    /// Error decoding Bson.
    Decoder(de::Error),
    /// Error encoding Bson.
    Encoder(ser::Error),
    /// Error accessing a value on a Bson document.
    ValueAccessError(ValueAccessError),
    /// Invalid params error that can be reported by the underlying C driver.
    InvalidParams(InvalidParamsError),
    // from CString::new(db)
    Nul(NulError)
}

impl fmt::Display for MongoError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MongoError::Bsonc(ref err) => write!(f, "{}", err),
            MongoError::Encoder(ref err) => write!(f, "{}", err),
            MongoError::Decoder(ref err) => write!(f, "{}", err),
            MongoError::ValueAccessError(ref err) => write!(f, "{}", err),
            MongoError::InvalidParams(ref err) => write!(f, "{}", err),
            MongoError::Nul(ref err) => write!(f, "{}", err)
        }
    }
}

impl fmt::Debug for MongoError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MongoError::Bsonc(ref err) => write!(f, "MongoError ({:?})", err),
            MongoError::Decoder(ref err) => write!(f, "MongoError ({:?})", err),
            MongoError::Encoder(ref err) => write!(f, "MongoError ({:?})", err),
            MongoError::ValueAccessError(ref err) => write!(f, "MongoError ({:?})", err),
            MongoError::InvalidParams(ref err) => write!(f, "MongoError ({:?})", err),
            MongoError::Nul(ref err) => write!(f, "MongoError ({:?})", err)
        }
    }
}

impl error::Error for MongoError {
    fn cause(&self) -> Option<&dyn error::Error> {
        match *self {
            MongoError::Bsonc(ref err) => Some(err),
            MongoError::Decoder(ref err) => Some(err),
            MongoError::Encoder(ref err) => Some(err),
            MongoError::ValueAccessError(ref err) => Some(err),
            MongoError::InvalidParams(ref err) => Some(err),
            MongoError::Nul(ref err) => Some(err)
        }
    }
}

impl From<de::Error> for MongoError {
    fn from(error: de::Error) -> MongoError {
        MongoError::Decoder(error)
    }
}

impl From<ser::Error> for MongoError {
    fn from(error: ser::Error) -> MongoError {
        MongoError::Encoder(error)
    }
}

impl From<ValueAccessError> for MongoError {
    fn from(error: ValueAccessError) -> MongoError {
        MongoError::ValueAccessError(error)
    }
}

impl From<NulError> for MongoError {
    fn from(error: NulError) -> MongoError {
        MongoError::Nul(error)
    }
}

/// Error in the underlying C driver.
pub struct BsoncError {
    inner: bindings::bson_error_t,
}

/// MongoDB error domain.
#[derive(Debug,PartialEq)]
pub enum MongoErrorDomain {
    Blank,
    Client,
    Stream,
    Protocol,
    Cursor,
    Query,
    Insert,
    Sasl,
    Bson,
    Matcher,
    Namespace,
    Command,
    Collection,
    Gridfs,
    Scram,
    Unknown
}

/// MongoDB error code.
#[derive(Debug,PartialEq)]
pub enum MongoErrorCode {
    Blank,
    StreamInvalidType,
    StreamInvalidState,
    StreamNameResolution,
    StreamSocket,
    StreamConnect,
    StreamNotEstablished,
    ClientNotReady,
    ClientTooBig,
    ClientTooSmall,
    ClientGetnonce,
    ClientAuthenticate,
    ClientNoAcceptablePeer,
    ClientInExhaust,
    ProtocolInvalidReply,
    ProtocolBadWireVersion,
    CursorInvalidCursor,
    QueryFailure,
    BsonInvalid,
    MatcherInvalid,
    NamespaceInvalid,
    NamespaceInvalidFilterType,
    CommandInvalidArg,
    CollectionInsertFailed,
    CollectionUpdateFailed,
    CollectionDeleteFailed,
    CollectionDoesNotExist,
    GridfsInvalidFilename,
    ScramNotDone,
    ScramProtocolError,
    QueryCommandNotFound,
    QueryNotTailable,
    WriteConcernError,
    DuplicateKey,
    Unknown(u32)
}

impl BsoncError {
    pub fn empty() -> BsoncError {
        BsoncError {
            inner: bindings::bson_error_t {
                domain:  0,
                code:    0,
                message: [0; 504]
            }
        }
    }

    /// Wether the error has content.
    pub fn is_empty(&self) -> bool {
        self.inner.domain == 0 && self.inner.code == 0
    }

    /// The error's domain.
    pub fn domain(&self) -> MongoErrorDomain {
        match self.inner.domain {
            0                                 => MongoErrorDomain::Blank,
            bindings::MONGOC_ERROR_CLIENT     => MongoErrorDomain::Client,
            bindings::MONGOC_ERROR_STREAM     => MongoErrorDomain::Stream,
            bindings::MONGOC_ERROR_PROTOCOL   => MongoErrorDomain::Protocol,
            bindings::MONGOC_ERROR_CURSOR     => MongoErrorDomain::Cursor,
            bindings::MONGOC_ERROR_QUERY      => MongoErrorDomain::Query,
            bindings::MONGOC_ERROR_INSERT     => MongoErrorDomain::Insert,
            bindings::MONGOC_ERROR_SASL       => MongoErrorDomain::Sasl,
            bindings::MONGOC_ERROR_BSON       => MongoErrorDomain::Bson,
            bindings::MONGOC_ERROR_MATCHER    => MongoErrorDomain::Matcher,
            bindings::MONGOC_ERROR_NAMESPACE  => MongoErrorDomain::Namespace,
            bindings::MONGOC_ERROR_COMMAND    => MongoErrorDomain::Command,
            bindings::MONGOC_ERROR_COLLECTION => MongoErrorDomain::Collection,
            bindings::MONGOC_ERROR_GRIDFS     => MongoErrorDomain::Gridfs,
            bindings::MONGOC_ERROR_SCRAM      => MongoErrorDomain::Scram,
            _                                 => MongoErrorDomain::Unknown
        }
    }

    /// The error's code.
    pub fn code(&self) -> MongoErrorCode {
        match self.inner.code {
            0                                                    => MongoErrorCode::Blank,
            bindings::MONGOC_ERROR_STREAM_INVALID_TYPE           => MongoErrorCode::StreamInvalidType,
            bindings::MONGOC_ERROR_STREAM_INVALID_STATE          => MongoErrorCode::StreamInvalidState,
            bindings::MONGOC_ERROR_STREAM_NAME_RESOLUTION        => MongoErrorCode::StreamNameResolution,
            bindings::MONGOC_ERROR_STREAM_SOCKET                 => MongoErrorCode::StreamSocket,
            bindings::MONGOC_ERROR_STREAM_CONNECT                => MongoErrorCode::StreamConnect,
            bindings::MONGOC_ERROR_STREAM_NOT_ESTABLISHED        => MongoErrorCode::StreamNotEstablished,
            bindings::MONGOC_ERROR_CLIENT_NOT_READY              => MongoErrorCode::ClientNotReady,
            bindings::MONGOC_ERROR_CLIENT_TOO_BIG                => MongoErrorCode::ClientTooBig,
            bindings::MONGOC_ERROR_CLIENT_TOO_SMALL              => MongoErrorCode::ClientTooSmall,
            bindings::MONGOC_ERROR_CLIENT_GETNONCE               => MongoErrorCode::ClientGetnonce,
            bindings::MONGOC_ERROR_CLIENT_AUTHENTICATE           => MongoErrorCode::ClientAuthenticate,
            bindings::MONGOC_ERROR_CLIENT_NO_ACCEPTABLE_PEER     => MongoErrorCode::ClientNoAcceptablePeer,
            bindings::MONGOC_ERROR_CLIENT_IN_EXHAUST             => MongoErrorCode::ClientInExhaust,
            bindings::MONGOC_ERROR_PROTOCOL_INVALID_REPLY        => MongoErrorCode::ProtocolInvalidReply,
            bindings::MONGOC_ERROR_PROTOCOL_BAD_WIRE_VERSION     => MongoErrorCode::ProtocolBadWireVersion,
            bindings::MONGOC_ERROR_CURSOR_INVALID_CURSOR         => MongoErrorCode::CursorInvalidCursor,
            bindings::MONGOC_ERROR_QUERY_FAILURE                 => MongoErrorCode::QueryFailure,
            bindings::MONGOC_ERROR_BSON_INVALID                  => MongoErrorCode::BsonInvalid,
            bindings::MONGOC_ERROR_MATCHER_INVALID               => MongoErrorCode::MatcherInvalid,
            bindings::MONGOC_ERROR_NAMESPACE_INVALID             => MongoErrorCode::NamespaceInvalid,
            bindings::MONGOC_ERROR_NAMESPACE_INVALID_FILTER_TYPE => MongoErrorCode::NamespaceInvalidFilterType,
            bindings::MONGOC_ERROR_COMMAND_INVALID_ARG           => MongoErrorCode::CommandInvalidArg,
            bindings::MONGOC_ERROR_COLLECTION_INSERT_FAILED      => MongoErrorCode::CollectionInsertFailed,
            bindings::MONGOC_ERROR_COLLECTION_UPDATE_FAILED      => MongoErrorCode::CollectionUpdateFailed,
            bindings::MONGOC_ERROR_COLLECTION_DELETE_FAILED      => MongoErrorCode::CollectionDeleteFailed,
            bindings::MONGOC_ERROR_COLLECTION_DOES_NOT_EXIST     => MongoErrorCode::CollectionDoesNotExist,
            bindings::MONGOC_ERROR_GRIDFS_INVALID_FILENAME       => MongoErrorCode::GridfsInvalidFilename,
            bindings::MONGOC_ERROR_SCRAM_NOT_DONE                => MongoErrorCode::ScramNotDone,
            bindings::MONGOC_ERROR_SCRAM_PROTOCOL_ERROR          => MongoErrorCode::ScramProtocolError,
            bindings::MONGOC_ERROR_QUERY_COMMAND_NOT_FOUND       => MongoErrorCode::QueryCommandNotFound,
            bindings::MONGOC_ERROR_QUERY_NOT_TAILABLE            => MongoErrorCode::QueryNotTailable,
            bindings::MONGOC_ERROR_WRITE_CONCERN_ERROR           => MongoErrorCode::WriteConcernError,
            bindings::MONGOC_ERROR_DUPLICATE_KEY                 => MongoErrorCode::DuplicateKey,
            code                                                 => MongoErrorCode::Unknown(code)
        }
    }

    /// The error's message.
    pub fn get_message(&self) -> Cow<str> {
        let cstr = unsafe { CStr::from_ptr(&self.inner.message as *const c_char) };
        String::from_utf8_lossy(cstr.to_bytes())
    }

    #[doc(hidden)]
    pub fn mut_inner(&mut self) -> &mut bindings::bson_error_t {
        &mut self.inner
    }
}

impl fmt::Debug for BsoncError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "BsoncError: {:?}/{:?} - {}",
            &self.domain(),
            &self.code(),
            &self.get_message()
        )
    }
}

impl fmt::Display for BsoncError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.get_message())
    }
}

impl error::Error for BsoncError {
    fn description(&self) -> &str {
        "Error reported by the underlying Mongo C driver"
    }
}

impl From<BsoncError> for MongoError {
    fn from(error: BsoncError) -> MongoError {
        MongoError::Bsonc(error)
    }
}

/// Invalid params error that can be reported by the underlying C driver.
pub struct InvalidParamsError;

impl fmt::Debug for InvalidParamsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "InvalidParamsError: Invalid params supplied")
    }
}

impl fmt::Display for InvalidParamsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Invalid params supplied")
    }
}

impl error::Error for InvalidParamsError {
    fn description(&self) -> &str {
        "Invalid params reported by the underlying Mongo C driver, no more information is available"
    }
}

impl From<InvalidParamsError> for MongoError {
    fn from(error: InvalidParamsError) -> MongoError {
        MongoError::InvalidParams(error)
    }
}

/// Error returned by a bulk operation that includes a report in the reply document.
#[derive(Debug)]
pub struct BulkOperationError {
    /// Returned error
    pub error: MongoError,
    /// Error report
    pub reply: Document
}

impl fmt::Display for BulkOperationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Bulk operation error {}", self.error)
    }
}

impl error::Error for BulkOperationError {
    fn description(&self) -> &str {
        "Error returned by a bulk operation that includes a report in the reply document"
    }
}

#[cfg(test)]
mod tests {
    use super::{BsoncError,MongoErrorDomain,MongoErrorCode};

    #[test]
    fn test_bson_error_empty() {
        let mut error = BsoncError::empty();
        assert!(error.is_empty());
        error.mut_inner().code = 1;
        assert!(!error.is_empty());
        error.mut_inner().domain = 1;
        error.mut_inner().code = 0;
        assert!(!error.is_empty());
    }

    #[test]
    fn test_bson_error_domain() {
        let mut error = BsoncError::empty();
        assert_eq!(MongoErrorDomain::Blank, error.domain());
        error.mut_inner().domain = 1;
        assert_eq!(MongoErrorDomain::Client, error.domain());
    }

    #[test]
    fn test_bson_error_code() {
        let mut error = BsoncError::empty();
        assert_eq!(MongoErrorCode::Blank, error.code());
        error.mut_inner().code = 1;
        assert_eq!(MongoErrorCode::StreamInvalidType, error.code());
    }
}