use super::*;
pub trait ApprovalSink: Send {
fn request(&self, scope: &str, reason: &str) -> Result<String, String>;
}
pub struct NullApprovalSink;
impl ApprovalSink for NullApprovalSink {
fn request(&self, _scope: &str, _reason: &str) -> Result<String, String> {
Err("no ApprovalSink configured — call DefaultHandler::with_approval_sink".into())
}
}
pub struct StdinApprovalSink;
impl ApprovalSink for StdinApprovalSink {
fn request(&self, scope: &str, reason: &str) -> Result<String, String> {
use std::io::Write;
print!("[approval:{scope}] {reason} (blank/n to deny) > ");
let _ = std::io::stdout().flush();
let mut line = String::new();
std::io::stdin().read_line(&mut line).map_err(|e| e.to_string())?;
let answer = line.trim();
if answer.is_empty() || answer.eq_ignore_ascii_case("n") || answer.eq_ignore_ascii_case("no") {
Err("denied by operator".into())
} else {
Ok(answer.to_string())
}
}
}
impl DefaultHandler {
pub(super) fn dispatch_approval(&mut self, op: &str, args: Vec<Value>) -> Result<Value, String> {
match op {
"request" => {
let scope = expect_str(args.first())?.to_string();
let reason = expect_str(args.get(1))?.to_string();
if !self.policy.allow_approval.is_empty()
&& !self.policy.allow_approval.iter().any(|a| a == &scope)
{
return Ok(err(Value::Str(format!(
"approval.request: scope `{scope}` not in --allow-approval {:?}",
self.policy.allow_approval
).into())));
}
match self.approval_sink.request(&scope, &reason) {
Ok(answer) => Ok(ok(Value::Str(answer.into()))),
Err(reason) => Ok(err(Value::Str(reason.into()))),
}
}
other => Err(format!("unsupported approval.{other}")),
}
}
}