neo4rs 0.8.0

Rust driver for Neo4j
Documentation
use crate::DeError;

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

#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
    #[error("an IO error occurred: {detail}")]
    IOError {
        #[from]
        detail: std::io::Error,
    },

    #[error("Invalid URI: {0}")]
    UrlParseError(#[from] url::ParseError),

    #[error("Unsupported URI scheme: {0}")]
    UnsupportedScheme(String),

    #[error("Invalid DNS name: {0}")]
    InvalidDnsName(String),

    #[error("connection error")]
    ConnectionError,

    #[error("attempted to serialize excessively long string")]
    StringTooLong,

    #[error("attempted to serialize excessively large map")]
    MapTooBig,

    #[error("attempted to serialize excessively large byte array")]
    BytesTooBig,

    #[error("attempted to serialize excessively long list")]
    ListTooLong,

    #[error("invalid config")]
    InvalidConfig,

    #[error("Neo4j error `{}`: {}", .0.code, .0.message)]
    Neo4j(Neo4jError),

    #[error("{0}")]
    UnsupportedVersion(String),

    #[error("{0}")]
    UnexpectedMessage(String),

    #[error("{0}")]
    UnknownType(String),

    #[error("{0}")]
    UnknownMessage(String),

    #[error("conversion error")]
    ConversionError,

    #[error("{0}")]
    AuthenticationError(String),

    #[error("{0}")]
    InvalidTypeMarker(String),

    #[error("{0}")]
    DeserializationError(DeError),
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Neo4jErrorKind {
    Client(Neo4jClientErrorKind),
    Transient,
    Database,
    Unknown,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Neo4jClientErrorKind {
    Security(Neo4jSecurityErrorKind),
    SessionExpired,
    FatalDiscovery,
    TransactionTerminated,
    ProtocolViolation,
    Other,
    Unknown,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Neo4jSecurityErrorKind {
    Authentication,
    AuthorizationExpired,
    TokenExpired,
    Other,
    Unknown,
}

impl Neo4jErrorKind {
    pub(crate) fn new(code: &str) -> Self {
        let code = Self::adjust_code(code).unwrap_or(code);
        Self::classify(code)
    }

    fn adjust_code(code: &str) -> Option<&str> {
        match code {
            "Neo.TransientError.Transaction.LockClientStopped" => {
                Some("Neo.ClientError.Transaction.LockClientStopped")
            }
            "Neo.TransientError.Transaction.Terminated" => {
                Some("Neo.ClientError.Transaction.Terminated")
            }
            _ => None,
        }
    }

    fn classify(code: &str) -> Self {
        let mut parts = code.split('.').skip(1);
        let [class, subclass, kind] = [parts.next(), parts.next(), parts.next()];

        match class {
            Some("ClientError") => match (subclass, kind) {
                (Some("Security"), Some("Unauthorized")) => Self::Client(
                    Neo4jClientErrorKind::Security(Neo4jSecurityErrorKind::Authentication),
                ),
                (Some("Security"), Some("AuthorizationExpired")) => Self::Client(
                    Neo4jClientErrorKind::Security(Neo4jSecurityErrorKind::AuthorizationExpired),
                ),
                (Some("Security"), Some("TokenExpired")) => Self::Client(
                    Neo4jClientErrorKind::Security(Neo4jSecurityErrorKind::TokenExpired),
                ),
                (Some("Database"), Some("DatabaseNotFound")) => {
                    Self::Client(Neo4jClientErrorKind::FatalDiscovery)
                }
                (Some("Transaction"), Some("Terminated")) => {
                    Self::Client(Neo4jClientErrorKind::TransactionTerminated)
                }
                (Some("Security"), Some(_)) => Self::Client(Neo4jClientErrorKind::Security(
                    Neo4jSecurityErrorKind::Other,
                )),
                (Some("Security"), _) => Self::Client(Neo4jClientErrorKind::Security(
                    Neo4jSecurityErrorKind::Unknown,
                )),
                (Some("Request"), _) => Self::Client(Neo4jClientErrorKind::ProtocolViolation),
                (Some("Cluster"), Some("NotALeader")) => {
                    Self::Client(Neo4jClientErrorKind::SessionExpired)
                }
                (Some("General"), Some("ForbiddenOnReadOnlyDatabase")) => {
                    Self::Client(Neo4jClientErrorKind::SessionExpired)
                }
                (Some(_), _) => Self::Client(Neo4jClientErrorKind::Other),
                _ => Self::Client(Neo4jClientErrorKind::Unknown),
            },
            Some("TransientError") => Self::Transient,
            Some(_) => Self::Database,
            None => Self::Unknown,
        }
    }

    pub(crate) fn can_retry(&self) -> bool {
        matches!(
            self,
            Self::Client(
                Neo4jClientErrorKind::Security(Neo4jSecurityErrorKind::AuthorizationExpired)
                    | Neo4jClientErrorKind::SessionExpired
            ) | Self::Transient
        )
    }

    #[allow(unused)]
    pub(crate) fn is_fatal(&self) -> bool {
        match self {
            Self::Client(Neo4jClientErrorKind::ProtocolViolation) => true,
            Self::Client(_) | Self::Transient => false,
            _ => true,
        }
    }

    pub(crate) fn new_error(self, code: String, message: String) -> Neo4jError {
        let code = Self::adjust_code(&code)
            .map(|s| s.to_owned())
            .unwrap_or(code);

        Neo4jError {
            kind: self,
            code,
            message,
        }
    }
}

impl From<&str> for Neo4jErrorKind {
    fn from(code: &str) -> Self {
        Self::new(code)
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Neo4jError {
    kind: Neo4jErrorKind,
    code: String,
    message: String,
}

impl Neo4jError {
    pub fn kind(&self) -> Neo4jErrorKind {
        self.kind
    }

    pub fn code(&self) -> &str {
        &self.code
    }

    pub fn message(&self) -> &str {
        &self.message
    }

    pub(crate) fn can_retry(&self) -> bool {
        self.kind.can_retry()
    }
}

impl std::convert::From<deadpool::managed::PoolError<Error>> for Error {
    fn from(e: deadpool::managed::PoolError<Error>) -> Self {
        match e {
            deadpool::managed::PoolError::Backend(e) => e,
            _ => Error::ConnectionError,
        }
    }
}

impl std::convert::From<deadpool::managed::BuildError<Error>> for Error {
    fn from(value: deadpool::managed::BuildError<Error>) -> Self {
        match value {
            deadpool::managed::BuildError::Backend(e) => e,
            _ => Error::ConnectionError,
        }
    }
}

pub fn unexpected<T: std::fmt::Debug>(response: T, request: &str) -> Error {
    Error::UnexpectedMessage(format!(
        "unexpected response for {}: {:?}",
        request, response
    ))
}