Skip to content

Commit

Permalink
Bolt 5.7: GQL compliant server errors
Browse files Browse the repository at this point in the history
  • Loading branch information
robsdedude committed Jan 31, 2025
1 parent 0eb4d91 commit f6b90b1
Show file tree
Hide file tree
Showing 29 changed files with 1,072 additions and 339 deletions.
1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ jobs:
- 5.9-enterprise-cluster-neo4j
- 5.13-enterprise-cluster-neo4j
- 5.23-enterprise-cluster-neo4j
- 5.26-enterprise-cluster-neo4j
include:
- tests: STUB_TESTS
config: ""
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
- Add support for Bolt 5.6 (GQL compatible notifications/result statuses)
- ⚠️ `neo4j::driver::notification::NotificationFilter`'s API has been completely reworked to support this new feature and enable more internal changes in the future without breaking the API again.
- ⚠️ changed `neo4j::summary::Summary::notifications` from `Option<Vec<Notification>>` to `Vec<Notification>` defaulting to `Vec::new()` when the server does not send any notifications.
- Add support for Bolt 5.7 (GQL compatible errors)
- ⚠️ `neo4j::error::ServerError` is now `#[non_exhaustive]`
- ⚠️ removed `impl From<neo4j::error::ServerError> for neo4j::error::Neo4jError`
- ⚠️ `neo4j::error::ServerError::new(...)` has been removed.
User-code should not need to create arbitrary `ServerError`s.
In return, `ServerError` now implements `Clone`.

**🔧 Fixes**
- Rework `neo4j::value::graph::Path`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ A bump in MSRV is considered a minor breaking change.
* [x] 5.5 (never released)
* [x] 5.6 (GQL notifications)
* [ ] 5.7
* [ ] (GQL errors)
* [x] (GQL errors)
* [ ] (new bolt version handshake)
* [ ] 5.8 (home db resolution cache)
* [x] Types
Expand Down
33 changes: 33 additions & 0 deletions doc_test_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,36 @@ pub fn db_exclusive(work: impl FnOnce() + UnwindSafe) {
resume_unwind(err)
}
}

pub fn error_retryable() -> Neo4jResult<()> {
let driver = Driver::new(
ConnectionConfig::new(("127.0.0.256", 7687).into()),
DriverConfig::new().with_auth(Arc::new(get_auth_token())),
);
let res = driver.verify_connectivity();
match res {
Ok(()) => panic!("Expected an error"),
Err(err) if err.is_retryable() => Err(err),
Err(err) => {
panic!("Expected a retryable error, got: {}", err);
}
}
}

pub fn error_non_retryable() -> Neo4jResult<()> {
let user = get_user();
let password = get_password() + "_wrong";
let auth = AuthToken::new_basic_auth(user, password);
let driver = Driver::new(
ConnectionConfig::new(get_address()),
DriverConfig::new().with_auth(Arc::new(auth)),
);
let res = driver.verify_connectivity();
match res {
Ok(()) => panic!("Expected an error"),
Err(err) if !err.is_retryable() => Err(err),
Err(err) => {
panic!("Expected a non-retryable error, got: {}", err);
}
}
}
7 changes: 3 additions & 4 deletions neo4j/src/driver/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,10 +1121,9 @@ mod tests {
// that Mutex to also panic.
fn get_tls_helper_lock() -> MutexGuard<'static, ()> {
let mutex = TLS_HELPER_MTX.get_or_init(Default::default);
match mutex.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
mutex
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}

fn get_test_client_config() -> ClientConfig {
Expand Down
4 changes: 4 additions & 0 deletions neo4j/src/driver/io/bolt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod bolt5x2;
mod bolt5x3;
mod bolt5x4;
mod bolt5x6;
mod bolt5x7;
mod bolt_state;
mod chunk;
mod handshake;
Expand Down Expand Up @@ -61,6 +62,7 @@ use bolt5x2::{Bolt5x2, Bolt5x2StructTranslator};
use bolt5x3::{Bolt5x3, Bolt5x3StructTranslator};
use bolt5x4::{Bolt5x4, Bolt5x4StructTranslator};
use bolt5x6::{Bolt5x6, Bolt5x6StructTranslator};
use bolt5x7::{Bolt5x7, Bolt5x7StructTranslator};
use bolt_state::{BoltState, BoltStateTracker};
use chunk::{Chunker, Dechunker};
pub(crate) use handshake::{open, TcpConnector};
Expand Down Expand Up @@ -177,6 +179,7 @@ impl<RW: Read + Write> Bolt<RW> {
data: BoltData::new(version, stream, socket, local_port, address),
// [bolt-version-bump] search tag when changing bolt version support
protocol: match version {
(5, 7) => Bolt5x7::<Bolt5x7StructTranslator>::default().into(),
(5, 6) => Bolt5x6::<Bolt5x6StructTranslator>::default().into(),
(5, 4) => Bolt5x4::<Bolt5x4StructTranslator>::default().into(),
(5, 3) => Bolt5x3::<Bolt5x3StructTranslator>::default().into(),
Expand Down Expand Up @@ -492,6 +495,7 @@ enum BoltProtocolVersion {
V5x3(Bolt5x3<Bolt5x3StructTranslator>),
V5x4(Bolt5x4<Bolt5x4StructTranslator>),
V5x6(Bolt5x6<Bolt5x6StructTranslator>),
V5x7(Bolt5x7<Bolt5x7StructTranslator>),
}

#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
Expand Down
16 changes: 8 additions & 8 deletions neo4j/src/driver/io/bolt/bolt5x0/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ impl<T: BoltStructTranslator> Bolt5x0<T> {
protocol_version,
}
}

pub(in super::super) fn try_parse_error(meta: ValueReceive) -> Result<ServerError> {
let meta = meta
.try_into_map()
.map_err(|_| Neo4jError::protocol_error("FAILURE meta was not a Dictionary"))?;
Ok(ServerError::from_meta(meta))
}
}

impl<T: BoltStructTranslator> Default for Bolt5x0<T> {
Expand Down Expand Up @@ -992,7 +999,7 @@ impl<T: BoltStructTranslator> BoltProtocol for Bolt5x0<T> {
assert_response_field_count("FAILURE", &fields, 1)?;
let meta = fields.pop().unwrap();
bolt_debug!(bolt_data, "S: FAILURE {}", meta.dbg_print());
let mut error = try_parse_error(meta)?;
let mut error = Self::try_parse_error(meta)?;
bolt_data.bolt_state.failure();
match on_server_error {
None => response.callbacks.on_failure(error),
Expand Down Expand Up @@ -1032,10 +1039,3 @@ impl<T: BoltStructTranslator> BoltProtocol for Bolt5x0<T> {
}
}
}

fn try_parse_error(meta: ValueReceive) -> Result<ServerError> {
let meta = meta
.try_into_map()
.map_err(|_| Neo4jError::protocol_error("FAILURE meta was not a Dictionary"))?;
Ok(ServerError::from_meta(meta))
}
2 changes: 2 additions & 0 deletions neo4j/src/driver/io/bolt/bolt5x6/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ impl<T: BoltStructTranslator> Default for Bolt5x6<T> {
}

impl<T: BoltStructTranslator> BoltProtocol for Bolt5x6<T> {
#[inline]
fn hello<RW: Read + Write>(
&mut self,
data: &mut BoltData<RW>,
Expand Down Expand Up @@ -202,6 +203,7 @@ impl<T: BoltStructTranslator> BoltProtocol for Bolt5x6<T> {
self.bolt5x4.route(data, parameters, callbacks)
}

#[inline]
fn telemetry<RW: Read + Write>(
&mut self,
data: &mut BoltData<RW>,
Expand Down
19 changes: 19 additions & 0 deletions neo4j/src/driver/io/bolt/bolt5x7.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright Rouven Bauer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod protocol;
mod translator;

pub(crate) use protocol::Bolt5x7;
pub(crate) use translator::Bolt5x7StructTranslator;
Loading

0 comments on commit f6b90b1

Please sign in to comment.