Add 'PoolResult' alias; rename 'DbError' to 'Error'.

This commit is contained in:
Sergio Benitez 2020-10-21 22:33:06 -07:00
parent f5fd1007e2
commit aabbfcfe94
1 changed files with 105 additions and 112 deletions

View File

@ -394,7 +394,7 @@ use self::r2d2::ManageConnection;
#[cfg(feature = "memcache_pool")] pub extern crate memcache;
#[cfg(feature = "memcache_pool")] pub extern crate r2d2_memcache;
/// A structure representing a particular database configuration.
/// A default, helper `Config` for any `Poolable` type.
///
/// For the following configuration:
///
@ -405,19 +405,20 @@ use self::r2d2::ManageConnection;
/// timeout = 5
/// ```
///
/// would generate the following struct:
/// ...`Config::from("my_database", cargo)` would return the following struct:
///
/// ```rust,ignore
/// ```rust
/// # use rocket_contrib::databases::Config;
/// Config {
/// url: "postgres://root:root@localhost/my_database",
/// url: "postgres://root:root@localhost/my_database".into(),
/// pool_size: 10,
/// timeout: 5
/// }
/// };
/// ```
///
/// If you want to implement your own custom database adapter (or other
/// database-like struct that can be pooled by `r2d2`) and need some more
/// configurations options, you need to implement your own config struct.
/// configurations options, you may need to define a custom `Config` struct.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Config {
/// Connection URL specified in the Rocket configuration.
@ -431,61 +432,51 @@ pub struct Config {
}
use serde::{Serialize, Deserialize};
use rocket::figment::{Figment, Error, providers::Serialized};
use rocket::figment::{self, Figment, providers::Serialized};
impl Config {
/// Retrieves the database configuration for the database named `name`.
///
/// This function is primarily used by the code generated by the `#[database]`
/// attribute.
/// This function is primarily used by the generated code from the
/// `#[database]` attribute.
///
/// # Example
///
/// Consider the following configuration:
///
/// ```toml
/// ```rust
/// # #[cfg(feature = "diesel_sqlite_pool")] {
/// # use rocket::figment::{Figment, providers::{Format, Toml}};
/// // Assume that these are the contents of `Rocket.toml`:
/// # let toml = Toml::string(r#"
/// [global.databases]
/// my_db = { url = "db/db.sqlite", pool_size = 25 }
/// my_other_db = { url = "mysql://root:root@localhost/database" }
/// ```
/// # "#).nested();
///
/// Which can be extracted like following:
///
/// ```rust
/// # #[cfg(feature = "diesel_sqlite_pool")] {
/// # use rocket::figment::{value::{Map, Value}, util::map};
/// use rocket_contrib::databases::Config;
///
/// # #[rocket::launch]
/// # async fn rocket() -> _ {
/// # let db: Map<_, Value> = map! {
/// # "url" => "db/db.sqlite".into(),
/// # "pool_size" => 25.into()
/// # };
/// # let other_db: Map<_, Value> = map! {
/// # "url" => "mysql://root:root@localhost/database".into()
/// # };
/// fn pool(cargo: &rocket::Cargo) {
/// let config = Config::from("my_db", cargo).unwrap();
/// assert_eq!(config.url, "db/db.sqlite");
/// assert_eq!(config.pool_size, 25);
///
/// let config = Config::from("my_other_db", cargo).unwrap();
/// assert_eq!(config.url, "mysql://root:root@localhost/database");
/// assert_eq!(config.pool_size, cargo.config().workers as u32);
///
/// let config = Config::from("unknown_db", cargo);
/// assert!(config.is_err())
/// }
/// #
/// # let figment = rocket::Config::figment()
/// # .merge(("databases", map!["my_db" => db]))
/// # .merge(("databases", map!["my_other_db" => other_db]));
/// #
/// # let mut rocket = rocket::custom(figment);
/// // Access the global config
/// let cargo = rocket.inspect().await;
/// // Create a db config from the global one
/// let db_config = Config::from(cargo, "my_db").expect("my_db config");
/// // The same with different error handling
/// let other_db_config = match Config::from(cargo, "my_other_db") {
/// Ok(config) => config,
/// Err(err) => panic!("Couldn't read my_other_db config because of error: {:?}", err),
/// };
/// # rocket
/// # }
/// # rocket::async_test(async {
/// # let config = Figment::from(rocket::Config::default()).merge(toml);
/// # let mut rocket = rocket::custom(config);
/// # let cargo = rocket.inspect().await;
/// # pool(cargo);
/// # });
/// # }
/// ```
pub fn from(cargo: &rocket::Cargo, db: &str) -> Result<Config, Error> {
let db_key = format!("databases.{}", db);
pub fn from(db_name: &str, cargo: &rocket::Cargo) -> Result<Config, figment::Error> {
let db_key = format!("databases.{}", db_name);
let key = |name: &str| format!("{}.{}", db_key, name);
Figment::from(cargo.figment())
.merge(Serialized::default(&key("pool_size"), cargo.config().workers))
@ -499,18 +490,24 @@ impl Config {
/// This type is only relevant to implementors of the [`Poolable`] trait. See
/// the [`Poolable`] documentation for more information on how to use this type.
#[derive(Debug)]
pub enum DbError<T> {
/// The custom error type to wrap alongside `r2d2::Error`.
pub enum Error<T> {
/// A custom error of type `T`.
Custom(T),
/// The error returned by an r2d2 pool.
PoolError(r2d2::Error),
/// The error returned by `Figment` when a config fails to deserialize.
Config(Error),
/// An error occurred while initializing an `r2d2` pool.
Pool(r2d2::Error),
/// An error occurred while extracting a `figment` configuration.
Config(figment::Error),
}
impl<T> From<Error> for DbError<T> {
fn from(error: Error) -> Self {
DbError::Config(error)
impl<T> From<figment::Error> for Error<T> {
fn from(error: figment::Error) -> Self {
Error::Config(error)
}
}
impl<T> From<r2d2::Error> for Error<T> {
fn from(error: r2d2::Error) -> Self {
Error::Pool(error)
}
}
@ -529,7 +526,7 @@ impl<T> From<Error> for DbError<T> {
///
/// # Implementation Guide
///
/// As a r2d2-compatible database (or other resource) adapter provider,
/// As an r2d2-compatible database (or other resource) adapter provider,
/// implementing `Poolable` in your own library will enable Rocket users to
/// consume your adapter with its built-in connection pooling support.
///
@ -548,7 +545,6 @@ impl<T> From<Error> for DbError<T> {
/// `Poolable` for `foo::Connection`:
///
/// ```rust
/// use rocket_contrib::databases::{r2d2, DbError, Config, Poolable};
/// # mod foo {
/// # use std::fmt;
/// # use rocket_contrib::databases::r2d2;
@ -575,20 +571,16 @@ impl<T> From<Error> for DbError<T> {
/// # fn has_broken(&self, _: &mut Connection) -> bool { panic!() }
/// # }
/// # }
/// #
/// use rocket_contrib::databases::{r2d2, Error, Config, Poolable, PoolResult};
///
/// impl Poolable for foo::Connection {
/// type Manager = foo::ConnectionManager;
/// type Error = DbError<foo::Error>;
/// type Error = foo::Error;
///
/// fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
/// let config = Config::from(cargo, name)?;
/// let manager = foo::ConnectionManager::new(&config.url)
/// .map_err(DbError::Custom)?;
///
/// r2d2::Pool::builder()
/// .max_size(config.pool_size)
/// .build(manager)
/// .map_err(DbError::PoolError)
/// fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
/// let config = Config::from(db_name, cargo)?;
/// let manager = foo::ConnectionManager::new(&config.url).map_err(Error::Custom)?;
/// Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
/// }
/// }
/// ```
@ -598,113 +590,114 @@ impl<T> From<Error> for DbError<T> {
/// error type and the `r2d2::Error` type that can result from
/// `r2d2::Pool::builder()` or `database::Config::from()`.
///
/// For more concrete examples, consult Rocket's existing implementations of [`Poolable`].
/// In the event that a connection manager isn't fallible (as is the case with
/// Diesel's r2d2 connection manager, for instance), the associated error type
/// for the `Poolable` implementation should be `std::convert::Infallible`.
///
/// For more concrete example, consult Rocket's existing implementations of
/// [`Poolable`].
pub trait Poolable: Send + Sized + 'static {
/// The associated connection manager for the given connection type.
type Manager: ManageConnection<Connection=Self>;
/// The associated error type in the event that constructing the connection
/// manager and/or the connection pool fails.
type Error: std::fmt::Debug;
/// Creates an `r2d2` connection pool for `Manager::Connection`, returning
/// the pool on success.
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error>;
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self>;
}
/// A type alias for the return type of [`Poolable::pool()`].
#[allow(type_alias_bounds)]
pub type PoolResult<P: Poolable> = Result<r2d2::Pool<P::Manager>, Error<P::Error>>;
#[cfg(feature = "diesel_sqlite_pool")]
impl Poolable for diesel::SqliteConnection {
type Manager = diesel::r2d2::ConnectionManager<diesel::SqliteConnection>;
type Error = DbError<r2d2::Error>;
type Error = std::convert::Infallible;
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
let config = Config::from(cargo, name)?;
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
let config = Config::from(db_name, cargo)?;
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
.map_err(DbError::PoolError)
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
}
}
#[cfg(feature = "diesel_postgres_pool")]
impl Poolable for diesel::PgConnection {
type Manager = diesel::r2d2::ConnectionManager<diesel::PgConnection>;
type Error = DbError<r2d2::Error>;
type Error = std::convert::Infallible;
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
let config = Config::from(cargo, name)?;
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
let config = Config::from(db_name, cargo)?;
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
.map_err(DbError::PoolError)
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
}
}
#[cfg(feature = "diesel_mysql_pool")]
impl Poolable for diesel::MysqlConnection {
type Manager = diesel::r2d2::ConnectionManager<diesel::MysqlConnection>;
type Error = DbError<r2d2::Error>;
type Error = std::convert::Infallible;
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
let config = Config::from(cargo, name)?;
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
let config = Config::from(db_name, cargo)?;
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
.map_err(DbError::PoolError)
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
}
}
// TODO: Come up with a way to handle TLS
// TODO: Add a feature to enable TLS in `postgres`; parse a suitable `config`.
#[cfg(feature = "postgres_pool")]
impl Poolable for postgres::Client {
type Manager = r2d2_postgres::PostgresConnectionManager<postgres::tls::NoTls>;
type Error = DbError<postgres::Error>;
type Error = postgres::Error;
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
let config = Config::from(cargo, name)?;
let manager = r2d2_postgres::PostgresConnectionManager::new(
config.url.parse().map_err(DbError::Custom)?,
postgres::tls::NoTls,
);
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
.map_err(DbError::PoolError)
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
let config = Config::from(db_name, cargo)?;
let url = config.url.parse().map_err(Error::Custom)?;
let manager = r2d2_postgres::PostgresConnectionManager::new(url, postgres::tls::NoTls);
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
}
}
#[cfg(feature = "mysql_pool")]
impl Poolable for mysql::Conn {
type Manager = r2d2_mysql::MysqlConnectionManager;
type Error = DbError<r2d2::Error>;
type Error = std::convert::Infallible;
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
let config = Config::from(cargo, name)?;
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
let config = Config::from(db_name, cargo)?;
let opts = mysql::OptsBuilder::from_opts(&config.url);
let manager = r2d2_mysql::MysqlConnectionManager::new(opts);
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
.map_err(DbError::PoolError)
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
}
}
#[cfg(feature = "sqlite_pool")]
impl Poolable for rusqlite::Connection {
type Manager = r2d2_sqlite::SqliteConnectionManager;
type Error = DbError<r2d2::Error>;
type Error = std::convert::Infallible;
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
let config = Config::from(cargo, name)?;
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
let config = Config::from(db_name, cargo)?;
let manager = r2d2_sqlite::SqliteConnectionManager::file(&*config.url);
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
.map_err(DbError::PoolError)
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
}
}
#[cfg(feature = "memcache_pool")]
impl Poolable for memcache::Client {
type Manager = r2d2_memcache::MemcacheConnectionManager;
type Error = DbError<memcache::MemcacheError>;
// Unused, but we might want it in the future without a breaking change.
type Error = memcache::MemcacheError;
fn pool(cargo: &rocket::Cargo, name: &str) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
let config = Config::from(cargo, name)?;
fn pool(db_name: &str, cargo: &rocket::Cargo) -> PoolResult<Self> {
let config = Config::from(db_name, cargo)?;
let manager = r2d2_memcache::MemcacheConnectionManager::new(&*config.url);
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
.map_err(DbError::PoolError)
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
}
}
@ -759,7 +752,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
pub fn fairing(fairing_name: &'static str, db_name: &'static str) -> impl Fairing {
AdHoc::on_attach(fairing_name, move |mut rocket| async move {
let cargo = rocket.inspect().await;
let config = match Config::from(cargo, db_name) {
let config = match Config::from(db_name, cargo) {
Ok(config) => config,
Err(config_error) => {
rocket::error!("database configuration error for '{}'", db_name);
@ -768,7 +761,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
}
};
match C::pool(cargo, db_name) {
match C::pool(db_name, cargo) {
Ok(pool) => {
let pool_size = config.pool_size;
let managed = ConnectionPool::<K, C> {