use std::cell::RefCell;
use super::slot::bound_session;
use super::{SessionState, SharedSession};
tokio::task_local! {
static CALLER: RefCell<Option<SharedSession>>;
}
pub fn scope<R>(session: Option<SharedSession>, operation: impl FnOnce() -> R) -> R {
CALLER.sync_scope(RefCell::new(session), operation)
}
pub fn current() -> Option<SharedSession> {
CALLER
.try_with(|caller| caller.borrow().clone())
.ok()
.flatten()
.or_else(bound_session)
}
pub fn with<R>(read: impl FnOnce(&SessionState) -> R) -> Option<R> {
let session = current()?;
let guard = session.try_read().ok()?;
Some(read(&guard))
}
pub fn with_for_project<R>(project_root: &str, read: impl FnOnce(&SessionState) -> R) -> Option<R> {
with(|session| belongs_to(session, project_root).then(|| read(session))).flatten()
}
pub fn with_mut<R>(write: impl FnOnce(&mut SessionState) -> R) -> Option<R> {
let session = current()?;
let mut guard = session.try_write().ok()?;
Some(write(&mut guard))
}
fn belongs_to(session: &SessionState, project_root: &str) -> bool {
let Some(own) = session.project_root.as_deref() else {
return false;
};
own == project_root
|| crate::core::project_hash::hash_project_root(own)
== crate::core::project_hash::hash_project_root(project_root)
}
#[cfg(test)]
mod tests {
use super::*;
fn shared(root: &str) -> SharedSession {
let mut session = SessionState::new();
session.project_root = Some(root.to_string());
std::sync::Arc::new(tokio::sync::RwLock::new(session))
}
#[test]
fn only_the_scoped_caller_session_of_the_project_is_own() {
assert!(with(|session| session.id.clone()).is_none());
assert!(with_mut(|session| session.id.clone()).is_none());
let session = shared("/work/p1");
let expected = session.try_read().unwrap().id.clone();
let own = scope(Some(session.clone()), || {
with_for_project("/work/p1", |s| s.id.clone())
});
assert_eq!(own, Some(expected));
assert!(
scope(Some(session), || with_for_project("/work/p2", |_| ())).is_none(),
"a caller session of another project is not this project's own"
);
}
}