Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add deadpool-arangodb #153

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ jobs:
fail-fast: false
matrix:
crate:
- arangodb
- diesel
- lapin
- postgres
Expand All @@ -112,6 +113,9 @@ jobs:
feature: postgres
- crate: diesel
feature: sqlite
exclude:
- crate: arangodb
feature: serde
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down Expand Up @@ -158,6 +162,7 @@ jobs:
- { crate: deadpool-runtime, msrv: '1.75.0' }
- { crate: deadpool-sync, msrv: '1.75.0' }
- { crate: deadpool, msrv: '1.75.0' }
- { crate: deadpool-arangodb, msrv: '1.75.0' }
- { crate: deadpool-diesel, msrv: '1.78.0' }
- { crate: deadpool-lapin, msrv: '1.75.0' }
- { crate: deadpool-postgres, msrv: '1.75.0' }
Expand Down Expand Up @@ -188,13 +193,21 @@ jobs:
- deadpool-runtime
- deadpool-sync
- deadpool
- deadpool-arangodb
- deadpool-diesel
- deadpool-lapin
- deadpool-postgres
- deadpool-redis
- 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:15.3-alpine
ports:
Expand Down Expand Up @@ -234,6 +247,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
Expand All @@ -258,6 +275,7 @@ jobs:
- deadpool-runtime
- deadpool-sync
- deadpool
- deadpool-arangodb
- deadpool-diesel
- deadpool-lapin
- deadpool-postgres
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ harness = false

[workspace]
members = [
"arangodb",
"diesel",
"lapin",
"memcached",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct Manager {}
impl managed::Manager for Manager {
type Type = Computer;
type Error = Error;

async fn create(&self) -> Result<Computer, Error> {
Ok(Computer {})
}
Expand Down Expand Up @@ -99,6 +99,7 @@ Backend | Crate | Latest Version |
[tiberius](https://crates.io/crates/tiberius) | [deadpool-tiberius](https://crates.io/crates/deadpool-tiberius) | [![Latest Version](https://img.shields.io/crates/v/deadpool-tiberius.svg)](https://crates.io/crates/deadpool-tiberius) |
[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) |
[rbatis](https://crates.io/crates/rbatis) | [rbatis](https://crates.io/crates/rbatis) | [![Latest Version](https://img.shields.io/crates/v/rbatis.svg)](https://crates.io/crates/rbatis) |
[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

Expand Down
34 changes: 34 additions & 0 deletions arangodb/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "deadpool-arangodb"
version = "0.1.0"
edition = "2021"
resolver = "2"
authors = ["Daniel Wiesenberg <[email protected]>"]
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/reqwest_async"]

[dependencies]
deadpool = { path = "../", version = "0.12.0", default-features = false, features = [
"managed",
"serde",
] }
arangors = { version = "0.6.0", default-features = false }
serde = { version = "1.0", features = ["derive"] }
url = "2.2"

[dev-dependencies]
config = { version = "0.13", default-features = false }
dotenv = "0.15.0"
futures = "0.3"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
48 changes: 48 additions & 0 deletions arangodb/README.md
Original file line number Diff line number Diff line change
@@ -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,<br>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,<br>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 <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)

at your option.
174 changes: 174 additions & 0 deletions arangodb/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use std::fmt;

use deadpool::Runtime;
use serde::Deserialize;
use url::Url;

use crate::{CreatePoolError, 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;
/// #
/// #[derive(serde::Deserialize)]
/// struct Config {
/// arango: deadpool_arangodb::Config,
/// }
///
/// impl Config {
/// pub fn from_env() -> Result<Self, config::ConfigError> {
/// let mut cfg = config::Config::new();
/// cfg.merge(config::Environment::new().separator("__")).unwrap();
/// cfg.try_into()
/// }
/// }
/// ```
#[derive(Clone, Debug, Deserialize)]
pub struct Config {
/// ArangoDB URL.
///
/// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt).
pub url: Option<String>,
/// 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<String>,
/// ArangoDB password.
///
/// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt).
pub password: Option<String>,
/// 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<PoolConfig>,
}

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<U: Into<String>>(url: U) -> Result<Self, ConfigError> {
let url = url.into();
let url = Url::parse(&url).map_err(|e| ConfigError::InvalidUrl(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| ConfigError::InvalidUseJwt(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<Pool, CreatePoolError> {
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
/// 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,
}
}
}

/// 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,
}
}
}
Loading
Loading