use agent_client_protocol_schema::v1::{
PermissionOption, PermissionOptionKind, RequestPermissionOutcome, SelectedPermissionOutcome,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PermissionPolicy {
Deny,
AllowOnce,
}
impl PermissionPolicy {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Deny => "deny",
Self::AllowOnce => "allow_once",
}
}
#[must_use]
pub fn decide(self, options: &[PermissionOption]) -> RequestPermissionOutcome {
let selected = match self {
Self::Deny => first_of(
options,
&[
PermissionOptionKind::RejectOnce,
PermissionOptionKind::RejectAlways,
],
),
Self::AllowOnce => first_of(options, &[PermissionOptionKind::AllowOnce]),
};
match selected {
Some(option) => RequestPermissionOutcome::Selected(SelectedPermissionOutcome::new(
option.option_id.clone(),
)),
None => RequestPermissionOutcome::Cancelled,
}
}
}
fn first_of<'a>(
options: &'a [PermissionOption],
wanted: &[PermissionOptionKind],
) -> Option<&'a PermissionOption> {
wanted
.iter()
.find_map(|kind| options.iter().find(|option| option.kind == *kind))
}
#[cfg(test)]
mod tests {
use agent_client_protocol_schema::v1::{
PermissionOption, PermissionOptionKind, RequestPermissionOutcome,
};
use super::PermissionPolicy;
fn full_options() -> Vec<PermissionOption> {
vec![
PermissionOption::new("a1", "Allow once", PermissionOptionKind::AllowOnce),
PermissionOption::new("a2", "Always allow", PermissionOptionKind::AllowAlways),
PermissionOption::new("r1", "Reject", PermissionOptionKind::RejectOnce),
PermissionOption::new("r2", "Always reject", PermissionOptionKind::RejectAlways),
]
}
fn selected_id(outcome: &RequestPermissionOutcome) -> Option<String> {
match outcome {
RequestPermissionOutcome::Selected(selected) => Some(selected.option_id.to_string()),
_ => None,
}
}
#[test]
fn deny_selects_the_reject_once_option() {
let outcome = PermissionPolicy::Deny.decide(&full_options());
assert_eq!(selected_id(&outcome).as_deref(), Some("r1"));
}
#[test]
fn deny_falls_back_to_reject_always_when_no_reject_once_is_offered() {
let options = vec![
PermissionOption::new("a1", "Allow once", PermissionOptionKind::AllowOnce),
PermissionOption::new("r2", "Always reject", PermissionOptionKind::RejectAlways),
];
let outcome = PermissionPolicy::Deny.decide(&options);
assert_eq!(selected_id(&outcome).as_deref(), Some("r2"));
}
#[test]
fn deny_cancels_when_no_reject_option_is_offered() {
let options = vec![
PermissionOption::new("a1", "Allow once", PermissionOptionKind::AllowOnce),
PermissionOption::new("a2", "Always allow", PermissionOptionKind::AllowAlways),
];
let outcome = PermissionPolicy::Deny.decide(&options);
assert_eq!(outcome, RequestPermissionOutcome::Cancelled);
}
#[test]
fn allow_once_selects_the_allow_once_option() {
let outcome = PermissionPolicy::AllowOnce.decide(&full_options());
assert_eq!(selected_id(&outcome).as_deref(), Some("a1"));
}
#[test]
fn allow_once_never_widens_to_allow_always() {
let options = vec![PermissionOption::new(
"a2",
"Always allow",
PermissionOptionKind::AllowAlways,
)];
let outcome = PermissionPolicy::AllowOnce.decide(&options);
assert_eq!(outcome, RequestPermissionOutcome::Cancelled);
}
#[test]
fn an_empty_option_list_still_produces_an_answer() {
for policy in [PermissionPolicy::Deny, PermissionPolicy::AllowOnce] {
assert_eq!(policy.decide(&[]), RequestPermissionOutcome::Cancelled);
}
}
#[test]
fn each_policy_has_a_distinct_audit_label() {
assert_eq!(PermissionPolicy::Deny.label(), "deny");
assert_eq!(PermissionPolicy::AllowOnce.label(), "allow_once");
}
}