|
| 1 | +use deadpool::{managed::BuildError, Runtime}; |
| 2 | +#[cfg(feature = "serde")] |
| 3 | +use serde_1::Deserialize; |
| 4 | +use url::Url; |
| 5 | + |
| 6 | +use crate::{Pool, PoolConfig}; |
| 7 | + |
| 8 | +/// Configuration object. |
| 9 | +/// |
| 10 | +/// # Example (from environment) |
| 11 | +/// |
| 12 | +/// By enabling the `serde` feature you can read the configuration using the |
| 13 | +/// [`config`](https://crates.io/crates/config) crate as following: |
| 14 | +/// ```env |
| 15 | +/// ARANGODB__URL=arangodb.example.com |
| 16 | +/// ARANGODB__USERNAME=root |
| 17 | +/// ARANGODB__PASSWORD=deadpool |
| 18 | +/// ARANGODB__USE_JWT=true |
| 19 | +/// ARANGODB__POOL__MAX_SIZE=16 |
| 20 | +/// ARANGODB__POOL__TIMEOUTS__WAIT__SECS=2 |
| 21 | +/// ARANGODB__POOL__TIMEOUTS__WAIT__NANOS=0 |
| 22 | +/// ``` |
| 23 | +/// ```rust |
| 24 | +/// # use serde_1 as serde; |
| 25 | +/// # |
| 26 | +/// #[derive(serde::Deserialize)] |
| 27 | +/// # #[serde(crate = "serde_1")] |
| 28 | +/// struct Config { |
| 29 | +/// arango: deadpool_arangodb::Config, |
| 30 | +/// } |
| 31 | +/// |
| 32 | +/// impl Config { |
| 33 | +/// pub fn from_env() -> Result<Self, config::ConfigError> { |
| 34 | +/// let mut cfg = config::Config::new(); |
| 35 | +/// cfg.merge(config::Environment::new().separator("__")).unwrap(); |
| 36 | +/// cfg.try_into() |
| 37 | +/// } |
| 38 | +/// } |
| 39 | +/// ``` |
| 40 | +#[derive(Clone, Debug)] |
| 41 | +#[cfg_attr(feature = "serde", derive(serde_1::Deserialize))] |
| 42 | +#[cfg_attr(feature = "serde", serde(crate = "serde_1"))] |
| 43 | +pub struct Config { |
| 44 | + /// ArangoDB URL. |
| 45 | + /// |
| 46 | + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). |
| 47 | + pub url: Option<String>, |
| 48 | + /// ArangoDB username. |
| 49 | + /// If you have not manually created a new user on a ArangoDB instance, then this must be `root`. |
| 50 | + /// |
| 51 | + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). |
| 52 | + pub username: Option<String>, |
| 53 | + /// ArangoDB password. |
| 54 | + /// |
| 55 | + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). |
| 56 | + pub password: Option<String>, |
| 57 | + /// If jwt authentication should be used. JWT token expires after 1 month. |
| 58 | + /// |
| 59 | + /// See [Arangors Connection](arangors/connection/struct.GenericConnection.html#method.establish_jwt). |
| 60 | + pub use_jwt: bool, |
| 61 | + |
| 62 | + /// [`Pool`] configuration. |
| 63 | + pub pool: Option<PoolConfig>, |
| 64 | +} |
| 65 | + |
| 66 | +impl Config { |
| 67 | + /// Creates a new [`Config`] from the given URL. |
| 68 | + /// |
| 69 | + /// Url format is: `http://username:password@localhost:8529/?use_jwt=true`. If `use_jwt` is missing, then it defaults to `true`. |
| 70 | + pub fn from_url<U: Into<String>>(url: U) -> Result<Self, BuildError<()>> { |
| 71 | + let url = url.into(); |
| 72 | + let url = Url::parse(&url) |
| 73 | + .map_err(|e| BuildError::Config(format!("Could not extract a valid config from url: `{}` - Error: {}", url, e)))?; |
| 74 | + |
| 75 | + let use_jwt = url.query_pairs() |
| 76 | + .filter(|(name, _)| name == "use_jwt") |
| 77 | + .map(|(_, value)| value.to_string()) |
| 78 | + .next(); |
| 79 | + let use_jwt = match use_jwt { |
| 80 | + Some(use_jwt) => use_jwt.parse() |
| 81 | + .map_err(|e| BuildError::Config(format!("Could not parse `use_jwt` value: `{}` - Error: {}", use_jwt, e)))?, |
| 82 | + None => true, |
| 83 | + }; |
| 84 | + |
| 85 | + Ok(Config { |
| 86 | + url: Some(format!("{}://{}:{}", url.scheme(), url.host_str().unwrap(), url.port_or_known_default().unwrap())), |
| 87 | + username: Some(url.username().to_string()), |
| 88 | + password: url.password().map(ToString::to_string), |
| 89 | + use_jwt, |
| 90 | + pool: None, |
| 91 | + }) |
| 92 | + } |
| 93 | + |
| 94 | + |
| 95 | + /// Creates a new [`Pool`] using this [`Config`]. |
| 96 | + /// |
| 97 | + /// # Errors |
| 98 | + /// |
| 99 | + /// See [`BuildError`] and [`ClientError`] for details. |
| 100 | + /// |
| 101 | + /// [`ClientError`]: arangors::ClientError |
| 102 | + pub fn create_pool(&self, runtime: Runtime) -> Result<Pool, BuildError<arangors::ClientError>> { |
| 103 | + let manager = match (&self.url, &self.username, &self.password) { |
| 104 | + (Some(_), Some(_), Some(_)) => crate::Manager::from_config(self.clone())?, |
| 105 | + _ => { |
| 106 | + return Err(BuildError::Config("url, username and password must be specified.".into())) |
| 107 | + } |
| 108 | + }; |
| 109 | + |
| 110 | + let pool_config = self.get_pool_config(); |
| 111 | + Pool::builder(manager) |
| 112 | + .config(pool_config) |
| 113 | + .runtime(runtime) |
| 114 | + .build() |
| 115 | + } |
| 116 | + |
| 117 | + /// Returns [`deadpool::managed::PoolConfig`] which can be used to construct |
| 118 | + /// a [`deadpool::managed::Pool`] instance. |
| 119 | + #[must_use] |
| 120 | + pub fn get_pool_config(&self) -> PoolConfig { |
| 121 | + self.pool.unwrap_or_default() |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +impl Default for Config { |
| 126 | + fn default() -> Self { |
| 127 | + Self { |
| 128 | + url: None, |
| 129 | + username: None, |
| 130 | + password: None, |
| 131 | + use_jwt: true, |
| 132 | + pool: None, |
| 133 | + } |
| 134 | + } |
| 135 | +} |
0 commit comments