use std::collections::HashMap;
use std::sync::Mutex;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApprovalDecision {
Once,
Always,
Deny,
DenyAlways,
}
pub struct ApprovalStack {
decisions: Mutex<HashMap<String, HashMap<String, ApprovalDecision>>>,
}
impl ApprovalStack {
pub fn new() -> Self {
Self {
decisions: Mutex::new(HashMap::new()),
}
}
pub fn check(&self, tool_name: &str, path: &str) -> Option<ApprovalDecision> {
let map = self.decisions.lock().unwrap();
if let Some(tool_map) = map.get(tool_name) {
if let Some(&dec) = tool_map.get(path) {
return Some(dec);
}
if let Some(&dec) = tool_map.get("*") {
return Some(dec);
}
}
None
}
pub fn record(&self, tool_name: &str, path: &str, decision: ApprovalDecision) {
let mut map = self.decisions.lock().unwrap();
map.entry(tool_name.to_string())
.or_default()
.insert(path.to_string(), decision);
}
pub fn clear(&self) {
self.decisions.lock().unwrap().clear();
}
}
impl Default for ApprovalStack {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_once_then_check() {
let stack = ApprovalStack::new();
assert!(stack.check("write_file", "src/main.rs").is_none());
stack.record("write_file", "src/main.rs", ApprovalDecision::Once);
assert_eq!(
stack.check("write_file", "src/main.rs"),
Some(ApprovalDecision::Once)
);
assert!(stack.check("write_file", "src/lib.rs").is_none());
}
#[test]
fn test_always_catch_all() {
let stack = ApprovalStack::new();
stack.record("read_file", "*", ApprovalDecision::Always);
assert_eq!(
stack.check("read_file", "any/path.rs"),
Some(ApprovalDecision::Always)
);
}
}