From a385e6733462f6f3006ba139cedb82cc447fae74 Mon Sep 17 00:00:00 2001 From: Daniel Wiesenberg Date: Thu, 7 Oct 2021 13:22:24 +0200 Subject: [PATCH 1/8] Add deadpool-arangodb --- .github/workflows/ci.yml | 15 ++++ Cargo.toml | 1 + README.md | 5 +- arangodb/Cargo.toml | 32 ++++++++ arangodb/README.md | 48 +++++++++++ arangodb/src/config.rs | 135 ++++++++++++++++++++++++++++++ arangodb/src/lib.rs | 163 +++++++++++++++++++++++++++++++++++++ arangodb/tests/arangodb.rs | 59 ++++++++++++++ src/managed/builder.rs | 9 +- 9 files changed, 464 insertions(+), 3 deletions(-) create mode 100644 arangodb/Cargo.toml create mode 100644 arangodb/README.md create mode 100644 arangodb/src/config.rs create mode 100644 arangodb/src/lib.rs create mode 100644 arangodb/tests/arangodb.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 558e43f6..b2e6fe90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,6 +89,7 @@ jobs: fail-fast: false matrix: crate: + - arangodb - diesel - lapin - postgres @@ -136,6 +137,7 @@ jobs: # `nom = 6.0.0`. Try re-enable it on next `lapin` major version # upgrade. #- { crate: deadpool-lapin, msrv: '1.54.0' } + - { crate: deadpool-arangodb, msrv: '1.54.0' } - { crate: deadpool-postgres, msrv: '1.54.0' } - { crate: deadpool-redis, msrv: '1.54.0' } - { crate: deadpool-sqlite, msrv: '1.54.0' } @@ -165,6 +167,7 @@ jobs: matrix: crate: - deadpool + - deadpool-arangodb - deadpool-diesel - deadpool-lapin - deadpool-postgres @@ -172,6 +175,13 @@ jobs: - deadpool-sqlite runs-on: ubuntu-latest services: + arangodb: + image: arangodb + ports: + - 8529:8529 + env: + ARANGO_ROOT_PASSWORD: deadpool + ARANGODB_OVERRIDE_DETECTED_TOTAL_MEMORY: 500M postgres: image: postgres ports: @@ -207,6 +217,10 @@ jobs: - run: cargo test -p ${{ matrix.crate }} --all-features env: + ARANGODB__URL: http://127.0.0.1:8529 + ARANGODB__USERNAME: root + ARANGODB__PASSWORD: deadpool + ARANGODB__USE_JWT: "true" PG__HOST: 127.0.0.1 PG__PORT: 5432 PG__USER: deadpool @@ -231,6 +245,7 @@ jobs: matrix: crate: - deadpool + - deadpool-arangodb - deadpool-diesel - deadpool-lapin - deadpool-postgres diff --git a/Cargo.toml b/Cargo.toml index 2e8b9c1d..8734872e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ harness = false [workspace] members = [ + "arangodb", "diesel", "lapin", "postgres", diff --git a/README.md b/README.md index 74794a44..171035e7 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,11 @@ struct Manager {} impl managed::Manager for Manager { type Type = Computer; type Error = Error; - + async fn create(&self) -> Result { Ok(Computer {}) } - + async fn recycle(&self, _: &mut Computer) -> managed::RecycleResult { Ok(()) } @@ -98,6 +98,7 @@ Backend | Crate | Latest Version | [rusqlite](https://crates.io/crates/rusqlite) | [deadpool-sqlite](https://crates.io/crates/deadpool-sqlite) | [![Latest Version](https://img.shields.io/crates/v/deadpool-sqlite.svg)](https://crates.io/crates/deadpool-sqlite) | [diesel](https://crates.io/crates/diesel) | [deadpool-diesel](https://crates.io/crates/deadpool-diesel) | [![Latest Version](https://img.shields.io/crates/v/deadpool-diesel.svg)](https://crates.io/crates/deadpool-diesel) | [r2d2](https://crates.io/crates/r2d2) | [deadpool-r2d2](https://crates.io/crates/deadpool-r2d2) | [![Latest Version](https://img.shields.io/crates/v/deadpool-r2d2.svg)](https://crates.io/crates/deadpool-r2d2) | +[arangors](https://crates.io/crates/arangors) | [deadpool-arangodb](https://crates.io/crates/deadpool-arangodb) | [![Latest Version](https://img.shields.io/crates/v/deadpool-arangodb.svg)](https://crates.io/crates/deadpool-arangodb) | ### Reasons for yet another connection pool diff --git a/arangodb/Cargo.toml b/arangodb/Cargo.toml new file mode 100644 index 00000000..0fb18ec3 --- /dev/null +++ b/arangodb/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "deadpool-arangodb" +version = "0.1.0-pre" +edition = "2018" +resolver = "2" +authors = ["Daniel Wiesenberg "] +description = "Dead simple async pool for ArangoDB" +keywords = ["async", "database", "pool", "arango", "arangodb"] +license = "MIT/Apache-2.0" +repository = "https://github.com/bikeshedder/deadpool/arangodb" +readme = "README.md" + +[package.metadata.docs.rs] +all-features = true + +[features] +default = ["rt_tokio_1"] +rt_tokio_1 = ["deadpool/rt_tokio_1", "arangors/reqwest_async"] +rt_async-std_1 = ["deadpool/rt_async-std_1", "arangors/surf_async"] +serde = ["deadpool/serde", "serde_1"] + +[dependencies] +deadpool = { path = "../", version = "0.9.0-pre", default-features = false, features = ["managed"] } +arangors = { git = "https://github.com/fMeow/arangors", rev = "2c31587", default-features = false, features = ["rocksdb"] } +serde_1 = { package = "serde", version = "1.0", features = ["derive"], optional = true } +url = "2.2" + +[dev-dependencies] +config = { version = "0.11", default-features = false } +dotenv = "0.15.0" +futures = "0.3" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } diff --git a/arangodb/README.md b/arangodb/README.md new file mode 100644 index 00000000..bffcabb8 --- /dev/null +++ b/arangodb/README.md @@ -0,0 +1,48 @@ +# Deadpool for ArangoDB [![Latest Version](https://img.shields.io/crates/v/deadpool-arangodb.svg)](https://crates.io/crates/deadpool-arangodb) ![Unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg "Unsafe forbidden") [![Rust 1.54+](https://img.shields.io/badge/rustc-1.54+-lightgray.svg "Rust 1.54+")](https://blog.rust-lang.org/2021/07/29/Rust-1.54.0.html) + +Deadpool is a dead simple async pool for connections and objects +of any type. + +This crate implements a [`deadpool`](https://crates.io/crates/deadpool) +manager for [`ArangoDB`](https://www.arangodb.com/) using [`arangors`](https://crates.io/crates/arangors). + +## Features + +| Feature | Description | Extra dependencies | Default | +| ------- | ----------- | ------------------ | ------- | +| `rt_tokio_1` | Enable support for [tokio](https://crates.io/crates/tokio) crate,
through the usage of [reqwest](https://crates.io/crates/reqwest) as http client | `deadpool/rt_tokio_1`, `arangors/reqwest_async` | yes | +| `rt_async-std_1` | Enable support for [async-std](https://crates.io/crates/config) crate,
through the usage of [surf](https://crates.io/crates/surf) as http client | `deadpool/rt_async-std_1`, `arangors/surf_async` | no | +| `serde` | Enable support for [serde](https://crates.io/crates/serde) crate | `deadpool/serde`, `serde/derive` | no | + +## Example + +```rust +use deadpool_arangodb::{Config, Runtime}; + +#[tokio::main] +async fn main() { + let mut cfg = Config { + url: Some("http://localhost:8529".to_string()), + username: Some("root".to_string()), + password: Some("deadpool".to_string()), + use_jwt: true, + pool: None, + }; + let pool = cfg.create_pool(Runtime::Tokio1).unwrap(); + let mut conn = pool.get().await.unwrap(); + + let db = conn.create_database("deadpool_favorite_foods") + .await.expect("Failed to create database: {:?}"); + + // Do stuff with db... +} +``` + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/arangodb/src/config.rs b/arangodb/src/config.rs new file mode 100644 index 00000000..e143498e --- /dev/null +++ b/arangodb/src/config.rs @@ -0,0 +1,135 @@ +use deadpool::{managed::BuildError, Runtime}; +#[cfg(feature = "serde")] +use serde_1::Deserialize; +use url::Url; + +use crate::{Pool, PoolConfig}; + +/// Configuration object. +/// +/// # Example (from environment) +/// +/// By enabling the `serde` feature you can read the configuration using the +/// [`config`](https://crates.io/crates/config) crate as following: +/// ```env +/// ARANGODB__URL=arangodb.example.com +/// ARANGODB__USERNAME=root +/// ARANGODB__PASSWORD=deadpool +/// ARANGODB__USE_JWT=true +/// ARANGODB__POOL__MAX_SIZE=16 +/// ARANGODB__POOL__TIMEOUTS__WAIT__SECS=2 +/// ARANGODB__POOL__TIMEOUTS__WAIT__NANOS=0 +/// ``` +/// ```rust +/// # use serde_1 as serde; +/// # +/// #[derive(serde::Deserialize)] +/// # #[serde(crate = "serde_1")] +/// struct Config { +/// arango: deadpool_arangodb::Config, +/// } +/// +/// impl Config { +/// pub fn from_env() -> Result { +/// let mut cfg = config::Config::new(); +/// cfg.merge(config::Environment::new().separator("__")).unwrap(); +/// cfg.try_into() +/// } +/// } +/// ``` +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde_1::Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "serde_1"))] +pub struct Config { + /// ArangoDB URL. + /// + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). + pub url: Option, + /// ArangoDB username. + /// If you have not manually created a new user on a ArangoDB instance, then this must be `root`. + /// + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). + pub username: Option, + /// ArangoDB password. + /// + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). + pub password: Option, + /// If jwt authentication should be used. JWT token expires after 1 month. + /// + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). + pub use_jwt: bool, + + /// [`Pool`] configuration. + pub pool: Option, +} + +impl Config { + /// Creates a new [`Config`] from the given URL. + /// + /// Url format is: `http://username:password@localhost:8529/?use_jwt=true`. If `use_jwt` is missing, then it defaults to `true`. + pub fn from_url>(url: U) -> Result> { + let url = url.into(); + let url = Url::parse(&url) + .map_err(|e| BuildError::Config(format!("Could not extract a valid config from url: `{}` - Error: {}", url, e)))?; + + let use_jwt = url.query_pairs() + .filter(|(name, _)| name == "use_jwt") + .map(|(_, value)| value.to_string()) + .next(); + let use_jwt = match use_jwt { + Some(use_jwt) => use_jwt.parse() + .map_err(|e| BuildError::Config(format!("Could not parse `use_jwt` value: `{}` - Error: {}", use_jwt, e)))?, + None => true, + }; + + Ok(Config { + url: Some(format!("{}://{}:{}", url.scheme(), url.host_str().unwrap(), url.port_or_known_default().unwrap())), + username: Some(url.username().to_string()), + password: url.password().map(ToString::to_string), + use_jwt, + pool: None, + }) + } + + + /// Creates a new [`Pool`] using this [`Config`]. + /// + /// # Errors + /// + /// See [`BuildError`] and [`ClientError`] for details. + /// + /// [`ClientError`]: arangors::ClientError + pub fn create_pool(&self, runtime: Runtime) -> Result> { + let manager = match (&self.url, &self.username, &self.password) { + (Some(_), Some(_), Some(_)) => crate::Manager::from_config(self.clone())?, + _ => { + return Err(BuildError::Config("url, username and password must be specified.".into())) + } + }; + + let pool_config = self.get_pool_config(); + Pool::builder(manager) + .config(pool_config) + .runtime(runtime) + .build() + } + + /// Returns [`deadpool::managed::PoolConfig`] which can be used to construct + /// a [`deadpool::managed::Pool`] instance. + #[must_use] + pub fn get_pool_config(&self) -> PoolConfig { + self.pool.unwrap_or_default() + } +} + +impl Default for Config { + fn default() -> Self { + Self { + url: None, + username: None, + password: None, + use_jwt: true, + pool: None, + } + } +} diff --git a/arangodb/src/lib.rs b/arangodb/src/lib.rs new file mode 100644 index 00000000..92d5a3a2 --- /dev/null +++ b/arangodb/src/lib.rs @@ -0,0 +1,163 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![deny( + nonstandard_style, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links +)] +#![forbid(non_ascii_idents, unsafe_code)] +#![warn( + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + unused_import_braces, + unused_labels, + unused_lifetimes, + unused_qualifications, + unused_results +)] + +mod config; + +use std::ops::{Deref, DerefMut}; + +use arangors::{ + Connection as ArangoConnection, + ClientError, uclient::ClientExt, +}; +use deadpool::{async_trait, managed}; + +pub use arangors; +pub use deadpool::managed::reexports::*; +pub use deadpool::managed::BuildError; +use url::Url; + +pub use self::config::Config; + +/// Type alias for using [`deadpool::managed::Pool`] with [`arangors`]. +pub type Pool = managed::Pool; + +/// Type alias for using [`deadpool::managed::PoolError`] with [`arangors`]. +pub type PoolError = managed::PoolError; + +/// Type alias for using [`deadpool::managed::Object`] with [`arangors`]. +type Object = managed::Object; + +/// Type alias for using [`deadpool::managed::RecycleResult`] with [`arangors`]. +type RecycleResult = managed::RecycleResult; + +/// Wrapper around [`arangors::Connection`]. +/// +/// This structure implements [`std::ops::Deref`] and can therefore +/// be used just like a regular [`arangors::Connection`]. +#[derive(Debug)] +pub struct Connection { + conn: Object, +} + +impl Connection { + /// Takes this [`Connection`] from its [`Pool`] permanently. + /// + /// This reduces the size of the [`Pool`]. + #[must_use] + pub fn take(this: Self) -> ArangoConnection { + Object::take(this.conn) + } +} + +impl From for Connection { + fn from(conn: Object) -> Self { + Self { conn } + } +} + +impl Deref for Connection { + type Target = ArangoConnection; + + fn deref(&self) -> &ArangoConnection { + &self.conn + } +} + +impl DerefMut for Connection { + fn deref_mut(&mut self) -> &mut ArangoConnection { + &mut self.conn + } +} + +impl AsRef for Connection { + fn as_ref(&self) -> &ArangoConnection { + &self.conn + } +} + +impl AsMut for Connection { + fn as_mut(&mut self) -> &mut ArangoConnection { + &mut self.conn + } +} + +/// [`Manager`] for creating and recycling [`arangors`] connections. +/// +/// [`Manager`]: managed::Manager +#[derive(Debug)] +pub struct Manager { + url: String, + username: String, + password: String, + use_jwt: bool, +} + +impl Manager { + /// Creates a new [`Manager`] with the given params. + pub fn new(url: &str, username: &str, password: &str, use_jwt: bool) -> Self { + Self { + url: url.to_string(), + username: username.to_string(), + password: password.to_string(), + use_jwt, + } + } + + /// Creates a new [`Manager`] with the given params. + pub fn from_config(config: Config) -> Result> { + Ok(Self { + url: config.url.ok_or(BuildError::Config("url must be specified.".into()))?, + username: config.username.ok_or(BuildError::Config("username must be specified.".into()))?, + password: config.password.ok_or(BuildError::Config("password must be specified.".into()))?, + use_jwt: config.use_jwt, + }) + } +} + +#[async_trait] +impl managed::Manager for Manager { + type Type = ArangoConnection; + type Error = ClientError; + + async fn create(&self) -> Result { + let conn = if self.use_jwt { + ArangoConnection::establish_jwt(&self.url, &self.username, &self.password) + .await? + } else { + ArangoConnection::establish_basic_auth(&self.url, &self.username, &self.password) + .await? + }; + + return Ok(conn); + } + + async fn recycle(&self, conn: &mut ArangoConnection) -> RecycleResult { + let url = Url::parse(&self.url).expect("Url should be valid at this point"); + conn.session() + // I don't know if this is the correct way to do it, but TRACE should allow us to check if the connection is still open, + // if the server answers it's open, if not, then not. + .trace(url, String::default()) + .await + .map(|_| ()) + .map_err(|e| managed::RecycleError::Backend(ClientError::HttpClient(e))) + } +} diff --git a/arangodb/tests/arangodb.rs b/arangodb/tests/arangodb.rs new file mode 100644 index 00000000..6b78bf83 --- /dev/null +++ b/arangodb/tests/arangodb.rs @@ -0,0 +1,59 @@ +use serde_1::Deserialize; + +use deadpool_arangodb::{Pool, Runtime}; + + +#[derive(Debug, Default, Deserialize)] +#[serde(crate = "serde_1")] +struct Config { + #[serde(default)] + arango: deadpool_arangodb::Config, +} + +impl Config { + pub fn from_env() -> Self { + let mut cfg = Config::test_default(); + cfg.merge(config::Environment::new().separator("__")) + .unwrap(); + cfg.try_into().unwrap() + } + + pub fn test_default() -> Self { + Self { + arango: deadpool_arangodb::Config { + url: Some("http://localhost:8529".to_string()), + username: Some("root".to_string()), + password: Some("deadpool".to_string()), + use_jwt: true, + pool: None, + } + } + } +} + +fn create_pool() -> Pool { + let cfg = Config::test_default(); + cfg.arango.create_pool(Runtime::Tokio1).unwrap() +} + +const DB_NAME: &str = "deadpool"; + +#[tokio::test] +async fn create_database() { + let pool = create_pool(); + let conn = pool.get().await.unwrap(); + + let result = conn.create_database(DB_NAME).await; + if let Err(e) = result { + assert!(false, "Failed to create database: {:?}", e) + }; + let result = conn.db(DB_NAME).await; + assert!(result.is_ok()); + + let result = conn.drop_database(DB_NAME).await; + if let Err(e) = result { + assert!(false, "Failed to drop database: {:?}", e) + }; + let result = conn.db(DB_NAME).await; + assert!(result.is_err()); +} diff --git a/src/managed/builder.rs b/src/managed/builder.rs index b28d0aef..e0d3e78c 100644 --- a/src/managed/builder.rs +++ b/src/managed/builder.rs @@ -11,6 +11,10 @@ use super::{ /// [`Pool`]. #[derive(Debug)] pub enum BuildError { + /// Something is wrong with the configuration. + /// See message string for details. + Config(String), + /// Backend reported an error when creating a [`Pool`]. Backend(E), @@ -21,6 +25,9 @@ pub enum BuildError { impl fmt::Display for BuildError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Config(msg) => { + write!(f, "Error occurred while building the pool: Config: {}", msg) + } Self::Backend(e) => write!(f, "Error occurred while building the pool: Backend: {}", e), Self::NoRuntimeSpecified(msg) => write!( f, @@ -35,7 +42,7 @@ impl std::error::Error for BuildError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Backend(e) => Some(e), - Self::NoRuntimeSpecified(_) => None, + Self::Config(_) | Self::NoRuntimeSpecified(_) => None, } } } From 0a52281388af315cc189d97d2170212e17038304 Mon Sep 17 00:00:00 2001 From: "Michael P. Jung" Date: Wed, 3 Nov 2021 10:24:57 +0100 Subject: [PATCH 2/8] Fix arangodb reexports --- arangodb/src/lib.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/arangodb/src/lib.rs b/arangodb/src/lib.rs index 92d5a3a2..54967f2c 100644 --- a/arangodb/src/lib.rs +++ b/arangodb/src/lib.rs @@ -22,29 +22,27 @@ mod config; -use std::ops::{Deref, DerefMut}; +use std::{convert::Infallible, ops::{Deref, DerefMut}}; use arangors::{ Connection as ArangoConnection, ClientError, uclient::ClientExt, }; use deadpool::{async_trait, managed}; - -pub use arangors; -pub use deadpool::managed::reexports::*; -pub use deadpool::managed::BuildError; use url::Url; -pub use self::config::Config; - -/// Type alias for using [`deadpool::managed::Pool`] with [`arangors`]. -pub type Pool = managed::Pool; +pub use arangors; -/// Type alias for using [`deadpool::managed::PoolError`] with [`arangors`]. -pub type PoolError = managed::PoolError; +pub use self::config::{Config}; -/// Type alias for using [`deadpool::managed::Object`] with [`arangors`]. -type Object = managed::Object; +pub use deadpool::managed::reexports::*; +deadpool::managed_reexports!( + "arangors", + Manager, + deadpool::managed::Object, + ClientError, + Infallible +); /// Type alias for using [`deadpool::managed::RecycleResult`] with [`arangors`]. type RecycleResult = managed::RecycleResult; @@ -123,7 +121,7 @@ impl Manager { } /// Creates a new [`Manager`] with the given params. - pub fn from_config(config: Config) -> Result> { + pub fn from_config(config: Config) -> Result { Ok(Self { url: config.url.ok_or(BuildError::Config("url must be specified.".into()))?, username: config.username.ok_or(BuildError::Config("username must be specified.".into()))?, From 638e85a607afc6b9943c6bc2bdeba549dc2ae456 Mon Sep 17 00:00:00 2001 From: "Michael P. Jung" Date: Wed, 3 Nov 2021 10:47:31 +0100 Subject: [PATCH 3/8] Fix arangodb test config --- arangodb/tests/arangodb.rs | 43 +++++++++++++++----------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/arangodb/tests/arangodb.rs b/arangodb/tests/arangodb.rs index 6b78bf83..72dd29fd 100644 --- a/arangodb/tests/arangodb.rs +++ b/arangodb/tests/arangodb.rs @@ -3,57 +3,48 @@ use serde_1::Deserialize; use deadpool_arangodb::{Pool, Runtime}; +fn default_dbname() -> String { + "deadpool".to_string() +} + + #[derive(Debug, Default, Deserialize)] #[serde(crate = "serde_1")] struct Config { #[serde(default)] arango: deadpool_arangodb::Config, + #[serde(default="default_dbname")] + dbname: String, } impl Config { pub fn from_env() -> Self { - let mut cfg = Config::test_default(); + let mut cfg = config::Config::new(); cfg.merge(config::Environment::new().separator("__")) .unwrap(); - cfg.try_into().unwrap() - } - - pub fn test_default() -> Self { - Self { - arango: deadpool_arangodb::Config { - url: Some("http://localhost:8529".to_string()), - username: Some("root".to_string()), - password: Some("deadpool".to_string()), - use_jwt: true, - pool: None, - } - } + let mut cfg = cfg.try_into::().unwrap(); + cfg.arango.url.get_or_insert("http://localhost:8529".to_string()); + cfg } } -fn create_pool() -> Pool { - let cfg = Config::test_default(); - cfg.arango.create_pool(Runtime::Tokio1).unwrap() -} - -const DB_NAME: &str = "deadpool"; - #[tokio::test] async fn create_database() { - let pool = create_pool(); + let cfg = Config::from_env(); + let pool = cfg.arango.create_pool(Runtime::Tokio1).unwrap(); let conn = pool.get().await.unwrap(); - let result = conn.create_database(DB_NAME).await; + let result = conn.create_database(&cfg.dbname).await; if let Err(e) = result { assert!(false, "Failed to create database: {:?}", e) }; - let result = conn.db(DB_NAME).await; + let result = conn.db(&cfg.dbname).await; assert!(result.is_ok()); - let result = conn.drop_database(DB_NAME).await; + let result = conn.drop_database(&cfg.dbname).await; if let Err(e) = result { assert!(false, "Failed to drop database: {:?}", e) }; - let result = conn.db(DB_NAME).await; + let result = conn.db(&cfg.dbname).await; assert!(result.is_err()); } From 0c784fc15bdb4adf4c0de350022db97ba2605b93 Mon Sep 17 00:00:00 2001 From: "Michael P. Jung" Date: Wed, 3 Nov 2021 12:47:47 +0100 Subject: [PATCH 4/8] Remove `BuildError::Config` variant That variant was added back by accident when merging the arangodb PR. --- arangodb/Cargo.toml | 2 +- arangodb/src/config.rs | 79 +++++++++++++++++++++++++++++--------- arangodb/src/lib.rs | 31 ++++++++------- arangodb/tests/arangodb.rs | 10 ++--- src/managed/builder.rs | 9 +---- 5 files changed, 86 insertions(+), 45 deletions(-) diff --git a/arangodb/Cargo.toml b/arangodb/Cargo.toml index 0fb18ec3..722c0099 100644 --- a/arangodb/Cargo.toml +++ b/arangodb/Cargo.toml @@ -20,7 +20,7 @@ rt_async-std_1 = ["deadpool/rt_async-std_1", "arangors/surf_async"] serde = ["deadpool/serde", "serde_1"] [dependencies] -deadpool = { path = "../", version = "0.9.0-pre", default-features = false, features = ["managed"] } +deadpool = { path = "../", version = "0.9.0", default-features = false, features = ["managed"] } arangors = { git = "https://github.com/fMeow/arangors", rev = "2c31587", default-features = false, features = ["rocksdb"] } serde_1 = { package = "serde", version = "1.0", features = ["derive"], optional = true } url = "2.2" diff --git a/arangodb/src/config.rs b/arangodb/src/config.rs index e143498e..916cae5b 100644 --- a/arangodb/src/config.rs +++ b/arangodb/src/config.rs @@ -1,9 +1,11 @@ -use deadpool::{managed::BuildError, Runtime}; +use std::fmt; + +use deadpool::Runtime; #[cfg(feature = "serde")] use serde_1::Deserialize; use url::Url; -use crate::{Pool, PoolConfig}; +use crate::{CreatePoolError, Pool, PoolConfig}; /// Configuration object. /// @@ -67,23 +69,29 @@ impl Config { /// Creates a new [`Config`] from the given URL. /// /// Url format is: `http://username:password@localhost:8529/?use_jwt=true`. If `use_jwt` is missing, then it defaults to `true`. - pub fn from_url>(url: U) -> Result> { + pub fn from_url>(url: U) -> Result { let url = url.into(); - let url = Url::parse(&url) - .map_err(|e| BuildError::Config(format!("Could not extract a valid config from url: `{}` - Error: {}", url, e)))?; + let url = Url::parse(&url).map_err(|e| ConfigError::InvalidUrl(url, e))?; - let use_jwt = url.query_pairs() + let use_jwt = url + .query_pairs() .filter(|(name, _)| name == "use_jwt") .map(|(_, value)| value.to_string()) .next(); let use_jwt = match use_jwt { - Some(use_jwt) => use_jwt.parse() - .map_err(|e| BuildError::Config(format!("Could not parse `use_jwt` value: `{}` - Error: {}", use_jwt, e)))?, + Some(use_jwt) => use_jwt + .parse() + .map_err(|e| ConfigError::InvalidUseJwt(use_jwt, e))?, None => true, }; Ok(Config { - url: Some(format!("{}://{}:{}", url.scheme(), url.host_str().unwrap(), url.port_or_known_default().unwrap())), + url: Some(format!( + "{}://{}:{}", + url.scheme(), + url.host_str().unwrap(), + url.port_or_known_default().unwrap() + )), username: Some(url.username().to_string()), password: url.password().map(ToString::to_string), use_jwt, @@ -91,7 +99,6 @@ impl Config { }) } - /// Creates a new [`Pool`] using this [`Config`]. /// /// # Errors @@ -99,19 +106,14 @@ impl Config { /// See [`BuildError`] and [`ClientError`] for details. /// /// [`ClientError`]: arangors::ClientError - pub fn create_pool(&self, runtime: Runtime) -> Result> { - let manager = match (&self.url, &self.username, &self.password) { - (Some(_), Some(_), Some(_)) => crate::Manager::from_config(self.clone())?, - _ => { - return Err(BuildError::Config("url, username and password must be specified.".into())) - } - }; - + pub fn create_pool(&self, runtime: Runtime) -> Result { + let manager = crate::Manager::from_config(self.clone())?; let pool_config = self.get_pool_config(); Pool::builder(manager) .config(pool_config) .runtime(runtime) .build() + .map_err(CreatePoolError::Build) } /// Returns [`deadpool::managed::PoolConfig`] which can be used to construct @@ -133,3 +135,44 @@ impl Default for Config { } } } + +/// This error is returned if the configuration contains an error +#[derive(Debug)] +pub enum ConfigError { + /// The `url` is invalid + InvalidUrl(String, url::ParseError), + /// The `use_jwt` part of the URL is invalid + InvalidUseJwt(String, std::str::ParseBoolError), + /// The `use` is `None` + MissingUrl, + /// The `username` is `None` + MissingUsername, + /// The `password` is None + MissingPassword, +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidUrl(url, e) => write!(f, "InvalidUrl: {} - Error: {}", url, e), + Self::InvalidUseJwt(use_jwt, e) => write!( + f, + "Could not parse `use_jwt` value: `{}` - Error: {}", + use_jwt, e + ), + Self::MissingUrl => write!(f, "Missing URL"), + Self::MissingUsername => write!(f, "Missing username"), + Self::MissingPassword => write!(f, "Missing password"), + } + } +} + +impl std::error::Error for ConfigError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidUrl(_, e) => Some(e), + Self::InvalidUseJwt(_, e) => Some(e), + _ => None, + } + } +} diff --git a/arangodb/src/lib.rs b/arangodb/src/lib.rs index 54967f2c..3f425c62 100644 --- a/arangodb/src/lib.rs +++ b/arangodb/src/lib.rs @@ -22,18 +22,18 @@ mod config; -use std::{convert::Infallible, ops::{Deref, DerefMut}}; - -use arangors::{ - Connection as ArangoConnection, - ClientError, uclient::ClientExt, +use std::{ + convert::Infallible, + ops::{Deref, DerefMut}, }; + +use arangors::{uclient::ClientExt, ClientError, Connection as ArangoConnection}; use deadpool::{async_trait, managed}; use url::Url; pub use arangors; -pub use self::config::{Config}; +pub use self::config::{Config, ConfigError}; pub use deadpool::managed::reexports::*; deadpool::managed_reexports!( @@ -41,7 +41,7 @@ deadpool::managed_reexports!( Manager, deadpool::managed::Object, ClientError, - Infallible + ConfigError ); /// Type alias for using [`deadpool::managed::RecycleResult`] with [`arangors`]. @@ -121,11 +121,17 @@ impl Manager { } /// Creates a new [`Manager`] with the given params. - pub fn from_config(config: Config) -> Result { + pub fn from_config(config: Config) -> Result { Ok(Self { - url: config.url.ok_or(BuildError::Config("url must be specified.".into()))?, - username: config.username.ok_or(BuildError::Config("username must be specified.".into()))?, - password: config.password.ok_or(BuildError::Config("password must be specified.".into()))?, + url: config + .url + .ok_or(CreatePoolError::Config(ConfigError::MissingUrl))?, + username: config + .username + .ok_or(CreatePoolError::Config(ConfigError::MissingUsername))?, + password: config + .password + .ok_or(CreatePoolError::Config(ConfigError::MissingPassword))?, use_jwt: config.use_jwt, }) } @@ -138,8 +144,7 @@ impl managed::Manager for Manager { async fn create(&self) -> Result { let conn = if self.use_jwt { - ArangoConnection::establish_jwt(&self.url, &self.username, &self.password) - .await? + ArangoConnection::establish_jwt(&self.url, &self.username, &self.password).await? } else { ArangoConnection::establish_basic_auth(&self.url, &self.username, &self.password) .await? diff --git a/arangodb/tests/arangodb.rs b/arangodb/tests/arangodb.rs index 72dd29fd..026e0dac 100644 --- a/arangodb/tests/arangodb.rs +++ b/arangodb/tests/arangodb.rs @@ -1,19 +1,17 @@ use serde_1::Deserialize; -use deadpool_arangodb::{Pool, Runtime}; - +use deadpool_arangodb::Runtime; fn default_dbname() -> String { "deadpool".to_string() } - #[derive(Debug, Default, Deserialize)] #[serde(crate = "serde_1")] struct Config { #[serde(default)] arango: deadpool_arangodb::Config, - #[serde(default="default_dbname")] + #[serde(default = "default_dbname")] dbname: String, } @@ -23,7 +21,9 @@ impl Config { cfg.merge(config::Environment::new().separator("__")) .unwrap(); let mut cfg = cfg.try_into::().unwrap(); - cfg.arango.url.get_or_insert("http://localhost:8529".to_string()); + cfg.arango + .url + .get_or_insert("http://localhost:8529".to_string()); cfg } } diff --git a/src/managed/builder.rs b/src/managed/builder.rs index e0d3e78c..b28d0aef 100644 --- a/src/managed/builder.rs +++ b/src/managed/builder.rs @@ -11,10 +11,6 @@ use super::{ /// [`Pool`]. #[derive(Debug)] pub enum BuildError { - /// Something is wrong with the configuration. - /// See message string for details. - Config(String), - /// Backend reported an error when creating a [`Pool`]. Backend(E), @@ -25,9 +21,6 @@ pub enum BuildError { impl fmt::Display for BuildError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Config(msg) => { - write!(f, "Error occurred while building the pool: Config: {}", msg) - } Self::Backend(e) => write!(f, "Error occurred while building the pool: Backend: {}", e), Self::NoRuntimeSpecified(msg) => write!( f, @@ -42,7 +35,7 @@ impl std::error::Error for BuildError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Backend(e) => Some(e), - Self::Config(_) | Self::NoRuntimeSpecified(_) => None, + Self::NoRuntimeSpecified(_) => None, } } } From 955db4fee6cc1790664150231a5bf2e2203569f2 Mon Sep 17 00:00:00 2001 From: "Michael P. Jung" Date: Wed, 3 Nov 2021 12:51:39 +0100 Subject: [PATCH 5/8] Make the `serde` dependency non-optional `arangos` depends on `serde` with the `derive` feature anyways so it is of no use making this optional. --- arangodb/Cargo.toml | 5 ++--- arangodb/src/config.rs | 10 +++------- arangodb/src/lib.rs | 5 +---- arangodb/tests/arangodb.rs | 3 +-- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/arangodb/Cargo.toml b/arangodb/Cargo.toml index 722c0099..69f55c9c 100644 --- a/arangodb/Cargo.toml +++ b/arangodb/Cargo.toml @@ -17,12 +17,11 @@ all-features = true default = ["rt_tokio_1"] rt_tokio_1 = ["deadpool/rt_tokio_1", "arangors/reqwest_async"] rt_async-std_1 = ["deadpool/rt_async-std_1", "arangors/surf_async"] -serde = ["deadpool/serde", "serde_1"] [dependencies] -deadpool = { path = "../", version = "0.9.0", default-features = false, features = ["managed"] } +deadpool = { path = "../", version = "0.9.0", default-features = false, features = ["managed", "serde"] } arangors = { git = "https://github.com/fMeow/arangors", rev = "2c31587", default-features = false, features = ["rocksdb"] } -serde_1 = { package = "serde", version = "1.0", features = ["derive"], optional = true } +serde = { version = "1.0", features = ["derive"] } url = "2.2" [dev-dependencies] diff --git a/arangodb/src/config.rs b/arangodb/src/config.rs index 916cae5b..e82df923 100644 --- a/arangodb/src/config.rs +++ b/arangodb/src/config.rs @@ -1,8 +1,7 @@ use std::fmt; use deadpool::Runtime; -#[cfg(feature = "serde")] -use serde_1::Deserialize; +use serde::Deserialize; use url::Url; use crate::{CreatePoolError, Pool, PoolConfig}; @@ -23,10 +22,9 @@ use crate::{CreatePoolError, Pool, PoolConfig}; /// ARANGODB__POOL__TIMEOUTS__WAIT__NANOS=0 /// ``` /// ```rust -/// # use serde_1 as serde; +/// # use serde; /// # /// #[derive(serde::Deserialize)] -/// # #[serde(crate = "serde_1")] /// struct Config { /// arango: deadpool_arangodb::Config, /// } @@ -39,9 +37,7 @@ use crate::{CreatePoolError, Pool, PoolConfig}; /// } /// } /// ``` -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde_1::Deserialize))] -#[cfg_attr(feature = "serde", serde(crate = "serde_1"))] +#[derive(Clone, Debug, Deserialize)] pub struct Config { /// ArangoDB URL. /// diff --git a/arangodb/src/lib.rs b/arangodb/src/lib.rs index 3f425c62..43d41399 100644 --- a/arangodb/src/lib.rs +++ b/arangodb/src/lib.rs @@ -22,10 +22,7 @@ mod config; -use std::{ - convert::Infallible, - ops::{Deref, DerefMut}, -}; +use std::ops::{Deref, DerefMut}; use arangors::{uclient::ClientExt, ClientError, Connection as ArangoConnection}; use deadpool::{async_trait, managed}; diff --git a/arangodb/tests/arangodb.rs b/arangodb/tests/arangodb.rs index 026e0dac..a0ee045e 100644 --- a/arangodb/tests/arangodb.rs +++ b/arangodb/tests/arangodb.rs @@ -1,4 +1,4 @@ -use serde_1::Deserialize; +use serde::Deserialize; use deadpool_arangodb::Runtime; @@ -7,7 +7,6 @@ fn default_dbname() -> String { } #[derive(Debug, Default, Deserialize)] -#[serde(crate = "serde_1")] struct Config { #[serde(default)] arango: deadpool_arangodb::Config, From 1b69c53a173f1c9c41280129d554121b5eee0e3a Mon Sep 17 00:00:00 2001 From: "Michael P. Jung" Date: Wed, 3 Nov 2021 13:00:24 +0100 Subject: [PATCH 6/8] Exclude serde feature for arangodb integration test --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2e6fe90..a305eec3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,9 @@ jobs: feature: postgres - crate: diesel feature: sqlite + exclude: + - crate: arangodb + feature: serde runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From d1a5faae318a752d7c1c1eda7bfe830fa2a69a86 Mon Sep 17 00:00:00 2001 From: "Michael P. Jung" Date: Tue, 26 Sep 2023 12:37:10 +0000 Subject: [PATCH 7/8] Update deadpool version to 0.10 --- arangodb/Cargo.toml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arangodb/Cargo.toml b/arangodb/Cargo.toml index 69f55c9c..6ca0d7f4 100644 --- a/arangodb/Cargo.toml +++ b/arangodb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deadpool-arangodb" -version = "0.1.0-pre" +version = "0.1.0" edition = "2018" resolver = "2" authors = ["Daniel Wiesenberg "] @@ -19,13 +19,16 @@ rt_tokio_1 = ["deadpool/rt_tokio_1", "arangors/reqwest_async"] rt_async-std_1 = ["deadpool/rt_async-std_1", "arangors/surf_async"] [dependencies] -deadpool = { path = "../", version = "0.9.0", default-features = false, features = ["managed", "serde"] } -arangors = { git = "https://github.com/fMeow/arangors", rev = "2c31587", default-features = false, features = ["rocksdb"] } +deadpool = { path = "../", version = "0.10.0", default-features = false, features = [ + "managed", + "serde", +] } +arangors = "0.5.4" serde = { version = "1.0", features = ["derive"] } url = "2.2" [dev-dependencies] -config = { version = "0.11", default-features = false } +config = { version = "0.13", default-features = false } dotenv = "0.15.0" futures = "0.3" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } From ca8440db7d748261b3a55053f8ee099f7c4b6370 Mon Sep 17 00:00:00 2001 From: "Michael P. Jung" Date: Wed, 19 Jun 2024 10:37:43 +0000 Subject: [PATCH 8/8] Update to latest deadpool and arangors version (wip) --- arangodb/Cargo.toml | 8 ++++---- arangodb/src/lib.rs | 9 ++++----- arangodb/tests/arangodb.rs | 18 ++++++++++-------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/arangodb/Cargo.toml b/arangodb/Cargo.toml index 6ca0d7f4..8e487f7a 100644 --- a/arangodb/Cargo.toml +++ b/arangodb/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "deadpool-arangodb" version = "0.1.0" -edition = "2018" +edition = "2021" resolver = "2" authors = ["Daniel Wiesenberg "] description = "Dead simple async pool for ArangoDB" @@ -16,14 +16,14 @@ all-features = true [features] default = ["rt_tokio_1"] rt_tokio_1 = ["deadpool/rt_tokio_1", "arangors/reqwest_async"] -rt_async-std_1 = ["deadpool/rt_async-std_1", "arangors/surf_async"] +#rt_async-std_1 = ["deadpool/rt_async-std_1", "arangors/reqwest_async"] [dependencies] -deadpool = { path = "../", version = "0.10.0", default-features = false, features = [ +deadpool = { path = "../", version = "0.12.0", default-features = false, features = [ "managed", "serde", ] } -arangors = "0.5.4" +arangors = { version = "0.6.0", default-features = false } serde = { version = "1.0", features = ["derive"] } url = "2.2" diff --git a/arangodb/src/lib.rs b/arangodb/src/lib.rs index 43d41399..9b4b47c3 100644 --- a/arangodb/src/lib.rs +++ b/arangodb/src/lib.rs @@ -25,7 +25,7 @@ mod config; use std::ops::{Deref, DerefMut}; use arangors::{uclient::ClientExt, ClientError, Connection as ArangoConnection}; -use deadpool::{async_trait, managed}; +use deadpool::managed; use url::Url; pub use arangors; @@ -36,7 +36,7 @@ pub use deadpool::managed::reexports::*; deadpool::managed_reexports!( "arangors", Manager, - deadpool::managed::Object, + managed::Object, ClientError, ConfigError ); @@ -134,7 +134,6 @@ impl Manager { } } -#[async_trait] impl managed::Manager for Manager { type Type = ArangoConnection; type Error = ClientError; @@ -147,10 +146,10 @@ impl managed::Manager for Manager { .await? }; - return Ok(conn); + Ok(conn) } - async fn recycle(&self, conn: &mut ArangoConnection) -> RecycleResult { + async fn recycle(&self, conn: &mut ArangoConnection, _metrics: &Metrics) -> RecycleResult { let url = Url::parse(&self.url).expect("Url should be valid at this point"); conn.session() // I don't know if this is the correct way to do it, but TRACE should allow us to check if the connection is still open, diff --git a/arangodb/tests/arangodb.rs b/arangodb/tests/arangodb.rs index a0ee045e..770ed49d 100644 --- a/arangodb/tests/arangodb.rs +++ b/arangodb/tests/arangodb.rs @@ -1,3 +1,4 @@ +use config::ConfigError; use serde::Deserialize; use deadpool_arangodb::Runtime; @@ -15,34 +16,35 @@ struct Config { } impl Config { - pub fn from_env() -> Self { - let mut cfg = config::Config::new(); - cfg.merge(config::Environment::new().separator("__")) + pub fn from_env() -> Result { + let cfg = config::Config::builder() + .add_source(config::Environment::default().separator("__")) + .build() .unwrap(); - let mut cfg = cfg.try_into::().unwrap(); + let mut cfg: Self = cfg.try_deserialize()?; cfg.arango .url .get_or_insert("http://localhost:8529".to_string()); - cfg + Ok(cfg) } } #[tokio::test] async fn create_database() { - let cfg = Config::from_env(); + let cfg = Config::from_env().unwrap(); let pool = cfg.arango.create_pool(Runtime::Tokio1).unwrap(); let conn = pool.get().await.unwrap(); let result = conn.create_database(&cfg.dbname).await; if let Err(e) = result { - assert!(false, "Failed to create database: {:?}", e) + panic!("Failed to create database: {:?}", e) }; let result = conn.db(&cfg.dbname).await; assert!(result.is_ok()); let result = conn.drop_database(&cfg.dbname).await; if let Err(e) = result { - assert!(false, "Failed to drop database: {:?}", e) + panic!("Failed to drop database: {:?}", e) }; let result = conn.db(&cfg.dbname).await; assert!(result.is_err());