1use std::fmt;
2
3#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
4#[repr(u8)]
5pub enum SafetyLevel {
6 Inert = 0,
7 SafeRead = 1,
8 SafeWrite = 2,
9}
10
11#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12pub enum Verdict {
13 Denied,
14 Allowed(SafetyLevel),
15}
16
17impl Verdict {
18 pub fn combine(self, other: Verdict) -> Verdict {
19 match (self, other) {
20 (Verdict::Denied, _) | (_, Verdict::Denied) => Verdict::Denied,
21 (Verdict::Allowed(a), Verdict::Allowed(b)) => Verdict::Allowed(a.max(b)),
22 }
23 }
24
25 pub fn is_allowed(self) -> bool {
26 matches!(self, Verdict::Allowed(_))
27 }
28}
29
30impl SafetyLevel {
31 pub fn resolve_threshold(name: &str) -> Option<(SafetyLevel, Option<&'static str>)> {
42 Some(match name {
43 "paranoid" => (SafetyLevel::Inert, None),
44 "reader" => (SafetyLevel::SafeRead, None),
45 "editor" | "developer" | "local-admin" | "network-admin" | "yolo" => {
46 (SafetyLevel::SafeWrite, None)
47 }
48 "inert" => (SafetyLevel::Inert, Some("paranoid")),
51 "safe-read" => (SafetyLevel::SafeRead, Some("reader")),
52 "safe-write" => (SafetyLevel::SafeWrite, Some("developer")),
53 _ => return None,
54 })
55 }
56}
57
58impl fmt::Display for SafetyLevel {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 SafetyLevel::Inert => write!(f, "inert"),
62 SafetyLevel::SafeRead => write!(f, "safe-read"),
63 SafetyLevel::SafeWrite => write!(f, "safe-write"),
64 }
65 }
66}
67
68impl fmt::Display for Verdict {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 Verdict::Denied => write!(f, "denied"),
72 Verdict::Allowed(level) => write!(f, "allowed ({level})"),
73 }
74 }
75}
76
77impl clap::ValueEnum for SafetyLevel {
78 fn value_variants<'a>() -> &'a [Self] {
79 &[SafetyLevel::Inert, SafetyLevel::SafeRead, SafetyLevel::SafeWrite]
80 }
81
82 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
83 match self {
84 SafetyLevel::Inert => Some(clap::builder::PossibleValue::new("inert")),
85 SafetyLevel::SafeRead => Some(clap::builder::PossibleValue::new("safe-read")),
86 SafetyLevel::SafeWrite => Some(clap::builder::PossibleValue::new("safe-write")),
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn level_ordering() {
97 assert!(SafetyLevel::Inert < SafetyLevel::SafeRead);
98 assert!(SafetyLevel::SafeRead < SafetyLevel::SafeWrite);
99 }
100
101 #[test]
102 fn threshold_names_map_new_and_legacy() {
103 assert_eq!(SafetyLevel::resolve_threshold("paranoid"), Some((SafetyLevel::Inert, None)));
105 assert_eq!(SafetyLevel::resolve_threshold("reader"), Some((SafetyLevel::SafeRead, None)));
106 assert_eq!(SafetyLevel::resolve_threshold("editor"), Some((SafetyLevel::SafeWrite, None)));
107 assert_eq!(SafetyLevel::resolve_threshold("developer"), Some((SafetyLevel::SafeWrite, None)));
108 assert_eq!(SafetyLevel::resolve_threshold("yolo"), Some((SafetyLevel::SafeWrite, None)));
109
110 assert_eq!(SafetyLevel::resolve_threshold("inert"), Some((SafetyLevel::Inert, Some("paranoid"))));
112 assert_eq!(SafetyLevel::resolve_threshold("safe-read"), Some((SafetyLevel::SafeRead, Some("reader"))));
113 assert_eq!(SafetyLevel::resolve_threshold("safe-write"), Some((SafetyLevel::SafeWrite, Some("developer"))));
114
115 assert_eq!(SafetyLevel::resolve_threshold("inert").unwrap().0, SafetyLevel::Inert);
117 assert_eq!(SafetyLevel::resolve_threshold("safe-write").unwrap().0, SafetyLevel::SafeWrite);
118
119 assert_eq!(SafetyLevel::resolve_threshold("nonsense"), None);
120 }
121
122 #[test]
123 fn combine_both_allowed() {
124 let a = Verdict::Allowed(SafetyLevel::Inert);
125 let b = Verdict::Allowed(SafetyLevel::SafeRead);
126 assert_eq!(a.combine(b), Verdict::Allowed(SafetyLevel::SafeRead));
127 }
128
129 #[test]
130 fn combine_one_denied() {
131 let a = Verdict::Allowed(SafetyLevel::Inert);
132 assert_eq!(a.combine(Verdict::Denied), Verdict::Denied);
133 assert_eq!(Verdict::Denied.combine(a), Verdict::Denied);
134 }
135
136 #[test]
137 fn combine_identity() {
138 let a = Verdict::Allowed(SafetyLevel::SafeWrite);
139 let identity = Verdict::Allowed(SafetyLevel::Inert);
140 assert_eq!(identity.combine(a), a);
141 }
142}