use super::approval::{ApprovalRequest, ApprovalResult, ApprovalStatus, RiskLevel};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::Duration;
pub trait ApprovalHandler: Send + Sync {
fn request_approval(&self, request: ApprovalRequest) -> ApprovalResult;
fn supports_async(&self) -> bool {
false
}
}
#[derive(Debug, Clone, Default)]
pub struct AutoApprovalHandler {
approver_name: Option<String>,
}
impl AutoApprovalHandler {
pub fn new() -> Self {
Self::default()
}
pub fn with_approver(mut self, name: impl Into<String>) -> Self {
self.approver_name = Some(name.into());
self
}
}
impl ApprovalHandler for AutoApprovalHandler {
fn request_approval(&self, request: ApprovalRequest) -> ApprovalResult {
let mut result = ApprovalResult::approved(&request.id);
if let Some(name) = &self.approver_name {
result = result.approver(name);
} else {
result = result.approver("auto");
}
result.metadata("auto_approved", "true")
}
}
#[derive(Debug, Clone, Default)]
pub struct DenyAllHandler {
reason: String,
}
impl DenyAllHandler {
pub fn new() -> Self {
Self {
reason: "Denied by policy".to_string(),
}
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = reason.into();
self
}
}
impl ApprovalHandler for DenyAllHandler {
fn request_approval(&self, request: ApprovalRequest) -> ApprovalResult {
ApprovalResult::denied(&request.id, &self.reason)
}
}
#[derive(Debug, Clone)]
pub struct TimeoutApprovalHandler {
timeout: Duration,
}
impl TimeoutApprovalHandler {
pub fn new(timeout: Duration) -> Self {
Self { timeout }
}
pub fn with_secs(secs: u64) -> Self {
Self::new(Duration::from_secs(secs))
}
}
impl ApprovalHandler for TimeoutApprovalHandler {
fn request_approval(&self, request: ApprovalRequest) -> ApprovalResult {
std::thread::sleep(self.timeout);
ApprovalResult::timed_out(&request.id)
}
}
#[derive(Debug, Clone)]
pub struct QueuedRequest {
pub request: ApprovalRequest,
#[allow(dead_code)]
pub result: Option<ApprovalResult>,
}
#[derive(Debug, Clone)]
pub struct QueueApprovalHandler {
queue: Arc<Mutex<VecDeque<QueuedRequest>>>,
results: Arc<Mutex<std::collections::HashMap<String, ApprovalResult>>>,
default_on_timeout: bool,
}
impl Default for QueueApprovalHandler {
fn default() -> Self {
Self::new()
}
}
impl QueueApprovalHandler {
pub fn new() -> Self {
Self {
queue: Arc::new(Mutex::new(VecDeque::new())),
results: Arc::new(Mutex::new(std::collections::HashMap::new())),
default_on_timeout: false,
}
}
pub fn default_on_timeout(mut self, approve: bool) -> Self {
self.default_on_timeout = approve;
self
}
pub fn pending_count(&self) -> usize {
self.queue.lock().unwrap().len()
}
pub fn pending_requests(&self) -> Vec<ApprovalRequest> {
self.queue
.lock()
.unwrap()
.iter()
.map(|q| q.request.clone())
.collect()
}
pub fn next_request(&self) -> Option<ApprovalRequest> {
self.queue
.lock()
.unwrap()
.front()
.map(|q| q.request.clone())
}
pub fn approve(&self, request_id: &str, approver: Option<&str>) -> bool {
self.decide(request_id, true, None, approver)
}
pub fn deny(&self, request_id: &str, reason: &str, approver: Option<&str>) -> bool {
self.decide(request_id, false, Some(reason), approver)
}
fn decide(
&self,
request_id: &str,
approved: bool,
reason: Option<&str>,
approver: Option<&str>,
) -> bool {
let mut queue = self.queue.lock().unwrap();
let pos = queue.iter().position(|q| q.request.id == request_id);
if let Some(idx) = pos {
queue.remove(idx);
let mut result = if approved {
ApprovalResult::approved(request_id)
} else {
ApprovalResult::denied(request_id, reason.unwrap_or("Denied"))
};
if let Some(name) = approver {
result = result.approver(name);
}
self.results
.lock()
.unwrap()
.insert(request_id.to_string(), result);
true
} else {
false
}
}
pub fn get_result(&self, request_id: &str) -> Option<ApprovalResult> {
self.results.lock().unwrap().get(request_id).cloned()
}
pub fn clear(&self) {
self.queue.lock().unwrap().clear();
self.results.lock().unwrap().clear();
}
}
impl ApprovalHandler for QueueApprovalHandler {
fn request_approval(&self, request: ApprovalRequest) -> ApprovalResult {
let request_id = request.id.clone();
self.queue.lock().unwrap().push_back(QueuedRequest {
request: request.clone(),
result: None,
});
if let Some(timeout_secs) = request.timeout_secs {
let start = std::time::Instant::now();
let timeout = Duration::from_secs(timeout_secs);
loop {
if let Some(result) = self.get_result(&request_id) {
return result;
}
if start.elapsed() > timeout {
self.queue
.lock()
.unwrap()
.retain(|q| q.request.id != request_id);
return if self.default_on_timeout {
ApprovalResult::approved(&request_id).metadata("timeout_default", "true")
} else {
ApprovalResult::timed_out(&request_id)
};
}
std::thread::sleep(Duration::from_millis(100));
}
} else {
ApprovalResult {
request_id,
approved: false,
status: ApprovalStatus::Pending,
reason: None,
approver: None,
decided_at: 0,
metadata: std::collections::HashMap::new(),
}
}
}
fn supports_async(&self) -> bool {
true
}
}
#[derive(Clone)]
pub struct RiskBasedHandler {
max_auto_approve: RiskLevel,
fallback: Arc<dyn ApprovalHandler>,
}
impl std::fmt::Debug for RiskBasedHandler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RiskBasedHandler")
.field("max_auto_approve", &self.max_auto_approve)
.field("fallback", &"<dyn ApprovalHandler>")
.finish()
}
}
impl RiskBasedHandler {
pub fn new(max_auto_approve: RiskLevel, fallback: Arc<dyn ApprovalHandler>) -> Self {
Self {
max_auto_approve,
fallback,
}
}
fn should_auto_approve(&self, level: RiskLevel) -> bool {
matches!(
(level, self.max_auto_approve),
(RiskLevel::Low, _)
| (
RiskLevel::Medium,
RiskLevel::Medium | RiskLevel::High | RiskLevel::Critical
)
| (RiskLevel::High, RiskLevel::High | RiskLevel::Critical)
| (RiskLevel::Critical, RiskLevel::Critical)
)
}
}
impl ApprovalHandler for RiskBasedHandler {
fn request_approval(&self, request: ApprovalRequest) -> ApprovalResult {
if self.should_auto_approve(request.risk_level) {
ApprovalResult::approved(&request.id)
.approver("risk_policy")
.metadata("auto_approved_risk", format!("{:?}", request.risk_level))
} else {
self.fallback.request_approval(request)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auto_approval_handler() {
let handler = AutoApprovalHandler::new();
let request = ApprovalRequest::new("test_op");
let result = handler.request_approval(request);
assert!(result.approved);
assert_eq!(result.status, ApprovalStatus::Approved);
}
#[test]
fn test_auto_approval_with_name() {
let handler = AutoApprovalHandler::new().with_approver("test_system");
let request = ApprovalRequest::new("test_op");
let result = handler.request_approval(request);
assert_eq!(result.approver, Some("test_system".to_string()));
}
#[test]
fn test_deny_all_handler() {
let handler = DenyAllHandler::new().with_reason("Testing");
let request = ApprovalRequest::new("test_op");
let result = handler.request_approval(request);
assert!(!result.approved);
assert_eq!(result.status, ApprovalStatus::Denied);
assert_eq!(result.reason, Some("Testing".to_string()));
}
#[test]
fn test_queue_handler_pending() {
let handler = QueueApprovalHandler::new();
let request = ApprovalRequest::new("test_op");
let result = handler.request_approval(request);
assert!(!result.approved);
assert_eq!(result.status, ApprovalStatus::Pending);
assert_eq!(handler.pending_count(), 1);
}
#[test]
fn test_queue_handler_approve() {
let handler = QueueApprovalHandler::new();
let request = ApprovalRequest::new("test_op");
let request_id = request.id.clone();
handler.request_approval(request);
assert!(handler.approve(&request_id, Some("admin")));
let result = handler.get_result(&request_id).unwrap();
assert!(result.approved);
assert_eq!(handler.pending_count(), 0);
}
#[test]
fn test_queue_handler_deny() {
let handler = QueueApprovalHandler::new();
let request = ApprovalRequest::new("test_op");
let request_id = request.id.clone();
handler.request_approval(request);
assert!(handler.deny(&request_id, "Not allowed", None));
let result = handler.get_result(&request_id).unwrap();
assert!(!result.approved);
assert_eq!(result.reason, Some("Not allowed".to_string()));
}
#[test]
fn test_queue_handler_with_timeout() {
let handler = QueueApprovalHandler::new();
let request = ApprovalRequest::new("test_op").timeout(1);
let result = handler.request_approval(request);
assert!(!result.approved);
assert_eq!(result.status, ApprovalStatus::TimedOut);
}
#[test]
fn test_risk_based_handler_auto_approve_low() {
let fallback = Arc::new(DenyAllHandler::new());
let handler = RiskBasedHandler::new(RiskLevel::Low, fallback);
let request = ApprovalRequest::new("test").risk_level(RiskLevel::Low);
let result = handler.request_approval(request);
assert!(result.approved);
}
#[test]
fn test_risk_based_handler_fallback() {
let fallback = Arc::new(DenyAllHandler::new());
let handler = RiskBasedHandler::new(RiskLevel::Low, fallback);
let request = ApprovalRequest::new("test").risk_level(RiskLevel::High);
let result = handler.request_approval(request);
assert!(!result.approved); }
#[test]
fn test_queue_pending_requests() {
let handler = QueueApprovalHandler::new();
handler.request_approval(ApprovalRequest::new("op1"));
handler.request_approval(ApprovalRequest::new("op2"));
handler.request_approval(ApprovalRequest::new("op3"));
let pending = handler.pending_requests();
assert_eq!(pending.len(), 3);
assert_eq!(pending[0].operation, "op1");
assert_eq!(pending[2].operation, "op3");
}
#[test]
fn test_queue_clear() {
let handler = QueueApprovalHandler::new();
handler.request_approval(ApprovalRequest::new("op1"));
handler.request_approval(ApprovalRequest::new("op2"));
handler.clear();
assert_eq!(handler.pending_count(), 0);
}
#[test]
fn test_supports_async() {
let auto = AutoApprovalHandler::new();
assert!(!auto.supports_async());
let queue = QueueApprovalHandler::new();
assert!(queue.supports_async());
}
}