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" => (SafetyLevel::SafeWrite, None),
46 "inert" => (SafetyLevel::Inert, Some("paranoid")),
49 "safe-read" => (SafetyLevel::SafeRead, Some("reader")),
50 "safe-write" => (SafetyLevel::SafeWrite, Some("developer")),
51 _ => return None,
52 })
53 }
54}
55
56impl fmt::Display for SafetyLevel {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 SafetyLevel::Inert => write!(f, "inert"),
60 SafetyLevel::SafeRead => write!(f, "safe-read"),
61 SafetyLevel::SafeWrite => write!(f, "safe-write"),
62 }
63 }
64}
65
66impl fmt::Display for Verdict {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Verdict::Denied => write!(f, "denied"),
70 Verdict::Allowed(level) => write!(f, "allowed ({level})"),
71 }
72 }
73}
74
75impl clap::ValueEnum for SafetyLevel {
76 fn value_variants<'a>() -> &'a [Self] {
77 &[SafetyLevel::Inert, SafetyLevel::SafeRead, SafetyLevel::SafeWrite]
78 }
79
80 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
81 match self {
82 SafetyLevel::Inert => Some(clap::builder::PossibleValue::new("inert")),
83 SafetyLevel::SafeRead => Some(clap::builder::PossibleValue::new("safe-read")),
84 SafetyLevel::SafeWrite => Some(clap::builder::PossibleValue::new("safe-write")),
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn level_ordering() {
95 assert!(SafetyLevel::Inert < SafetyLevel::SafeRead);
96 assert!(SafetyLevel::SafeRead < SafetyLevel::SafeWrite);
97 }
98
99 #[test]
100 fn threshold_names_map_new_and_legacy() {
101 assert_eq!(SafetyLevel::resolve_threshold("paranoid"), Some((SafetyLevel::Inert, None)));
103 assert_eq!(SafetyLevel::resolve_threshold("reader"), Some((SafetyLevel::SafeRead, None)));
104 assert_eq!(SafetyLevel::resolve_threshold("editor"), Some((SafetyLevel::SafeWrite, None)));
105 assert_eq!(SafetyLevel::resolve_threshold("developer"), Some((SafetyLevel::SafeWrite, None)));
106 assert_eq!(SafetyLevel::resolve_threshold("yolo"), Some((SafetyLevel::SafeWrite, None)));
107
108 assert_eq!(SafetyLevel::resolve_threshold("inert"), Some((SafetyLevel::Inert, Some("paranoid"))));
110 assert_eq!(SafetyLevel::resolve_threshold("safe-read"), Some((SafetyLevel::SafeRead, Some("reader"))));
111 assert_eq!(SafetyLevel::resolve_threshold("safe-write"), Some((SafetyLevel::SafeWrite, Some("developer"))));
112
113 assert_eq!(SafetyLevel::resolve_threshold("inert").unwrap().0, SafetyLevel::Inert);
115 assert_eq!(SafetyLevel::resolve_threshold("safe-write").unwrap().0, SafetyLevel::SafeWrite);
116
117 assert_eq!(SafetyLevel::resolve_threshold("nonsense"), None);
118 }
119
120 #[test]
121 fn combine_both_allowed() {
122 let a = Verdict::Allowed(SafetyLevel::Inert);
123 let b = Verdict::Allowed(SafetyLevel::SafeRead);
124 assert_eq!(a.combine(b), Verdict::Allowed(SafetyLevel::SafeRead));
125 }
126
127 #[test]
128 fn combine_one_denied() {
129 let a = Verdict::Allowed(SafetyLevel::Inert);
130 assert_eq!(a.combine(Verdict::Denied), Verdict::Denied);
131 assert_eq!(Verdict::Denied.combine(a), Verdict::Denied);
132 }
133
134 #[test]
135 fn combine_identity() {
136 let a = Verdict::Allowed(SafetyLevel::SafeWrite);
137 let identity = Verdict::Allowed(SafetyLevel::Inert);
138 assert_eq!(identity.combine(a), a);
139 }
140}