use std::future::Future;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::PoisonError;
use leptoskit_db::Context;
use leptoskit_db::Db;
use leptoskit_db::Id;
use leptoskit_db::ReadEntry;
use leptoskit_db::Tracked;
use leptoskit_db::Tx;
use leptoskit_query::BoxFuture;
use super::Identity;
use super::OperationContext;
use super::sealed::Sealed;
use crate::Error;
use crate::Operation;
pub struct QueryCx {
db: Tracked<Db>,
identity: Identity,
}
impl QueryCx {
pub fn new(db: Db, identity: Identity) -> Self {
Self {
db: Tracked::new(db),
identity,
}
}
pub fn db(&self) -> &Tracked<Db> {
&self.db
}
pub fn identity(&self) -> &Identity {
&self.identity
}
pub fn context(&self) -> &Context {
&self.identity.context
}
pub fn get<G: 'static>(&self) -> Option<&G> {
self.identity.context.get()
}
pub fn require<G: 'static>(&self) -> Result<&G, Error> {
require(&self.identity.context)
}
pub fn signed_in(&self) -> bool {
self.identity.context.actor().is_some()
}
pub fn user_id<U>(&self) -> Option<Id<U>> {
self.identity.context.actor().map(|actor| actor.id())
}
pub fn require_user<U>(&self) -> Result<Id<U>, Error> {
self.user_id()
.ok_or_else(|| Error::unauthorized("sign in to do this"))
}
#[doc(hidden)]
pub fn system(&self, reason: &'static str) -> Db {
self.db.connection().system(reason)
}
pub fn reads(&self) -> Vec<ReadEntry> {
self.db.reads()
}
pub fn into_reads(self) -> Vec<ReadEntry> {
self.db.into_reads()
}
}
#[derive(Clone, Debug, Default)]
pub struct ReadSink(Arc<Mutex<Vec<ReadEntry>>>);
impl ReadSink {
pub fn new() -> Self {
Self::default()
}
pub fn take(&self) -> Vec<ReadEntry> {
std::mem::take(&mut *self.0.lock().unwrap_or_else(PoisonError::into_inner))
}
fn extend(&self, reads: Vec<ReadEntry>) {
self.0
.lock()
.unwrap_or_else(PoisonError::into_inner)
.extend(reads);
}
}
impl std::fmt::Debug for QueryCx {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QueryCx")
.field("identity", &self.identity)
.finish_non_exhaustive()
}
}
type AfterCommit = BoxFuture<'static, ()>;
pub struct MutationCx {
tx: Tx,
identity: Identity,
attempt: u32,
after_commit: Mutex<Vec<AfterCommit>>,
}
impl MutationCx {
pub(crate) fn new(tx: Tx, identity: Identity, attempt: u32) -> Self {
Self {
tx,
identity,
attempt,
after_commit: Mutex::new(Vec::new()),
}
}
pub fn tx(&self) -> &Tx {
&self.tx
}
pub fn identity(&self) -> &Identity {
&self.identity
}
pub fn context(&self) -> &Context {
&self.identity.context
}
pub fn enqueue<J: crate::Job>(&self, job: J) -> crate::server::jobs::Enqueue<'_, J> {
crate::server::jobs::Enqueue::in_tx(&self.tx, job, self.identity.context.actor())
}
pub fn get<G: 'static>(&self) -> Option<&G> {
self.identity.context.get()
}
pub fn require<G: 'static>(&self) -> Result<&G, Error> {
require(&self.identity.context)
}
pub fn signed_in(&self) -> bool {
self.identity.context.actor().is_some()
}
pub fn user_id<U>(&self) -> Option<Id<U>> {
self.identity.context.actor().map(|actor| actor.id())
}
pub fn require_user<U>(&self) -> Result<Id<U>, Error> {
self.user_id()
.ok_or_else(|| Error::unauthorized("sign in to do this"))
}
pub fn attempt(&self) -> u32 {
self.attempt
}
pub fn after_commit(&self, work: impl Future<Output = ()> + Send + 'static) {
self.after_commit
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(Box::pin(work));
}
pub(crate) fn into_parts(self) -> (Tx, Vec<AfterCommit>) {
(
self.tx,
self.after_commit
.into_inner()
.unwrap_or_else(PoisonError::into_inner),
)
}
}
impl std::fmt::Debug for MutationCx {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MutationCx")
.field("identity", &self.identity)
.field("attempt", &self.attempt)
.finish_non_exhaustive()
}
}
fn require<G: 'static>(context: &Context) -> Result<&G, Error> {
context
.get()
.ok_or_else(|| Error::unauthorized("sign in to do this"))
}
impl Sealed for QueryCx {}
impl Sealed for MutationCx {}
impl OperationContext for crate::JobCx {
fn dispatch<O: Operation<Context = Self>>(
db: &Db,
identity: Identity,
_request_id: Option<&str>,
op: O,
) -> impl Future<Output = crate::Result<O::Output>> + Send {
let cx = crate::JobCx::new(db.clone(), identity, String::new(), 0);
async move { op.run(&cx).await }
}
}
impl OperationContext for QueryCx {
fn dispatch<O: Operation<Context = Self>>(
db: &Db,
identity: Identity,
request_id: Option<&str>,
op: O,
) -> impl Future<Output = crate::Result<O::Output>> + Send {
Self::dispatch_recording(db, identity, request_id, None, op)
}
async fn dispatch_recording<O: Operation<Context = Self>>(
db: &Db,
identity: Identity,
_request_id: Option<&str>,
reads: Option<ReadSink>,
op: O,
) -> crate::Result<O::Output> {
let cx = Self::new(db.clone(), identity);
let output = op.run(&cx).await;
if let Some(reads) = reads {
reads.extend(cx.into_reads());
}
output
}
}
impl OperationContext for MutationCx {
async fn dispatch<O: Operation<Context = Self>>(
db: &Db,
identity: Identity,
request_id: Option<&str>,
op: O,
) -> crate::Result<O::Output> {
super::run::mutate(db, identity, request_id, op).await
}
}