bolt_client/lib.rs
1#![warn(rust_2018_idioms)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! This crate contains a runtime-agnostic asynchronous client for graph database servers that
5//! support the [Bolt](https://neo4j.com/docs/bolt/current) protocol.
6//!
7//! The central feature of this library is the [`Client`] struct, which allows sending Bolt
8//! messages to a compatible server. Clients can operate over any type that implements
9//! [`AsyncRead`](futures_util::AsyncRead) and [`AsyncWrite`](futures_util::AsyncWrite).
10//!
11//! If you want to connect to a Bolt-compatible server from your application, you probably want to
12//! use a connection pool - see [bb8-bolt](https://crates.io/crates/bb8-bolt),
13//! [deadpool-bolt](https://crates.io/crates/deadpool-bolt), or
14//! [mobc-bolt](https://crates.io/crates/mobc-bolt).
15//!
16//! If you'd rather manage your own connections, an asynchronous TCP/TLS [`Stream`] wrapper is also
17//! available, if you're using the [tokio](https://tokio.rs/) runtime.
18//!
19//! # Features
20//! - `tokio-stream` - enables the [`Stream`] type
21//!
22//! # Example
23//! The below example demonstrates how to communicate with a Neo4j server using Bolt protocol
24//! version 4.
25//! ```
26//! use std::{collections::HashMap, env};
27//!
28//! use tokio::io::BufStream;
29//! use tokio_util::compat::*;
30//!
31//! use bolt_client::*;
32//! use bolt_proto::{message::*, value::*, version::*, Message, Value};
33//!
34//! #[tokio::main]
35//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! // Let's say you have a type that implements AsyncRead + AsyncWrite. Here's one
37//! // provided by the `tokio-stream` feature of this library. In this example, all
38//! // connection/authentication details are stored in environment variables.
39//! let stream = Stream::connect(env::var("BOLT_TEST_ADDR")?,
40//! env::var("BOLT_TEST_DOMAIN").ok()).await?;
41//! // Be sure to buffer your IO :)
42//! let stream = BufStream::new(stream).compat();
43//!
44//! // Create a new connection to the server and perform a handshake to establish a
45//! // protocol version. This example demonstrates usage of the v4.3 or v4.2 protocol.
46//! let mut result = Client::new(stream, &[V4_3, V4_2, 0, 0]).await;
47//! # skip_if_handshake_failed!(result, Ok(()));
48//! let mut client = result.unwrap();
49//!
50//! // Send a HELLO message with authentication details to the server to initialize
51//! // the session.
52//! let response: Message = client.hello(
53//! Metadata::from_iter(vec![
54//! ("user_agent", "my-client-name/1.0"),
55//! ("scheme", "basic"),
56//! ("principal", &env::var("BOLT_TEST_USERNAME")?),
57//! ("credentials", &env::var("BOLT_TEST_PASSWORD")?),
58//! ])).await?;
59//! # Success::try_from(response.clone()).unwrap();
60//! assert!(Success::try_from(response).is_ok());
61//!
62//! // Submit a query for execution on the server
63//! let response = client.run("RETURN 1 as num;", None, None).await?;
64//!
65//! // Successful responses will include a SUCCESS message with related metadata
66//! // Consuming these messages is optional and will be skipped for the rest of the example
67//! # Success::try_from(response.clone()).unwrap();
68//! assert!(Success::try_from(response).is_ok());
69//!
70//! // Use PULL to retrieve results of the query, organized into RECORD messages
71//! // We get a (Vec<Record>, Message) returned from a PULL
72//! let pull_meta = Metadata::from_iter(vec![("n", 1)]);
73//! let (records, response) = client.pull(Some(pull_meta.clone())).await?;
74//! # Success::try_from(response).unwrap();
75//!
76//! assert_eq!(records[0].fields(), &[Value::from(1)]);
77//! #
78//! # client.run("MATCH (n) DETACH DELETE n;", None, None).await?;
79//! # client.pull(Some(pull_meta.clone())).await?;
80//!
81//! // Submit a more complex query with parameters
82//! let params = Params::from_iter(vec![("name", "Rust")]);
83//! client.run(
84//! "CREATE (:Client)-[:WRITTEN_IN]->(:Language {name: $name});",
85//! Some(params), None).await?;
86//! client.pull(Some(pull_meta.clone())).await?;
87//!
88//! // Grab a node from the database and convert it to a native type
89//! client.run("MATCH (rust:Language) RETURN rust;", None, None).await?;
90//! let (records, response) = client.pull(Some(pull_meta.clone())).await?;
91//! # Success::try_from(response).unwrap();
92//! let node = Node::try_from(records[0].fields()[0].clone())?;
93//!
94//! // Access properties from returned values
95//! assert_eq!(node.labels(), &[String::from("Language")]);
96//! assert_eq!(node.properties(),
97//! &HashMap::from_iter(vec![(String::from("name"), Value::from("Rust"))]));
98//!
99//! // End the connection with the server
100//! client.goodbye().await?;
101//!
102//! Ok(())
103//! }
104//! ```
105//!
106//! For version 3 of the protocol, the metadata we pass to [`Client::pull`] is not required, since
107//! all records are consumed.
108//! ```
109//! # use std::collections::HashMap;
110//! # use std::env;
111//! #
112//! # use tokio::io::BufStream;
113//! # use tokio_util::compat::*;
114//! #
115//! # use bolt_client::*;
116//! # use bolt_proto::{message::*, value::*, version::*, Message, Value};
117//! #
118//! # #[tokio::main]
119//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
120//! # let stream = Stream::connect(env::var("BOLT_TEST_ADDR")?,
121//! # env::var("BOLT_TEST_DOMAIN").ok()).await?;
122//! # let stream = BufStream::new(stream).compat();
123//! // Now we only want Bolt v3
124//! let mut result = Client::new(stream, &[V3_0, 0, 0, 0]).await;
125//! # skip_if_handshake_failed!(result, Ok(()));
126//! # let mut client = result.unwrap();
127//! #
128//! # let response: Message = client.hello(
129//! # Metadata::from_iter(vec![
130//! # ("user_agent", "my-client-name/1.0"),
131//! # ("scheme", "basic"),
132//! # ("principal", &env::var("BOLT_TEST_USERNAME")?),
133//! # ("credentials", &env::var("BOLT_TEST_PASSWORD")?),
134//! # ])).await?;
135//! # Success::try_from(response).unwrap();
136//! #
137//! # let response = client.run("RETURN 1 as num;", None, None).await?;
138//! # Success::try_from(response).unwrap();
139//!
140//! // Use `None` for the PULL metadata
141//! let (records, response) = client.pull(None).await?;
142//! # Success::try_from(response).unwrap();
143//! #
144//! # assert_eq!(records[0].fields(), &[Value::from(1 as i8)]);
145//! # client.run("MATCH (n {test: 'doctest-v3'}) DETACH DELETE n;", None, None).await?;
146//! # client.pull(None).await?;
147//! #
148//! # let params = Params::from_iter(vec![("name", "C")]);
149//! # client.run(
150//! # "CREATE (:Seabolt {test: 'doctest-v3'})-[:WRITTEN_IN]->(:C {name: $name, test: 'doctest-v3'});",
151//! # Some(params), None).await?;
152//! # client.pull(None).await?;
153//! #
154//! # client.run("MATCH (c:C {test: 'doctest-v3'}) RETURN c;", None, None).await?;
155//! # let (records, response) = client.pull(None).await?;
156//! # Success::try_from(response).unwrap();
157//! # let node = Node::try_from(records[0].fields()[0].clone())?;
158//! # assert_eq!(node.labels(), &[String::from("C")]);
159//! # assert_eq!(node.properties(),
160//! # &HashMap::from_iter(vec![(String::from("name"), Value::from("C")),
161//! # (String::from("test"), Value::from("doctest-v3"))]));
162//! # client.goodbye().await?;
163//! # Ok(())
164//! # }
165//! ```
166//!
167//! For versions 1 and 2 of the protocol, there are a couple more differences:
168//! ```
169//! # use std::collections::HashMap;
170//! # use std::env;
171//! #
172//! # use tokio::io::BufStream;
173//! # use tokio_util::compat::*;
174//! #
175//! # use bolt_client::*;
176//! # use bolt_proto::{message::*, value::*, version::*, Message, Value};
177//! #
178//! # #[tokio::main]
179//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
180//! # let stream = Stream::connect(env::var("BOLT_TEST_ADDR")?,
181//! # env::var("BOLT_TEST_DOMAIN").ok()).await?;
182//! # let stream = BufStream::new(stream).compat();
183//! // For the handshake, we can support versions 1 and 2 only, preferring version 2.
184//! let mut result = Client::new(stream, &[V2_0, V1_0, 0, 0]).await;
185//! # skip_if_handshake_failed!(result, Ok(()));
186//! # let mut client = result.unwrap();
187//! #
188//! # let response: Message = client.hello(
189//! # Metadata::from_iter(vec![
190//! # ("user_agent", "my-client-name/1.0"),
191//! # ("scheme", "basic"),
192//! # ("principal", &env::var("BOLT_TEST_USERNAME")?),
193//! # ("credentials", &env::var("BOLT_TEST_PASSWORD")?),
194//! # ])).await?;
195//! # Success::try_from(response).unwrap();
196//! #
197//! # let response = client.run("RETURN 1 as num;", None, None).await?;
198//! # Success::try_from(response).unwrap();
199//!
200//! // No need to pass metadata here either
201//! let (records, response) = client.pull(None).await?;
202//! # Success::try_from(response).unwrap();
203//! # assert_eq!(records[0].fields(), &[Value::from(1 as i8)]);
204//! #
205//! # client.run("MATCH (n {test: 'doctest-v2-v1'}) DETACH DELETE n;", None, None).await?;
206//! # client.pull(None).await?;
207//! #
208//! # client.run("CREATE (:Client {test: 'doctest-v2-v1'})-[:WRITTEN_IN]->(:Language {name: $name, test: 'doctest-v2-v1'});",
209//! # Some(Params::from_iter(vec![("name".to_string(), Value::from("Rust"))])), None).await?;
210//! # client.pull(None).await?;
211//! # client.run("MATCH (rust:Language {test: 'doctest-v2-v1'}) RETURN rust;", None, None).await?;
212//! # let (records, response) = client.pull(None).await?;
213//! # Success::try_from(response).unwrap();
214//! #
215//! # let node = Node::try_from(records[0].fields()[0].clone())?;
216//! # assert_eq!(node.labels(), &["Language".to_string()]);
217//! # assert_eq!(node.properties(),
218//! # &HashMap::from_iter(vec![(String::from("name"), Value::from("Rust")),
219//! # (String::from("test"), Value::from("doctest-v2-v1"))]));
220//!
221//! // There is no call to `goodbye`
222//! # Ok(())
223//! # }
224//! ```
225//! See the documentation of the [`Client`] struct for information on transaction management, error
226//! handling, and more.
227#[doc(inline)]
228pub use self::client::Client;
229
230mod client;
231mod define_value_map;
232pub mod error;
233
234pub use bolt_proto;
235
236#[cfg(feature = "tokio-stream")]
237mod stream;
238
239#[cfg(feature = "tokio-stream")]
240pub use stream::Stream;
241
242// TODO: Convert Client methods to return a builder-type object so we don't need these anymore
243define_value_map!(Metadata);
244define_value_map!(Params);
245define_value_map!(RoutingContext);
246
247#[doc(hidden)]
248#[macro_export]
249macro_rules! skip_if_handshake_failed {
250 ($var:expr) => {
251 if let Err(err) = $var {
252 println!("Skipping test: {}", err);
253 return;
254 }
255 };
256 ($var:expr, $ret:expr) => {
257 if let Err(err) = $var {
258 println!("Skipping test: {}", err);
259 return $ret;
260 }
261 };
262}