lean_ctx/core/session/
caller.rs1use std::cell::RefCell;
15
16use super::slot::bound_session;
17use super::{SessionState, SharedSession};
18
19tokio::task_local! {
20 static CALLER: RefCell<Option<SharedSession>>;
23}
24
25pub fn scope<R>(session: Option<SharedSession>, operation: impl FnOnce() -> R) -> R {
27 CALLER.sync_scope(RefCell::new(session), operation)
28}
29
30pub fn current() -> Option<SharedSession> {
33 CALLER
34 .try_with(|caller| caller.borrow().clone())
35 .ok()
36 .flatten()
37 .or_else(bound_session)
38}
39
40pub fn with<R>(read: impl FnOnce(&SessionState) -> R) -> Option<R> {
43 let session = current()?;
44 let guard = session.try_read().ok()?;
45 Some(read(&guard))
46}
47
48pub fn with_for_project<R>(project_root: &str, read: impl FnOnce(&SessionState) -> R) -> Option<R> {
50 with(|session| belongs_to(session, project_root).then(|| read(session))).flatten()
51}
52
53pub fn with_mut<R>(write: impl FnOnce(&mut SessionState) -> R) -> Option<R> {
56 let session = current()?;
57 let mut guard = session.try_write().ok()?;
58 Some(write(&mut guard))
59}
60
61fn belongs_to(session: &SessionState, project_root: &str) -> bool {
62 let Some(own) = session.project_root.as_deref() else {
63 return false;
64 };
65 own == project_root
66 || crate::core::project_hash::hash_project_root(own)
67 == crate::core::project_hash::hash_project_root(project_root)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 fn shared(root: &str) -> SharedSession {
75 let mut session = SessionState::new();
76 session.project_root = Some(root.to_string());
77 std::sync::Arc::new(tokio::sync::RwLock::new(session))
78 }
79
80 #[test]
84 fn only_the_scoped_caller_session_of_the_project_is_own() {
85 assert!(with(|session| session.id.clone()).is_none());
86 assert!(with_mut(|session| session.id.clone()).is_none());
87
88 let session = shared("/work/p1");
89 let expected = session.try_read().unwrap().id.clone();
90 let own = scope(Some(session.clone()), || {
91 with_for_project("/work/p1", |s| s.id.clone())
92 });
93 assert_eq!(own, Some(expected));
94 assert!(
95 scope(Some(session), || with_for_project("/work/p2", |_| ())).is_none(),
96 "a caller session of another project is not this project's own"
97 );
98 }
99}