inital commit
This commit is contained in:
commit
944715d610
42 changed files with 3176 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
target/
|
1105
Cargo.lock
generated
Normal file
1105
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
30
Cargo.toml
Normal file
30
Cargo.toml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
[package]
|
||||||
|
name = "ood_persistence"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
|
||||||
|
repository = "https://github.com/pleshevskiy/ood_persistence"
|
||||||
|
description = "Asynchronous and synchronous interfaces and persistence implementations for your OOD architecture"
|
||||||
|
keywords = ["objected", "design", "architecture", "interface", "implementation"]
|
||||||
|
categories = ["rust-patterns", "database", "database-implementations"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[features]
|
||||||
|
async = ["async-trait"]
|
||||||
|
sync = []
|
||||||
|
|
||||||
|
bb8_postgres = ["async", "bb8", "bb8-postgres"]
|
||||||
|
r2d2_postgres = ["sync", "r2d2", "r2d2-postgres"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = { version = "0.1", optional = true }
|
||||||
|
|
||||||
|
bb8 = { version = "0.7", optional = true }
|
||||||
|
bb8-postgres = { version = "0.7", optional = true }
|
||||||
|
|
||||||
|
r2d2 = { version = "0.8", optional = true }
|
||||||
|
r2d2-postgres = { package = "r2d2_postgres", version = "0.18", optional = true }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["examples/*"]
|
25
README.md
Normal file
25
README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# OOD Persistence
|
||||||
|
|
||||||
|
Asynchronous and synchronous interfaces and persistence implementations for your OOD architecture
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add `ood_persistence = { version = "0", features = ["<IMPLEMENTATION_NAME>"] }` as a dependency in `Cargo.toml`.
|
||||||
|
|
||||||
|
NOTE: change `<IMPLEMENTATION_NOTE>` to feature name from available list. See `Cargo.toml` for more information.
|
||||||
|
|
||||||
|
`Cargo.toml` example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "my-crate"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Me <user@rust-lang.org>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ood_persistence = { version = "0", features = ["bb8_postgres"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
See examples directory.
|
11
examples/web/.env.example
Normal file
11
examples/web/.env.example
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
RUST_BACKTRACE="1"
|
||||||
|
RUST_LOG="debug"
|
||||||
|
|
||||||
|
POSTGRES_PASSWORD="test"
|
||||||
|
POSTGRES_USER="postgres"
|
||||||
|
POSTGRES_DB="x"
|
||||||
|
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5577/${POSTGRES_DB}"
|
||||||
|
|
||||||
|
DATABASE_POOL_MAX_SIZE=15
|
||||||
|
SERVER_PORT=32444
|
||||||
|
FEATURE_CORS=true
|
1059
examples/web/Cargo.lock
generated
Normal file
1059
examples/web/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
30
examples/web/Cargo.toml
Normal file
30
examples/web/Cargo.toml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
[package]
|
||||||
|
name = "web_example"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# configuration
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.7"
|
||||||
|
itconfig = { version = "1.1", features = ["macro"] }
|
||||||
|
lazy_static = "1.4"
|
||||||
|
# for local development
|
||||||
|
dotenv = { version = "0.15", optional = true }
|
||||||
|
async-trait = "0.1"
|
||||||
|
|
||||||
|
# database
|
||||||
|
ood_persistence = { path = "../../", features = ["bb8_postgres"] }
|
||||||
|
postgres-types = { version = "0.2", features = ["derive"] }
|
||||||
|
|
||||||
|
# runtime
|
||||||
|
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "signal"] }
|
||||||
|
|
||||||
|
# server
|
||||||
|
hyper = { version = "0.14", features = ["server", "http1", "runtime"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
dev = ["dotenv"]
|
8
examples/web/Migra.toml
Normal file
8
examples/web/Migra.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
root = "database"
|
||||||
|
|
||||||
|
[database]
|
||||||
|
connection = "$DATABASE_URL"
|
||||||
|
|
||||||
|
[migrations]
|
||||||
|
directory = "migrations"
|
||||||
|
table_name = "migrations"
|
0
examples/web/database/initdb.d/.gitkeep
Normal file
0
examples/web/database/initdb.d/.gitkeep
Normal file
5
examples/web/database/initdb.d/schema.sql
Normal file
5
examples/web/database/initdb.d/schema.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
create table lists (
|
||||||
|
id serial primary key,
|
||||||
|
name text not null
|
||||||
|
);
|
5
examples/web/database/schema.sql
Normal file
5
examples/web/database/schema.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
create table lists (
|
||||||
|
id serial primary key,
|
||||||
|
name text not null
|
||||||
|
);
|
13
examples/web/docker-compose.dev.yml
Normal file
13
examples/web/docker-compose.dev.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
image: postgres:12.3-alpine
|
||||||
|
ports:
|
||||||
|
- 5577:5432
|
||||||
|
volumes:
|
||||||
|
- ./database/initdb.d:/docker-entrypoint-initdb.d
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_DB: x
|
8
examples/web/src/app/list/_mocks.rs
Normal file
8
examples/web/src/app/list/_mocks.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use super::List;
|
||||||
|
|
||||||
|
pub fn create_list_1_mock() -> List {
|
||||||
|
List {
|
||||||
|
id: 1,
|
||||||
|
name: String::from("My first list"),
|
||||||
|
}
|
||||||
|
}
|
32
examples/web/src/app/list/controller.rs
Normal file
32
examples/web/src/app/list/controller.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use super::service::{create_postgres_list_service, ListService};
|
||||||
|
use super::{List, ListId};
|
||||||
|
use crate::db::persistence::PersistencePool;
|
||||||
|
use crate::db::persistence::PostgresPersistence;
|
||||||
|
use crate::error::ApiResult;
|
||||||
|
|
||||||
|
pub fn create_postgres_list_controller(
|
||||||
|
persistence: PostgresPersistence,
|
||||||
|
) -> ListController<PostgresPersistence> {
|
||||||
|
ListController {
|
||||||
|
list_service: create_postgres_list_service(persistence),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListController<P>
|
||||||
|
where
|
||||||
|
P: PersistencePool,
|
||||||
|
{
|
||||||
|
list_service: ListService<P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> ListController<P>
|
||||||
|
where
|
||||||
|
P: PersistencePool,
|
||||||
|
{
|
||||||
|
pub async fn get_list_opt(&self, list_id: Option<ListId>) -> ApiResult<Option<List>> {
|
||||||
|
match list_id {
|
||||||
|
Some(list_id) => self.list_service.get_list_opt(list_id).await,
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
examples/web/src/app/list/mod.rs
Normal file
14
examples/web/src/app/list/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod _mocks;
|
||||||
|
|
||||||
|
pub mod controller;
|
||||||
|
pub mod service;
|
||||||
|
pub mod storage_type;
|
||||||
|
|
||||||
|
pub type ListId = i32;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||||
|
pub struct List {
|
||||||
|
pub id: ListId,
|
||||||
|
pub name: String,
|
||||||
|
}
|
33
examples/web/src/app/list/service.rs
Normal file
33
examples/web/src/app/list/service.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use super::storage_type::ListStorage;
|
||||||
|
use super::{List, ListId};
|
||||||
|
use crate::db::list::storage::PostgresListStorage;
|
||||||
|
use crate::db::persistence::{PersistencePool, PostgresPersistence};
|
||||||
|
use crate::error::ApiResult;
|
||||||
|
|
||||||
|
pub fn create_postgres_list_service(
|
||||||
|
persistence: PostgresPersistence,
|
||||||
|
) -> ListService<PostgresPersistence> {
|
||||||
|
ListService {
|
||||||
|
persistence,
|
||||||
|
list_storage: Box::new(PostgresListStorage {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListService<P>
|
||||||
|
where
|
||||||
|
P: PersistencePool,
|
||||||
|
{
|
||||||
|
persistence: P,
|
||||||
|
list_storage: Box<dyn ListStorage<P::Conn>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> ListService<P>
|
||||||
|
where
|
||||||
|
P: PersistencePool,
|
||||||
|
{
|
||||||
|
pub async fn get_list_opt(&self, list_id: ListId) -> ApiResult<Option<List>> {
|
||||||
|
let mut conn = self.persistence.get_connection().await?;
|
||||||
|
let list = self.list_storage.get_list_opt(&mut conn, list_id).await?;
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
}
|
10
examples/web/src/app/list/storage_type.rs
Normal file
10
examples/web/src/app/list/storage_type.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use super::{List, ListId};
|
||||||
|
use crate::db::persistence::{ConnectionClient, QueryResult};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ListStorage<Conn>: Send + Sync
|
||||||
|
where
|
||||||
|
Conn: ConnectionClient,
|
||||||
|
{
|
||||||
|
async fn get_list_opt(&self, conn: &mut Conn, id: ListId) -> QueryResult<Option<List>>;
|
||||||
|
}
|
1
examples/web/src/app/mod.rs
Normal file
1
examples/web/src/app/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod list;
|
47
examples/web/src/config.rs
Normal file
47
examples/web/src/config.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
itconfig::config! {
|
||||||
|
#![config(unwrap)]
|
||||||
|
|
||||||
|
// RUST_BACKTRACE => "1",
|
||||||
|
|
||||||
|
RUST_LOG => "error",
|
||||||
|
|
||||||
|
database {
|
||||||
|
URL,
|
||||||
|
|
||||||
|
pool {
|
||||||
|
MAX_SIZE: u32 => 15,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
PORT: u16 => 8000,
|
||||||
|
},
|
||||||
|
|
||||||
|
feature {
|
||||||
|
static CORS: bool => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Util for configure application via env variables.
|
||||||
|
///
|
||||||
|
/// * Reads .env file for configure application (only for `dev` feature)
|
||||||
|
/// * Enables log macros via `env_logger` (See: https://docs.rs/env_logger)
|
||||||
|
/// * Initializes env config (See: https://docs.rs/itconfig)
|
||||||
|
///
|
||||||
|
/// Note: When enabled `dev` feature, this function try to load .env file
|
||||||
|
/// for configure application. If .env file cannot read will panic.
|
||||||
|
pub fn load_env_config() {
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
dotenv::dotenv().expect("Cannot load .env file");
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
debug!("Env variables from .env file loaded successfully");
|
||||||
|
|
||||||
|
debug!("Env configuration loaded successfully");
|
||||||
|
}
|
21
examples/web/src/db/list/mod.rs
Normal file
21
examples/web/src/db/list/mod.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::app::list::List;
|
||||||
|
|
||||||
|
pub mod storage;
|
||||||
|
|
||||||
|
pub type DbListId = i32;
|
||||||
|
|
||||||
|
#[derive(Debug, FromSql)]
|
||||||
|
#[postgres(name = "lists")]
|
||||||
|
struct DbList {
|
||||||
|
pub id: DbListId,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DbList> for List {
|
||||||
|
fn from(db: DbList) -> Self {
|
||||||
|
Self {
|
||||||
|
id: db.id,
|
||||||
|
name: db.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
examples/web/src/db/list/storage.rs
Normal file
29
examples/web/src/db/list/storage.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use super::DbList;
|
||||||
|
use crate::app::list::storage_type::ListStorage;
|
||||||
|
use crate::app::list::{List, ListId};
|
||||||
|
use crate::db::persistence::{try_get_one, ConnectionClient, PostgresConnection, QueryResult};
|
||||||
|
use postgres_types::Type;
|
||||||
|
|
||||||
|
pub struct PostgresListStorage {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'c> ListStorage<PostgresConnection<'c>> for PostgresListStorage {
|
||||||
|
async fn get_list_opt(
|
||||||
|
&self,
|
||||||
|
conn: &mut PostgresConnection<'c>,
|
||||||
|
list_id: ListId,
|
||||||
|
) -> QueryResult<Option<List>> {
|
||||||
|
let inner_conn = conn.inner();
|
||||||
|
|
||||||
|
let stmt = inner_conn
|
||||||
|
.prepare_typed("select l from lists as l where l.id = $1", &[Type::INT4])
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
inner_conn
|
||||||
|
.query_opt(&stmt, &[&list_id])
|
||||||
|
.await?
|
||||||
|
.map(try_get_one::<DbList, _>)
|
||||||
|
.transpose()
|
||||||
|
.map_err(From::from)
|
||||||
|
}
|
||||||
|
}
|
2
examples/web/src/db/mod.rs
Normal file
2
examples/web/src/db/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod list;
|
||||||
|
pub mod persistence;
|
35
examples/web/src/db/persistence.rs
Normal file
35
examples/web/src/db/persistence.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::config;
|
||||||
|
use ood_persistence::bb8_postgres::{tokio_postgres, NoTlsManager};
|
||||||
|
pub use ood_persistence::bb8_postgres::{
|
||||||
|
NoTlsConnection as PostgresConnection, NoTlsPersistence as PostgresPersistence,
|
||||||
|
NoTlsPool as PostgresPool,
|
||||||
|
};
|
||||||
|
pub use ood_persistence::{
|
||||||
|
asyn::{ConnectionClient, PersistencePool},
|
||||||
|
error::Result as QueryResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn create_postgres_pool() -> PostgresPool {
|
||||||
|
let db_conn_config = config::database::URL()
|
||||||
|
.parse()
|
||||||
|
.expect("Failed to convert database url to database config");
|
||||||
|
|
||||||
|
let manager = NoTlsManager::new(db_conn_config, tokio_postgres::NoTls);
|
||||||
|
let pool = PostgresPool::builder()
|
||||||
|
.max_size(config::database::pool::MAX_SIZE())
|
||||||
|
.build(manager)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create database pool");
|
||||||
|
|
||||||
|
debug!("Created database DatabaseConnection pool successfully");
|
||||||
|
|
||||||
|
pool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_one<Db, App>(row: tokio_postgres::Row) -> Result<App, tokio_postgres::Error>
|
||||||
|
where
|
||||||
|
Db: postgres_types::FromSqlOwned,
|
||||||
|
App: From<Db>,
|
||||||
|
{
|
||||||
|
row.try_get(0).map(From::<Db>::from)
|
||||||
|
}
|
65
examples/web/src/error.rs
Normal file
65
examples/web/src/error.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use ood_persistence::error::PersistenceError;
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub type SyncStdError = Box<dyn error::Error + Send + Sync + 'static>;
|
||||||
|
pub type StdResult<T> = Result<T, SyncStdError>;
|
||||||
|
pub type ApiResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
PersistenceError(PersistenceError),
|
||||||
|
Rest(RestKind),
|
||||||
|
Serde(serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::PersistenceError(err) => write!(f, "{}", err),
|
||||||
|
Self::Rest(err) => write!(f, "{}", err),
|
||||||
|
Self::Serde(err) => write!(f, "{}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl From<PersistenceError> for Error {
|
||||||
|
fn from(err: PersistenceError) -> Self {
|
||||||
|
Self::PersistenceError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hyper::Error> for Error {
|
||||||
|
fn from(err: hyper::Error) -> Self {
|
||||||
|
Self::Rest(RestKind::Hyper(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hyper::http::Error> for Error {
|
||||||
|
fn from(err: hyper::http::Error) -> Self {
|
||||||
|
Self::Rest(RestKind::HyperHttp(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for Error {
|
||||||
|
fn from(err: serde_json::Error) -> Self {
|
||||||
|
Self::Serde(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RestKind {
|
||||||
|
Hyper(hyper::Error),
|
||||||
|
HyperHttp(hyper::http::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RestKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Hyper(err) => write!(f, "{}", err),
|
||||||
|
Self::HyperHttp(err) => write!(f, "{}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
examples/web/src/lib.rs
Normal file
17
examples/web/src/lib.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate postgres_types;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate async_trait;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
mod db;
|
||||||
|
pub mod rest;
|
10
examples/web/src/main.rs
Normal file
10
examples/web/src/main.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use web_example::config::load_env_config;
|
||||||
|
use web_example::error::StdResult;
|
||||||
|
use web_example::rest::server::start_server;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> StdResult<()> {
|
||||||
|
load_env_config();
|
||||||
|
start_server().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
10
examples/web/src/rest/context.rs
Normal file
10
examples/web/src/rest/context.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use crate::db::persistence::{PostgresPersistence, PostgresPool};
|
||||||
|
|
||||||
|
pub struct RestGlobalContext {
|
||||||
|
pub pool: PostgresPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RestReqContext<'p> {
|
||||||
|
pub persistence: PostgresPersistence<'p>,
|
||||||
|
}
|
6
examples/web/src/rest/mod.rs
Normal file
6
examples/web/src/rest/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
pub mod context;
|
||||||
|
pub mod prelude;
|
||||||
|
pub mod routes;
|
||||||
|
pub mod server;
|
||||||
|
pub mod server_utils;
|
||||||
|
pub mod types;
|
6
examples/web/src/rest/prelude.rs
Normal file
6
examples/web/src/rest/prelude.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
pub use super::types::{QueryParams, ReqVariables, RestResponseData, RestResult};
|
||||||
|
pub use crate::error::{ApiResult, StdResult};
|
||||||
|
pub use hyper::{
|
||||||
|
header::{self, HeaderValue},
|
||||||
|
Body, Method, Request, Response, StatusCode,
|
||||||
|
};
|
32
examples/web/src/rest/routes/api/list.rs
Normal file
32
examples/web/src/rest/routes/api/list.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use crate::app::list::controller::create_postgres_list_controller;
|
||||||
|
use crate::rest::routes::*;
|
||||||
|
use crate::rest::server_utils::{create_not_found_err_json_response, create_ok_json_response};
|
||||||
|
|
||||||
|
pub enum Router {
|
||||||
|
GetListById(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeFrom<RouteParts<'_>> for Router {
|
||||||
|
fn maybe_from((method, uri_path_parts): RouteParts<'_>) -> Option<Self> {
|
||||||
|
match (method, uri_path_parts) {
|
||||||
|
(&Method::GET, [list_id]) => Some(Self::GetListById(list_id.to_string())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Resolver for Router {
|
||||||
|
async fn resolve(&self, ctx: RestReqContext<'_>, vars: ReqVariables<'_>) -> RestResult {
|
||||||
|
let controller = create_postgres_list_controller(ctx.persistence);
|
||||||
|
match self {
|
||||||
|
Self::GetListById(list_id) => {
|
||||||
|
let res = controller.get_list_opt(list_id.parse().ok()).await?;
|
||||||
|
match res {
|
||||||
|
Some(list) => create_ok_json_response(list),
|
||||||
|
None => create_not_found_err_json_response("List not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
examples/web/src/rest/routes/api/mod.rs
Normal file
33
examples/web/src/rest/routes/api/mod.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::rest::routes::*;
|
||||||
|
|
||||||
|
mod list;
|
||||||
|
|
||||||
|
pub enum Router {
|
||||||
|
List(list::Router),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeFrom<RouteParts<'_>> for Router {
|
||||||
|
fn maybe_from((method, uri_path_parts): RouteParts<'_>) -> Option<Self> {
|
||||||
|
let rest_parts = &uri_path_parts[1..];
|
||||||
|
uri_path_parts.get(0).copied().and_then(|part| match part {
|
||||||
|
"lists" => list::Router::maybe_from((method, rest_parts)).map(Self::List),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Resolver for Router {
|
||||||
|
async fn resolve(&self, ctx: RestReqContext<'_>, vars: ReqVariables<'_>) -> RestResult {
|
||||||
|
let mut res = match self {
|
||||||
|
Self::List(router) => router.resolve(ctx, vars).await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
res.headers_mut().append(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
HeaderValue::from_static("application/json; charset=utf-8"),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
16
examples/web/src/rest/routes/mod.rs
Normal file
16
examples/web/src/rest/routes/mod.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::rest::context::RestReqContext;
|
||||||
|
use crate::rest::prelude::*;
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
pub mod root;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Resolver {
|
||||||
|
async fn resolve(&self, ctx: RestReqContext<'_>, vars: ReqVariables<'_>) -> RestResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteParts<'a> = (&'a Method, &'a [&'a str]);
|
||||||
|
|
||||||
|
trait MaybeFrom<T>: Sized {
|
||||||
|
fn maybe_from(_: T) -> Option<Self>;
|
||||||
|
}
|
44
examples/web/src/rest/routes/root.rs
Normal file
44
examples/web/src/rest/routes/root.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use crate::config;
|
||||||
|
use crate::rest::routes::*;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Router {
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
CORS,
|
||||||
|
HealthCheck,
|
||||||
|
NotFound,
|
||||||
|
Api(api::Router),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RouteParts<'_>> for Router {
|
||||||
|
fn from((method, uri_path_parts): RouteParts<'_>) -> Self {
|
||||||
|
match (method, uri_path_parts) {
|
||||||
|
(&Method::OPTIONS, _) if config::feature::CORS() => Self::CORS,
|
||||||
|
(_, &["api", ..]) => api::Router::maybe_from((method, &uri_path_parts[1..]))
|
||||||
|
.map(Self::Api)
|
||||||
|
.unwrap_or(Self::NotFound),
|
||||||
|
(&Method::GET, &["health"]) => Self::HealthCheck,
|
||||||
|
_ => Self::NotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Resolver for Router {
|
||||||
|
async fn resolve(&self, ctx: RestReqContext<'_>, vars: ReqVariables<'_>) -> RestResult {
|
||||||
|
let res = match self {
|
||||||
|
Self::CORS => Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.body(Body::empty())?,
|
||||||
|
Self::Api(route) => route.resolve(ctx, vars).await?,
|
||||||
|
Self::HealthCheck => Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.body(Body::from("Ok"))?,
|
||||||
|
Self::NotFound => Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Body::from("Not Found"))?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
95
examples/web/src/rest/server.rs
Normal file
95
examples/web/src/rest/server.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use super::server_utils;
|
||||||
|
use crate::config;
|
||||||
|
use crate::db::persistence::create_postgres_pool;
|
||||||
|
use crate::error::SyncStdError;
|
||||||
|
use crate::rest::context::{RestGlobalContext, RestReqContext};
|
||||||
|
use crate::rest::prelude::*;
|
||||||
|
use crate::rest::routes::{self, Resolver};
|
||||||
|
use crate::rest::types::REST_INTERNAL_SERVER_ERROR;
|
||||||
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
use hyper::Server;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Waits for the Ctrl+C signal for graceful shutdown backend
|
||||||
|
async fn shutdown_signal() {
|
||||||
|
tokio::signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install CTRL+C signal handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_server() -> StdResult<()> {
|
||||||
|
let pool = create_postgres_pool().await;
|
||||||
|
// let persistence = ood_persistence::bb8_postgres::new(&pool);
|
||||||
|
let context = Arc::new(RestGlobalContext { pool });
|
||||||
|
|
||||||
|
let new_service = make_service_fn(move |_| {
|
||||||
|
let context = context.clone();
|
||||||
|
async {
|
||||||
|
Ok::<_, SyncStdError>(service_fn(move |req| process_request(req, context.clone())))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let port = config::server::PORT();
|
||||||
|
let addr = ([0, 0, 0, 0], port).into();
|
||||||
|
let server = Server::bind(&addr)
|
||||||
|
.serve(new_service)
|
||||||
|
.with_graceful_shutdown(shutdown_signal());
|
||||||
|
|
||||||
|
info!("🚀 Server listening on http://localhost:{}", port);
|
||||||
|
|
||||||
|
server.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_request_uri_path(uri_path: &str) -> Vec<&str> {
|
||||||
|
uri_path
|
||||||
|
.split('/')
|
||||||
|
.filter(|part| !part.is_empty())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_request(
|
||||||
|
req: Request<Body>,
|
||||||
|
context: Arc<RestGlobalContext>,
|
||||||
|
) -> StdResult<Response<Body>> {
|
||||||
|
let (req_parts, req_body) = req.into_parts();
|
||||||
|
let query_params = server_utils::get_query_params(req_parts.uri.query());
|
||||||
|
let req_variables = ReqVariables::new(req_body, query_params);
|
||||||
|
|
||||||
|
let method = &req_parts.method;
|
||||||
|
let uri_path_parts = &split_request_uri_path(req_parts.uri.path())[..];
|
||||||
|
|
||||||
|
let req_context = RestReqContext {
|
||||||
|
persistence: ood_persistence::bb8_postgres::new(&context.pool),
|
||||||
|
};
|
||||||
|
|
||||||
|
let route = routes::root::Router::from((method, uri_path_parts));
|
||||||
|
let mut res = match route.resolve(req_context, req_variables).await {
|
||||||
|
Err(_) => server_utils::create_json_response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
RestResponseData::<serde_json::Value>::error(REST_INTERNAL_SERVER_ERROR),
|
||||||
|
)
|
||||||
|
// TODO(pleshevskiy): investigate why `Send` is not implemented
|
||||||
|
.unwrap(),
|
||||||
|
Ok(res) => res,
|
||||||
|
};
|
||||||
|
|
||||||
|
if config::feature::CORS() {
|
||||||
|
let headers = res.headers_mut();
|
||||||
|
headers.insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
HeaderValue::from_static("*"),
|
||||||
|
);
|
||||||
|
headers.insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_METHODS,
|
||||||
|
HeaderValue::from_static("HEAD, GET, POST, PUT, PATCH"),
|
||||||
|
);
|
||||||
|
headers.insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_HEADERS,
|
||||||
|
HeaderValue::from_static("Authorization, Content-Type"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
65
examples/web/src/rest/server_utils.rs
Normal file
65
examples/web/src/rest/server_utils.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use crate::error::StdResult;
|
||||||
|
use crate::rest::prelude::*;
|
||||||
|
use serde::{de, ser};
|
||||||
|
|
||||||
|
pub async fn deserialize_request_body<T>(req_body: Body) -> StdResult<T>
|
||||||
|
where
|
||||||
|
T: de::DeserializeOwned,
|
||||||
|
{
|
||||||
|
let body_bytes = hyper::body::to_bytes(req_body).await?;
|
||||||
|
serde_json::from_slice(&body_bytes).map_err(From::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize_response<T>(res: Response<T>) -> RestResult
|
||||||
|
where
|
||||||
|
T: ser::Serialize,
|
||||||
|
{
|
||||||
|
let (parts, body) = res.into_parts();
|
||||||
|
let body = serde_json::to_vec(&body)?;
|
||||||
|
Ok(Response::from_parts(parts, Body::from(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_query_params(req_query: Option<&str>) -> QueryParams<'_> {
|
||||||
|
req_query
|
||||||
|
.map(|query| {
|
||||||
|
query
|
||||||
|
.split('&')
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|param| {
|
||||||
|
let mut parts = param.split('=');
|
||||||
|
if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
|
||||||
|
Some((key, value))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<QueryParams<'_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_not_found_err_json_response(message: &'static str) -> RestResult {
|
||||||
|
create_err_json_response(StatusCode::NOT_FOUND, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_err_json_response(status: StatusCode, message: &'static str) -> RestResult {
|
||||||
|
create_json_response::<serde_json::Value>(status, RestResponseData::simple_error(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_ok_json_response<Data: ser::Serialize>(body: Data) -> RestResult {
|
||||||
|
create_json_response(StatusCode::OK, RestResponseData::new(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_json_response<Data: ser::Serialize>(
|
||||||
|
status: StatusCode,
|
||||||
|
body: RestResponseData<Data>,
|
||||||
|
) -> RestResult {
|
||||||
|
let res = Response::builder()
|
||||||
|
.status(status)
|
||||||
|
.header(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
HeaderValue::from_static("application/json; charset=utf-8"),
|
||||||
|
)
|
||||||
|
.body(body)?;
|
||||||
|
serialize_response(res)
|
||||||
|
}
|
64
examples/web/src/rest/types.rs
Normal file
64
examples/web/src/rest/types.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::rest::prelude::ApiResult;
|
||||||
|
use hyper::{Body, Response};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub type RestResult = ApiResult<Response<Body>>;
|
||||||
|
|
||||||
|
pub type QueryParams<'a> = HashMap<&'a str, &'a str>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ReqVariables<'params> {
|
||||||
|
body: Body,
|
||||||
|
query_params: QueryParams<'params>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'params> ReqVariables<'params> {
|
||||||
|
pub fn new(body: Body, query_params: QueryParams<'params>) -> Self {
|
||||||
|
ReqVariables { body, query_params }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct RestResponseData<Data: Serialize> {
|
||||||
|
data: Option<Data>,
|
||||||
|
error: Option<RestResponseError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Serialize> Default for RestResponseData<S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
data: None,
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Data: Serialize> RestResponseData<Data> {
|
||||||
|
pub fn new(data: Data) -> Self {
|
||||||
|
Self {
|
||||||
|
data: Some(data),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error(err: RestResponseError) -> Self {
|
||||||
|
Self {
|
||||||
|
error: Some(err),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_error(message: &'static str) -> Self {
|
||||||
|
Self::error(RestResponseError { message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct RestResponseError {
|
||||||
|
message: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const REST_INTERNAL_SERVER_ERROR: RestResponseError = RestResponseError {
|
||||||
|
message: "internal server error",
|
||||||
|
};
|
14
src/asyn.rs
Normal file
14
src/asyn.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::error;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait PersistencePool: Send + Sync {
|
||||||
|
type Conn: ConnectionClient;
|
||||||
|
|
||||||
|
async fn get_connection(&self) -> error::Result<Self::Conn>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ConnectionClient {
|
||||||
|
type InnerConn;
|
||||||
|
|
||||||
|
fn inner(&mut self) -> &mut Self::InnerConn;
|
||||||
|
}
|
52
src/bb8_postgres.rs
Normal file
52
src/bb8_postgres.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::asyn::{ConnectionClient, PersistencePool};
|
||||||
|
use crate::error;
|
||||||
|
|
||||||
|
pub use bb8::{Pool, PooledConnection};
|
||||||
|
pub use bb8_postgres::tokio_postgres;
|
||||||
|
pub use bb8_postgres::PostgresConnectionManager as Manager;
|
||||||
|
|
||||||
|
pub type NoTlsManager = Manager<tokio_postgres::NoTls>;
|
||||||
|
pub type NoTlsPersistence<'p> = Persistence<'p, NoTlsManager>;
|
||||||
|
pub type NoTlsConnection<'p> = Connection<'p, NoTlsManager>;
|
||||||
|
pub type NoTlsPool = Pool<NoTlsManager>;
|
||||||
|
|
||||||
|
pub type InnerConn<'p, M> = PooledConnection<'p, M>;
|
||||||
|
|
||||||
|
pub fn new<M>(pool: &Pool<M>) -> Persistence<M>
|
||||||
|
where
|
||||||
|
M: bb8::ManageConnection,
|
||||||
|
{
|
||||||
|
Persistence(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Persistence<'p, M: bb8::ManageConnection>(&'p Pool<M>);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'p, M> PersistencePool for Persistence<'p, M>
|
||||||
|
where
|
||||||
|
M: bb8::ManageConnection + Send + Sync,
|
||||||
|
{
|
||||||
|
type Conn = Connection<'p, M>;
|
||||||
|
|
||||||
|
async fn get_connection(&self) -> error::Result<Self::Conn> {
|
||||||
|
self.0
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.map_err(|_| error::PersistenceError::GetConnection)
|
||||||
|
.map(Connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Connection<'p, M: bb8::ManageConnection>(InnerConn<'p, M>);
|
||||||
|
|
||||||
|
impl<'c, M> ConnectionClient for Connection<'c, M>
|
||||||
|
where
|
||||||
|
M: bb8::ManageConnection,
|
||||||
|
{
|
||||||
|
type InnerConn = InnerConn<'c, M>;
|
||||||
|
|
||||||
|
fn inner(&mut self) -> &mut Self::InnerConn {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
36
src/error.rs
Normal file
36
src/error.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, PersistenceError>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PersistenceError {
|
||||||
|
GetConnection,
|
||||||
|
UpgradeToTransaction,
|
||||||
|
CommitTransaction,
|
||||||
|
RollbackTransaction,
|
||||||
|
DbError(Box<dyn std::error::Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PersistenceError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
PersistenceError::GetConnection => f.write_str("Cannot get connection"),
|
||||||
|
PersistenceError::UpgradeToTransaction => {
|
||||||
|
f.write_str("Cannot upgrade connection to transaction")
|
||||||
|
}
|
||||||
|
PersistenceError::CommitTransaction => f.write_str("Cannot commit transaction"),
|
||||||
|
PersistenceError::RollbackTransaction => f.write_str("Cannot rollback transaction"),
|
||||||
|
PersistenceError::DbError(err) => write!(f, "DbError: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for PersistenceError {}
|
||||||
|
|
||||||
|
#[cfg(feature = "bb8_postgres")]
|
||||||
|
impl From<bb8_postgres::tokio_postgres::Error> for PersistenceError {
|
||||||
|
fn from(err: bb8_postgres::tokio_postgres::Error) -> Self {
|
||||||
|
Self::DbError(Box::new(err))
|
||||||
|
}
|
||||||
|
}
|
23
src/lib.rs
Normal file
23
src/lib.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate async_trait;
|
||||||
|
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub mod asyn;
|
||||||
|
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
pub mod syn;
|
||||||
|
|
||||||
|
#[cfg(feature = "bb8")]
|
||||||
|
pub use bb8;
|
||||||
|
|
||||||
|
#[cfg(feature = "bb8_postgres")]
|
||||||
|
pub mod bb8_postgres;
|
||||||
|
|
||||||
|
#[cfg(feature = "r2d2")]
|
||||||
|
pub use r2d2;
|
||||||
|
|
||||||
|
#[cfg(feature = "r2d2_postgres")]
|
||||||
|
pub mod r2d2_postgres;
|
||||||
|
|
||||||
|
pub mod error;
|
51
src/r2d2_postgres.rs
Normal file
51
src/r2d2_postgres.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::error;
|
||||||
|
use crate::syn::{ConnectionClient, PersistencePool};
|
||||||
|
|
||||||
|
pub use r2d2::{Pool, PooledConnection};
|
||||||
|
pub use r2d2_postgres::postgres;
|
||||||
|
pub use r2d2_postgres::PostgresConnectionManager as Manager;
|
||||||
|
|
||||||
|
pub type NoTlsManager = Manager<postgres::NoTls>;
|
||||||
|
pub type NoTlsPersistence<'p> = Persistence<'p, NoTlsManager>;
|
||||||
|
pub type NoTlsConnection<'p> = Connection<NoTlsManager>;
|
||||||
|
pub type NoTlsPool = Pool<NoTlsManager>;
|
||||||
|
|
||||||
|
pub type InnerConn<M> = PooledConnection<M>;
|
||||||
|
|
||||||
|
pub fn new<M>(pool: &Pool<M>) -> Persistence<M>
|
||||||
|
where
|
||||||
|
M: r2d2::ManageConnection,
|
||||||
|
{
|
||||||
|
Persistence(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Persistence<'p, M: r2d2::ManageConnection>(&'p Pool<M>);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'p, M> PersistencePool for Persistence<'p, M>
|
||||||
|
where
|
||||||
|
M: r2d2::ManageConnection + Send + Sync,
|
||||||
|
{
|
||||||
|
type Conn = Connection<M>;
|
||||||
|
|
||||||
|
fn get_connection(&self) -> error::Result<Self::Conn> {
|
||||||
|
self.0
|
||||||
|
.get()
|
||||||
|
.map_err(|_| error::PersistenceError::GetConnection)
|
||||||
|
.map(Connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Connection<M: r2d2::ManageConnection>(InnerConn<M>);
|
||||||
|
|
||||||
|
impl<M> ConnectionClient for Connection<M>
|
||||||
|
where
|
||||||
|
M: r2d2::ManageConnection,
|
||||||
|
{
|
||||||
|
type InnerConn = InnerConn<M>;
|
||||||
|
|
||||||
|
fn inner(&mut self) -> &mut Self::InnerConn {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
13
src/syn.rs
Normal file
13
src/syn.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use crate::error;
|
||||||
|
|
||||||
|
pub trait PersistencePool {
|
||||||
|
type Conn: ConnectionClient;
|
||||||
|
|
||||||
|
fn get_connection(&self) -> error::Result<Self::Conn>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ConnectionClient {
|
||||||
|
type InnerConn;
|
||||||
|
|
||||||
|
fn inner(&mut self) -> &mut Self::InnerConn;
|
||||||
|
}
|
Reference in a new issue