use parking_lot::Mutex;
use std::collections::HashMap;
use tokio::sync::oneshot;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathAccessResult {
Allowed,
Denied,
}
struct PathAccessEntry {
tool_name: String,
path: String,
#[allow(dead_code)]
mode: String,
agent_name: String,
sender: oneshot::Sender<PathAccessResult>,
}
#[derive(Default)]
pub struct PendingPathAccess {
inner: Mutex<HashMap<Uuid, PathAccessEntry>>,
}
impl PendingPathAccess {
pub fn new() -> Self {
Self::default()
}
pub fn register(
&self,
tool_name: String,
path: String,
mode: String,
agent_name: String,
) -> (Uuid, oneshot::Receiver<PathAccessResult>) {
let id = Uuid::new_v4();
let (tx, rx) = oneshot::channel();
self.inner.lock().insert(
id,
PathAccessEntry {
tool_name,
path,
mode,
agent_name,
sender: tx,
},
);
(id, rx)
}
pub fn resolve(&self, id: Uuid, result: PathAccessResult) -> Option<(String, String, String)> {
let entry = self.inner.lock().remove(&id)?;
let _ = entry.sender.send(result);
Some((entry.tool_name, entry.path, entry.agent_name))
}
pub fn path_context(&self, id: Uuid) -> Option<(String, String)> {
let guard = self.inner.lock();
let entry = guard.get(&id)?;
Some((entry.agent_name.clone(), entry.path.clone()))
}
pub fn cancel_all(&self) {
let mut guard = self.inner.lock();
for (_, entry) in guard.drain() {
let _ = entry.sender.send(PathAccessResult::Denied);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_and_resolve_round_trip() {
let registry = PendingPathAccess::new();
let (id, rx) = registry.register(
"read".to_string(),
"/secret/file".to_string(),
"read".to_string(),
"agent-1".to_string(),
);
assert_eq!(
registry.path_context(id),
Some(("agent-1".to_string(), "/secret/file".to_string()))
);
let resolved = registry.resolve(id, PathAccessResult::Allowed);
assert_eq!(
resolved,
Some((
"read".to_string(),
"/secret/file".to_string(),
"agent-1".to_string()
))
);
let result = rx.blocking_recv().expect("oneshot did not deliver");
assert_eq!(result, PathAccessResult::Allowed);
}
#[test]
fn resolve_missing_returns_none() {
let registry = PendingPathAccess::new();
let missing = Uuid::new_v4();
assert!(
registry
.resolve(missing, PathAccessResult::Denied)
.is_none()
);
}
}