Skip to content

Commit

Permalink
Return server errors as Neo4jError, not unexpected
Browse files Browse the repository at this point in the history
Cherry-Picked-From: #187
  • Loading branch information
knutwalker committed Aug 7, 2024
1 parent dea1da6 commit d0d61a7
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 28 deletions.
54 changes: 48 additions & 6 deletions lib/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,8 @@ pub enum Error {
)]
ProtocolMismatch(u32),

#[error("FAILURE response to {msg} [{code}]: {message}")]
Failure {
code: String,
message: String,
msg: &'static str,
},
#[error("Neo4j error `{}`: {}", .0.code, .0.message)]
Neo4j(Neo4jError),

#[error("{0}")]
UnexpectedMessage(String),
Expand Down Expand Up @@ -193,6 +189,27 @@ impl Neo4jErrorKind {
) | 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 {
Expand All @@ -201,6 +218,31 @@ impl From<&str> for Neo4jErrorKind {
}
}

#[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 {
Expand Down
5 changes: 3 additions & 2 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,9 @@ mod version;

pub use crate::auth::ClientCertificate;
pub use crate::config::{Config, ConfigBuilder, Database};
pub use crate::errors::{Error, Result};
pub use crate::errors::{
Error, Neo4jClientErrorKind, Neo4jError, Neo4jErrorKind, Neo4jSecurityErrorKind, Result,
};
pub use crate::graph::{query, Graph};
pub use crate::query::Query;
pub use crate::row::{Node, Path, Point2D, Point3D, Relation, Row, UnboundedRelation};
Expand All @@ -461,5 +463,4 @@ pub use crate::types::{
};
pub use crate::version::Version;

pub(crate) use errors::Neo4jErrorKind;
pub(crate) use messages::Success;
6 changes: 1 addition & 5 deletions lib/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,7 @@ impl BoltResponse {

pub fn into_error(self, msg: &'static str) -> Error {
match self {
BoltResponse::Failure(failure) => Error::Failure {
code: failure.code().to_string(),
message: failure.message().to_string(),
msg,
},
BoltResponse::Failure(failure) => Error::Neo4j(failure.into_error()),
_ => Error::UnexpectedMessage(format!("unexpected response for {}: {:?}", msg, self)),
}
}
Expand Down
20 changes: 17 additions & 3 deletions lib/src/messages/failure.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::types::{serde::DeError, BoltMap};
use crate::{
errors::Neo4jError,
types::{serde::DeError, BoltMap},
BoltType, Neo4jErrorKind,
};
use ::serde::Deserialize;
use neo4rs_macros::BoltStruct;

Expand All @@ -16,13 +20,23 @@ impl Failure {
self.metadata.get::<T>(key)
}

pub fn code(&self) -> &str {
pub(crate) fn code(&self) -> &str {
self.get("code").unwrap()
}

pub fn message(&self) -> &str {
pub(crate) fn message(&self) -> &str {
self.get("message").unwrap()
}

pub(crate) fn into_error(self) -> Neo4jError {
let mut meta = self.metadata.value;
let (code, message) = (meta.remove("code"), meta.remove("message"));
let (code, message) = match (code, message) {
(Some(BoltType::String(s)), Some(BoltType::String(m))) => (s.value, m.value),
_ => (String::new(), String::new()),
};
Neo4jErrorKind::new(&code).new_error(code, message)
}
}

#[cfg(test)]
Expand Down
21 changes: 9 additions & 12 deletions lib/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
pool::ManagedConnection,
stream::{DetachedRowStream, RowStream},
types::{BoltList, BoltMap, BoltString, BoltType},
Error, Neo4jErrorKind, Success,
Error, Success,
};

/// Abstracts a cypher query that is sent to neo4j server.
Expand Down Expand Up @@ -129,23 +129,20 @@ impl From<&str> for Query {
type QueryResult<T> = Result<T, backoff::Error<Error>>;

fn wrap_error<T>(resp: Result<BoltResponse>, req: &'static str) -> QueryResult<T> {
let can_retry = if let Ok(BoltResponse::Failure(failure)) = &resp {
failure
.get::<String>("code")
.map_or(false, |code| Neo4jErrorKind::new(&code).can_retry())
} else {
false
};

let err = match resp {
let error = match resp {
Ok(BoltResponse::Failure(failure)) => Error::Neo4j(failure.into_error()),
Ok(resp) => resp.into_error(req),
Err(e) => e,
};
let can_retry = match &error {
Error::Neo4j(e) => e.can_retry(),
_ => false,
};

if can_retry {
Err(backoff::Error::transient(err))
Err(backoff::Error::transient(error))
} else {
Err(backoff::Error::permanent(err))
Err(backoff::Error::permanent(error))
}
}

Expand Down

0 comments on commit d0d61a7

Please sign in to comment.