use crate::auth::{CallError, CheckResult};
use crate::{CheckClient, ListResult, Namespace, Obj, Rel, Timestamp, UserId};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tokio::sync::OnceCell;
type CheckKey = (String, String, String, UserId);
type ListKey = (String, String, UserId);
pub type MemoObserveFn = Arc<dyn Fn(&str, bool) + Send + Sync>;
pub struct RequestMemo {
client: CheckClient,
ts: Option<Timestamp>,
checks: Mutex<HashMap<CheckKey, Arc<OnceCell<CheckResult>>>>,
lists: Mutex<HashMap<ListKey, Arc<OnceCell<ListResult>>>>,
observe: Option<MemoObserveFn>,
}
impl RequestMemo {
pub fn new(client: CheckClient) -> Self {
RequestMemo {
client,
ts: None,
checks: Mutex::new(HashMap::new()),
lists: Mutex::new(HashMap::new()),
observe: None,
}
}
pub fn with_timestamp(mut self, ts: Option<Timestamp>) -> Self {
self.ts = ts;
self
}
pub fn with_observer(mut self, f: MemoObserveFn) -> Self {
self.observe = Some(f);
self
}
fn report(&self, op: &str, hit: bool) {
if let Some(observe) = &self.observe {
observe(op, hit);
}
}
pub async fn check(
&self,
ns: Namespace,
obj: Obj,
rel: Rel,
user_id: UserId,
) -> Result<CheckResult, CallError> {
let key = (ns.0.clone(), obj.0.clone(), rel.0.clone(), user_id);
let cell = {
let mut map = self.checks.lock().expect("memo mutex poisoned");
map.entry(key).or_default().clone()
};
self.report("check", cell.initialized());
let result = cell
.get_or_try_init(|| {
let mut client = self.client.clone();
let ts = self.ts.clone();
async move { client.check(ns, obj, rel, user_id, ts).await }
})
.await?;
Ok(result.clone())
}
pub async fn list(
&self,
ns: Namespace,
rel: Rel,
user_id: UserId,
) -> Result<ListResult, CallError> {
let key = (ns.0.clone(), rel.0.clone(), user_id);
let cell = {
let mut map = self.lists.lock().expect("memo mutex poisoned");
map.entry(key).or_default().clone()
};
self.report("list", cell.initialized());
let result = cell
.get_or_try_init(|| {
let mut client = self.client.clone();
let ts = self.ts.clone();
async move { client.list(ns, rel, user_id, ts).await }
})
.await?;
Ok(result.clone())
}
}