feat: add postgres integration, submodule, start repo pattern
This commit is contained in:
commit
b3be701339
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "postgres"]
|
||||
path = postgres
|
||||
url = https://github.com/sfackler/rust-postgres
|
1293
Cargo.lock
generated
Normal file
1293
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
toad = "1.0.0-beta.2"
|
||||
toad-msg = "0.18.1"
|
||||
simple_logger = "4.2"
|
||||
nb = "1.1.0"
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
log = "0.4"
|
||||
postgres = {path = "./postgres/postgres"}
|
||||
rand = "0.8"
|
1
postgres
Submodule
1
postgres
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 790af54a0fdd5c487e77dc9a25d82921ee31ffe6
|
24
rustfmt.toml
Normal file
24
rustfmt.toml
Normal file
@ -0,0 +1,24 @@
|
||||
# General
|
||||
max_width = 100
|
||||
newline_style = "Unix"
|
||||
tab_spaces = 2
|
||||
indent_style = "Visual"
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_bodies = true
|
||||
|
||||
# Match statements
|
||||
match_arm_leading_pipes = "Always"
|
||||
match_block_trailing_comma = true
|
||||
|
||||
# Structs
|
||||
use_field_init_shorthand = true
|
||||
struct_field_align_threshold = 0
|
||||
|
||||
# Enums
|
||||
enum_discrim_align_threshold = 0
|
||||
|
||||
# Imports
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
imports_indent = "Visual"
|
||||
imports_layout = "HorizontalVertical"
|
13
src/app.rs
Normal file
13
src/app.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::hashed_text::{HashedTextExt, HashedTextExtImpl};
|
||||
use crate::postgres::PostgresImpl;
|
||||
use crate::user::UserRepo;
|
||||
|
||||
trait App: Send + Sync + Sized {
|
||||
type HashedTextExt: HashedTextExt;
|
||||
type UserRepo: UserRepo;
|
||||
}
|
||||
|
||||
pub struct AppConcrete {
|
||||
pg: PostgresImpl<postgres::Client>,
|
||||
hashed_text_ext: HashedTextExtImpl<PostgresImpl<postgres::Client>>,
|
||||
}
|
70
src/hashed_text.rs
Normal file
70
src/hashed_text.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use postgres::{GenericClient, GenericRow};
|
||||
|
||||
use crate::postgres::{DbError, Postgres};
|
||||
use crate::repo::Ext;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct HashedText(String);
|
||||
|
||||
pub trait HashedTextExt: Ext {
|
||||
fn matches<S: AsRef<str>>(&self, this: &HashedText, other: S) -> Result<bool, Self::Error>;
|
||||
}
|
||||
|
||||
pub struct HashedTextExtImpl<Db: Postgres>(&'static Db);
|
||||
|
||||
impl<Db> Ext for HashedTextExtImpl<Db> where Db: Postgres
|
||||
{
|
||||
type Error = DbError<Db>;
|
||||
}
|
||||
|
||||
impl<Db> HashedTextExt for HashedTextExtImpl<Db> where Db: Postgres
|
||||
{
|
||||
fn matches<S: AsRef<str>>(&self, this: &HashedText, other: S) -> Result<bool, DbError<Db>> {
|
||||
static QUERY: &'static str =
|
||||
"select public.hashed_text_matches($1, public.hashed_text_of_string($2))";
|
||||
|
||||
self.0
|
||||
.with_client(|client| client.query_one(QUERY, &[&other.as_ref(), &this.0.as_str()]))
|
||||
.and_then(|row| row.try_get(0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{any::Any, panic::AssertUnwindSafe};
|
||||
|
||||
use postgres::types::{Type, ToSql, private::BytesMut, FromSql};
|
||||
|
||||
use crate::{postgres::{test::{Client, Row}, PostgresImpl, Postgres}};
|
||||
|
||||
use super::{HashedTextExtImpl, HashedTextExt, HashedText};
|
||||
|
||||
#[test]
|
||||
fn hashed_text_matches_fn_call() {
|
||||
let client = || Client {
|
||||
query_one: Box::new(|_, q, ps| {
|
||||
assert_eq!(q.unwrap_str(), "select public.hashed_text_matches($1, public.hashed_text_of_string($2))");
|
||||
|
||||
let mut p0 = BytesMut::with_capacity(32);
|
||||
let mut p1 = BytesMut::with_capacity(32);
|
||||
|
||||
ps[0].to_sql(&Type::TEXT, &mut p0).unwrap();
|
||||
ps[1].to_sql(&Type::TEXT, &mut p1).unwrap();
|
||||
|
||||
let p0 = <&str as FromSql>::from_sql(&Type::TEXT, &p0).unwrap();
|
||||
let p1 = <&str as FromSql>::from_sql(&Type::TEXT, &p1).unwrap();
|
||||
|
||||
assert_eq!(p1, "XXX");
|
||||
|
||||
Ok(Row::new(vec![("", Type::BOOL)]).value(Type::BOOL, p0 == "foo"))
|
||||
}),
|
||||
..Client::default()
|
||||
};
|
||||
|
||||
let pg = PostgresImpl::<Client<()>>::try_new(|| Ok(client()), 1).unwrap();
|
||||
let htext = HashedTextExtImpl(unsafe {std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&pg)});
|
||||
|
||||
assert!(htext.matches(&HashedText(String::from("XXX")), "foo").unwrap());
|
||||
assert!(!htext.matches(&HashedText(String::from("XXX")), "foob").unwrap());
|
||||
}
|
||||
}
|
73
src/main.rs
Normal file
73
src/main.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use hashed_text::HashedTextExt;
|
||||
use toad::config::Config;
|
||||
use toad::net::Addrd;
|
||||
use toad::platform::Platform as _;
|
||||
use toad::req::Req;
|
||||
use toad::resp::Resp;
|
||||
use toad::std::dtls;
|
||||
|
||||
mod app;
|
||||
mod hashed_text;
|
||||
mod postgres;
|
||||
mod repo;
|
||||
mod user;
|
||||
use repo::Repo;
|
||||
use user::UserRepo;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct Email(String);
|
||||
|
||||
type Dtls = toad::std::dtls::N;
|
||||
type ToadT = toad::std::PlatformTypes<Dtls>;
|
||||
type Toad = toad::std::Platform<Dtls, toad::step::runtime::std::Runtime<Dtls>>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct Foo {
|
||||
id: String,
|
||||
}
|
||||
|
||||
fn handle_request(req: Addrd<Req<ToadT>>) -> Result<Addrd<Resp<ToadT>>, String> {
|
||||
let path = req.data()
|
||||
.path()
|
||||
.map_err(|e| format!("{e:?}"))
|
||||
.unwrap_or(None)
|
||||
.unwrap_or("");
|
||||
let mut path_segments = path.split("/").peekable();
|
||||
|
||||
if path_segments.peek() == Some(&"users") {
|
||||
let mut path_segments = path_segments.clone();
|
||||
path_segments.next();
|
||||
let id = path_segments.next();
|
||||
}
|
||||
|
||||
Ok(req.map(|r| Resp::for_request(&r).unwrap()))
|
||||
}
|
||||
|
||||
fn server_worker(p: &'static Toad) {
|
||||
loop {
|
||||
match nb::block!(p.poll_req()) {
|
||||
| Err(e) => log::error!("{e:?}"),
|
||||
| Ok(req) => match handle_request(req) {
|
||||
| Err(e) => log::error!("{e:?}"),
|
||||
| Ok(rep) => {
|
||||
nb::block!(p.send_msg(rep.clone().map(Into::into))).map_err(|e| log::error!("{e:?}"))
|
||||
.ok();
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
|
||||
static mut TOAD: Option<Toad> = None;
|
||||
|
||||
unsafe {
|
||||
TOAD = Some(Toad::try_new("0.0.0.0:4444", Config::default()).unwrap());
|
||||
}
|
||||
|
||||
let r = unsafe { TOAD.as_ref().unwrap() };
|
||||
|
||||
server_worker(r);
|
||||
}
|
410
src/postgres.rs
Normal file
410
src/postgres.rs
Normal file
@ -0,0 +1,410 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Mutex, MutexGuard, TryLockError};
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
pub type DbError<Pg> = <<Pg as Postgres>::Client as postgres::GenericClient>::Error;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ConnectionParams {
|
||||
pub host: String,
|
||||
pub port: usize,
|
||||
pub user: String,
|
||||
pub pass: String,
|
||||
}
|
||||
|
||||
pub trait Postgres
|
||||
where Self: Send + Sync + Sized + 'static
|
||||
{
|
||||
type Client: postgres::GenericClient;
|
||||
|
||||
fn try_new<F>(connect: F, pool_size: usize) -> Result<Self, DbError<Self>>
|
||||
where F: Fn() -> Result<Self::Client, DbError<Self>>;
|
||||
|
||||
fn with_client<F, R>(&self, f: F) -> Result<R, DbError<Self>>
|
||||
where F: FnOnce(&mut Self::Client) -> Result<R, DbError<Self>>;
|
||||
}
|
||||
|
||||
pub struct PostgresImpl<C> {
|
||||
size: usize,
|
||||
pool: Pin<Box<Vec<Mutex<C>>>>,
|
||||
}
|
||||
|
||||
impl<C> PostgresImpl<C> {
|
||||
fn unused_lock(&self) -> Option<MutexGuard<C>> {
|
||||
self.pool.iter().find_map(|m| m.try_lock().ok())
|
||||
}
|
||||
|
||||
fn block_for_next(&self) -> MutexGuard<C> {
|
||||
let ix = rand::thread_rng().gen_range(0..self.size);
|
||||
self.pool[ix].lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Postgres for PostgresImpl<C>
|
||||
where Self: Send + Sync,
|
||||
C: 'static + postgres::GenericClient
|
||||
{
|
||||
type Client = C;
|
||||
|
||||
fn try_new<F>(connect: F, pool_size: usize) -> Result<Self, C::Error>
|
||||
where F: Fn() -> Result<C, C::Error>
|
||||
{
|
||||
let mut pool = Vec::new();
|
||||
|
||||
if pool_size == 0 {
|
||||
panic!("pool size must be > 0");
|
||||
}
|
||||
|
||||
(0..pool_size).try_for_each(|_| {
|
||||
// let c = postgres::Client::connect(&format!("user={user} password='{pass}' host={host} port={port}"), postgres::NoTls)?;
|
||||
let c = connect()?;
|
||||
pool.push(Mutex::new(c));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(PostgresImpl { size: pool_size,
|
||||
pool: Box::pin(pool) })
|
||||
}
|
||||
|
||||
fn with_client<F, R>(&self, f: F) -> Result<R, C::Error>
|
||||
where F: FnOnce(&mut Self::Client) -> Result<R, C::Error>
|
||||
{
|
||||
match self.unused_lock() {
|
||||
| Some(mut lock) => f(lock.deref_mut()),
|
||||
| None => f(self.block_for_next().deref_mut()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub mod client_function {
|
||||
use postgres::StatementOrString;
|
||||
|
||||
use super::Row;
|
||||
|
||||
pub type Params<'a> = &'a [&'a (dyn postgres::types::ToSql + Sync)];
|
||||
|
||||
pub trait Execute<C, E>:
|
||||
Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<u64, E>
|
||||
{
|
||||
}
|
||||
pub trait BatchExecute<C, E>: Fn(&mut C, &str) -> Result<(), E> {}
|
||||
pub trait Query<C, E>:
|
||||
Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<Vec<Row<E>>, E>
|
||||
{
|
||||
}
|
||||
pub trait QueryOne<C, E>:
|
||||
Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<Row<E>, E>
|
||||
{
|
||||
}
|
||||
pub trait QueryOpt<C, E>:
|
||||
Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<Option<Row<E>>, E>
|
||||
{
|
||||
}
|
||||
pub trait SimpleQuery<C, E>:
|
||||
Fn(&mut C, &str) -> Result<Vec<postgres::SimpleQueryMessage>, E>
|
||||
{
|
||||
}
|
||||
pub trait CommitOrRollback<C, E>: Fn(C) -> Result<(), E> {}
|
||||
pub trait Transaction<C, E>: Fn(&C) -> Result<C, E> {}
|
||||
|
||||
impl<T, C, E> Execute<C, E> for T
|
||||
where T: Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<u64, E>
|
||||
{
|
||||
}
|
||||
impl<T, C, E> BatchExecute<C, E> for T where T: Fn(&mut C, &str) -> Result<(), E> {}
|
||||
impl<T, C, E> Query<C, E> for T
|
||||
where T: Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<Vec<Row<E>>, E>
|
||||
{
|
||||
}
|
||||
impl<T, C, E> QueryOne<C, E> for T
|
||||
where T: Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<Row<E>, E>
|
||||
{
|
||||
}
|
||||
impl<T, C, E> QueryOpt<C, E> for T
|
||||
where T: Fn(&mut C, StatementOrString<'_>, Params<'_>) -> Result<Option<Row<E>>, E>
|
||||
{
|
||||
}
|
||||
impl<T, C, E> SimpleQuery<C, E> for T
|
||||
where T: Fn(&mut C, &str) -> Result<Vec<postgres::SimpleQueryMessage>, E>
|
||||
{
|
||||
}
|
||||
impl<T, C, E> CommitOrRollback<C, E> for T where T: Fn(C) -> Result<(), E> {}
|
||||
impl<T, C, E> Transaction<C, E> for T where T: Fn(&C) -> Result<C, E> {}
|
||||
}
|
||||
|
||||
use client_function::{BatchExecute,
|
||||
CommitOrRollback,
|
||||
Execute,
|
||||
Query,
|
||||
QueryOne,
|
||||
QueryOpt,
|
||||
SimpleQuery,
|
||||
Transaction};
|
||||
use postgres::types::private::BytesMut;
|
||||
use postgres::types::{FromSql, ToSql, Type};
|
||||
use postgres::{Column, GenericRow, GenericClient};
|
||||
|
||||
use super::{Postgres, PostgresImpl};
|
||||
|
||||
pub struct Row<E> {
|
||||
pub columns: Vec<Column>,
|
||||
pub values: Vec<BytesMut>,
|
||||
__phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> Row<E> {
|
||||
pub fn new(cols: Vec<(&'static str, Type)>) -> Self {
|
||||
Self {
|
||||
columns: cols.into_iter().map(|(name, ty)| Column::new(name.to_string(), ty)).collect(),
|
||||
values: vec![],
|
||||
__phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value<V>(mut self, ty: Type, val: V) -> Self where V: ToSql {
|
||||
let mut bs = BytesMut::with_capacity(128);
|
||||
val.to_sql(&ty, &mut bs).unwrap();
|
||||
self.values.push(bs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> GenericRow for Row<E> where E: core::fmt::Debug {
|
||||
type Error = E;
|
||||
|
||||
fn columns(&self) -> &[Column] {
|
||||
&self.columns
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
|
||||
fn get<'a, I, T>(&'a self, idx: I) -> T
|
||||
where I: postgres::row::RowIndex + core::fmt::Display,
|
||||
T: FromSql<'a>
|
||||
{
|
||||
let ix = idx.__idx(self.columns()).unwrap();
|
||||
let ty = self.columns()[ix].type_();
|
||||
T::from_sql(ty, &self.values[ix]).unwrap()
|
||||
}
|
||||
|
||||
fn try_get<'a, I, T>(&'a self, idx: I) -> Result<T, E>
|
||||
where I: postgres::row::RowIndex + core::fmt::Display,
|
||||
T: FromSql<'a>
|
||||
{
|
||||
let ix = idx.__idx(self.columns()).unwrap();
|
||||
let ty = self.columns()[ix].type_();
|
||||
Ok(T::from_sql(ty, &self.values[ix]).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct Client<E> {
|
||||
pub __phantom: PhantomData<E>,
|
||||
pub execute: Box<dyn Execute<Self, E>>,
|
||||
pub query: Box<dyn Query<Self, E>>,
|
||||
pub query_one: Box<dyn QueryOne<Self, E>>,
|
||||
pub query_opt: Box<dyn QueryOpt<Self, E>>,
|
||||
pub simple_query: Box<dyn SimpleQuery<Self, E>>,
|
||||
pub batch_execute: Box<dyn BatchExecute<Self, E>>,
|
||||
pub commit: Box<dyn CommitOrRollback<Self, E>>,
|
||||
pub rollback: Box<dyn CommitOrRollback<Self, E>>,
|
||||
pub transaction: Box<dyn Transaction<Self, E>>,
|
||||
}
|
||||
|
||||
unsafe impl<E> Send for Client<E> {}
|
||||
unsafe impl<E> Sync for Client<E> {}
|
||||
|
||||
impl<E> Default for Client<E> where E: core::fmt::Debug
|
||||
{
|
||||
fn default() -> Self {
|
||||
Client { __phantom: PhantomData,
|
||||
execute: Box::new(|_, _, _| panic!("execute not implemented")),
|
||||
query: Box::new(|_, _, _| panic!("query not implemented")),
|
||||
query_one: Box::new(|_, _, _| panic!("query_one not implemented")),
|
||||
query_opt: Box::new(|_, _, _| panic!("query_opt not implemented")),
|
||||
simple_query: Box::new(|_, _| panic!("simple_query not implemented")),
|
||||
batch_execute: Box::new(|_, _| panic!("batch_execute not implemented")),
|
||||
commit: Box::new(|_| panic!("commit not implemented")),
|
||||
rollback: Box::new(|_| panic!("rollback not implemented")),
|
||||
transaction: Box::new(|_| panic!("transaction not implemented")) }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! common {
|
||||
() => {
|
||||
fn execute<T>(&mut self,
|
||||
query: &T,
|
||||
params: &[&(dyn postgres::types::ToSql + Sync)])
|
||||
-> Result<u64, Self::Error>
|
||||
where T: ?Sized + postgres::ToStatement
|
||||
{
|
||||
let mut f = Client::<E>::default().execute;
|
||||
std::mem::swap(&mut self.execute, &mut f);
|
||||
let res = f(self, query.__convert(), params);
|
||||
std::mem::swap(&mut self.execute, &mut f);
|
||||
res
|
||||
}
|
||||
|
||||
fn query<T>(&mut self,
|
||||
query: &T,
|
||||
params: &[&(dyn postgres::types::ToSql + Sync)])
|
||||
-> Result<Vec<Row<E>>, Self::Error>
|
||||
where T: ?Sized + postgres::ToStatement
|
||||
{
|
||||
let mut f = Client::<E>::default().query;
|
||||
std::mem::swap(&mut self.query, &mut f);
|
||||
let res = f(self, query.__convert(), params);
|
||||
std::mem::swap(&mut self.query, &mut f);
|
||||
res
|
||||
}
|
||||
|
||||
fn query_one<T>(&mut self,
|
||||
query: &T,
|
||||
params: &[&(dyn postgres::types::ToSql + Sync)])
|
||||
-> Result<Row<E>, Self::Error>
|
||||
where T: ?Sized + postgres::ToStatement
|
||||
{
|
||||
let mut f = Client::<E>::default().query_one;
|
||||
std::mem::swap(&mut self.query_one, &mut f);
|
||||
let res = f(self, query.__convert(), params);
|
||||
std::mem::swap(&mut self.query_one, &mut f);
|
||||
res
|
||||
}
|
||||
|
||||
fn query_opt<T>(&mut self,
|
||||
query: &T,
|
||||
params: &[&(dyn postgres::types::ToSql + Sync)])
|
||||
-> Result<Option<Row<E>>, Self::Error>
|
||||
where T: ?Sized + postgres::ToStatement
|
||||
{
|
||||
let mut f = Client::<E>::default().query_opt;
|
||||
std::mem::swap(&mut self.query_opt, &mut f);
|
||||
let res = f(self, query.__convert(), params);
|
||||
std::mem::swap(&mut self.query_opt, &mut f);
|
||||
res
|
||||
}
|
||||
|
||||
fn query_raw<T, P, I>(&mut self,
|
||||
_: &T,
|
||||
_: I)
|
||||
-> Result<postgres::RowIter<'_>, Self::Error>
|
||||
where T: ?Sized + postgres::ToStatement,
|
||||
P: postgres::types::BorrowToSql,
|
||||
I: IntoIterator<Item = P>,
|
||||
I::IntoIter: ExactSizeIterator
|
||||
{
|
||||
panic!("query_raw cannot be mocked")
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _: &str) -> Result<postgres::Statement, Self::Error> {
|
||||
panic!("prepared statements cannot be mocked")
|
||||
}
|
||||
|
||||
fn prepare_typed(&mut self,
|
||||
_: &str,
|
||||
_: &[postgres::types::Type])
|
||||
-> Result<postgres::Statement, Self::Error> {
|
||||
panic!("prepared statements cannot be mocked")
|
||||
}
|
||||
|
||||
fn copy_in<T>(&mut self, _: &T) -> Result<postgres::CopyInWriter<'_>, Self::Error>
|
||||
where T: ?Sized + postgres::ToStatement
|
||||
{
|
||||
panic!("copy_in cannot be mocked")
|
||||
}
|
||||
|
||||
fn copy_out<T>(&mut self, _: &T) -> Result<postgres::CopyOutReader<'_>, Self::Error>
|
||||
where T: ?Sized + postgres::ToStatement
|
||||
{
|
||||
panic!("copy_out cannot be mocked")
|
||||
}
|
||||
|
||||
fn simple_query(&mut self,
|
||||
query: &str)
|
||||
-> Result<Vec<postgres::SimpleQueryMessage>, Self::Error> {
|
||||
let mut f = Client::<E>::default().simple_query;
|
||||
std::mem::swap(&mut self.simple_query, &mut f);
|
||||
let res = f(self, query);
|
||||
std::mem::swap(&mut self.simple_query, &mut f);
|
||||
res
|
||||
}
|
||||
|
||||
fn batch_execute(&mut self, query: &str) -> Result<(), Self::Error> {
|
||||
let mut f = Client::<E>::default().batch_execute;
|
||||
std::mem::swap(&mut self.batch_execute, &mut f);
|
||||
let res = f(self, query);
|
||||
std::mem::swap(&mut self.batch_execute, &mut f);
|
||||
res
|
||||
}
|
||||
|
||||
fn transaction(&mut self) -> Result<Client<E>, Self::Error> {
|
||||
let mut f = Client::<E>::default().transaction;
|
||||
std::mem::swap(&mut self.transaction, &mut f);
|
||||
let res = f(self);
|
||||
std::mem::swap(&mut self.transaction, &mut f);
|
||||
res
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a, E> postgres::GenericTransaction<'a> for Client<E> where E: core::fmt::Debug
|
||||
{
|
||||
type Error = E;
|
||||
type NestedTransaction<'b> = Client<E> where Client<E>: 'b;
|
||||
type Row = Row<E>;
|
||||
|
||||
common!();
|
||||
|
||||
fn commit(mut self) -> Result<(), E> {
|
||||
let mut f = Client::<E>::default().commit;
|
||||
std::mem::swap(&mut self.commit, &mut f);
|
||||
let res = f(self);
|
||||
res
|
||||
}
|
||||
|
||||
fn rollback(mut self) -> Result<(), E> {
|
||||
let mut f = Client::<E>::default().rollback;
|
||||
std::mem::swap(&mut self.commit, &mut f);
|
||||
let res = f(self);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> postgres::GenericClient for Client<E> where E: core::fmt::Debug
|
||||
{
|
||||
type Error = E;
|
||||
type Transaction<'a> = Client<E> where Client<E>: 'a;
|
||||
type Row = Row<E>;
|
||||
|
||||
common!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_impl_pooling() {
|
||||
let client = || Client { query_one: Box::new(|_, _, _| {
|
||||
Ok(Row::new(vec![("foo", Type::TEXT)]).value(Type::TEXT, "bar"))
|
||||
}),
|
||||
..Client::default() };
|
||||
|
||||
let pg = PostgresImpl::<Client<()>>::try_new(|| Ok(client()), 2).unwrap();
|
||||
|
||||
// try_lock locks the first available and returns None when all are locked
|
||||
let a = pg.unused_lock().expect("there are unlocked clients");
|
||||
let _b = pg.unused_lock().expect("there are unlocked clients");
|
||||
assert!(pg.unused_lock().is_none());
|
||||
drop(a);
|
||||
assert!(pg.unused_lock().is_some());
|
||||
|
||||
// with_client does not block when there are available clients
|
||||
let row = pg.with_client(|c| c.query_one("", &[])).unwrap();
|
||||
assert_eq!(row.get::<_, String>("foo"), String::from("bar"));
|
||||
}
|
||||
}
|
22
src/repo.rs
Normal file
22
src/repo.rs
Normal file
@ -0,0 +1,22 @@
|
||||
pub trait Repo: Send + Sync {
|
||||
type T;
|
||||
type TPut;
|
||||
type Error: core::fmt::Debug;
|
||||
type Id: AsRef<str>;
|
||||
|
||||
fn get(&self, id: Self::Id) -> Result<Option<Self::T>, Self::Error>;
|
||||
fn get_all(&self) -> Result<Vec<Self::T>, Self::Error>;
|
||||
fn put(&self, id: Self::Id, state: Self::TPut) -> Result<(), Self::Error>;
|
||||
fn del(&self, id: Self::Id) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// An entity that has some operations which rely on
|
||||
/// an external service
|
||||
pub trait Ext: Send + Sync {
|
||||
type Error: core::fmt::Debug;
|
||||
|
||||
/// Preparing statements would go here
|
||||
fn init(&self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
25
src/user.rs
Normal file
25
src/user.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use crate::repo::Repo;
|
||||
use crate::{Email, hashed_text::HashedText};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct UserId(String);
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct UserTag(String);
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct User {
|
||||
uid: UserId,
|
||||
tag: UserTag,
|
||||
password: HashedText,
|
||||
email: Email,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct UserPut {
|
||||
tag: UserTag,
|
||||
password: HashedText,
|
||||
email: Email,
|
||||
}
|
||||
|
||||
pub trait UserRepo: Repo<T = User, TPut = UserPut, Id = UserId> {}
|
Loading…
Reference in New Issue
Block a user