1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
//! A wrapper around Botanist database scheme
//!
//! [![rust-db-adapter](https://github.com/Botanism/rust-db-adapter/actions/workflows/rust.yml/badge.svg)](https://github.com/Botanism/rust-db-adapter/actions/workflows/rust.yml)
//!
//! # Objective
//!
//! This crate was built with the intent to provide a safe wrapper around Botanist's
//! database scheme. This way invariants remain true at all times, no other tool can
//! mess the data in an incorrect way.
//!
//! Centralizing the interactions with the database also allows finer control over
//! iterations of its scheme. On that note changes in the scheme are done through migrations scripts
//! (see the `migrations`) folder. Hence the setup of the database is made very simple with
//! [`sqlx`]'s cli tool. Moreover deviations from the scheme provided by the migration scripts
//! will be detected by the tests (see `tests/framework` and [`mod@sqlx::migrate`]).
//!
//! Finally providing a rust library allows [db_adapter] to provide useful abstractions.
//!
//! # Setup
//! Setup is intended to be as simple as possible so if you find some way to simplify a step please open
//! an issue.
//! First of all make sure you have a postgresql database up and running. Search online for walkthroughs
//! if you don't know how. Then rename `.env-example` to `.env` and enter make sure you place your values
//! in it.
//! Now install [sqlx-cli] and run the migrations using `sqlx migrate run`. If you set up the DB and `.env`
//! correctly you should be good to go!
//! If you're only using the library you don't need to do anything else but you could still
//! run the tests just in case: `cargo t`.
//!
//! # Developement
//!
//! To contribute to [db_adapter] you should setup the test environement. In addition to the previous section's
//! steps you should setup another DB for the tests and refer it in `.env`. From there you can dive in the code!
//! Just make sure you don't break any invariants and remember to respect semver. You are also expected
//! to document and tests any item added to the public API.
//!
//! [sqlx-cli]: https://github.com/launchbadge/sqlx/tree/master/sqlx-cli
//! [db_adapter]: [`self`]
pub use sqlx::postgres::PgPool;
use std::borrow::Cow;
use std::convert::TryFrom;
use std::env;
use std::fmt::Write;
use thiserror::Error;
pub mod guild;
pub mod slap;
#[cfg(test)]
mod tests;
/// Creates a [connection pool] to the database
///
/// # Panic
/// Panics if `DATABASE_URL` is not set or if the connection could not be established.
///
/// [connection pool]: sqlx::postgres::PgPool
pub async fn establish_connection() -> PgPool {
dotenv::dotenv().ok();
PgPool::connect(&env::var("DATABASE_URL").expect("`DATABASE_URL` was not set"))
.await
.expect(
format!(
"Could not establish connection to {:?}",
&env::var("DATABASE_URL")
)
.as_str(),
)
}
/// Wrapper around all errors coming from the crate
#[derive(Debug, Error)]
pub enum AdapterError {
/// [`sqlx::Error`] errors
///
/// The crate uses [`sqlx`] under the hood to communicate with the DBs.
/// If the later fails for any reason the error is relayed.
#[error("could not execute querry")]
SqlxError(#[from] sqlx::Error),
/// Errors with guilds' configuration
#[error("guild configuration error")]
GuildError(#[from] guild::GuildConfigError),
}
pub(crate) fn as_pg_array(ids: &[i64]) -> String {
let mut array = String::new();
if ids.is_empty() {
array.push_str("'{}'");
return array;
}
write!(array, "'{{").unwrap();
for int in ids {
write!(array, "{},", int).unwrap();
}
array.pop(); //removing the trailing comma
write!(array, "}}'").unwrap();
array
}
pub(crate) fn stringify_option<'a, T: std::fmt::Display>(option: Option<T>) -> Cow<'a, str> {
match option {
Some(value) => Cow::Owned(format!("'{}'", value)),
None => Cow::Borrowed("NULL"),
}
}
pub(crate) fn from_i64<I: From<u64>>(int: i64) -> I {
u64::try_from(int).unwrap().into()
}
pub(crate) fn to_i64<I: Into<u64>>(id: I) -> i64 {
i64::try_from(id.into()).unwrap()
}