acme_proxy_server/
roles.rs1use std::fmt;
22use std::str::FromStr;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub enum ProcessRole {
27 Acme,
30 Admin,
32 Worker,
34}
35
36impl ProcessRole {
37 pub const ALL: [Self; 3] = [Self::Acme, Self::Admin, Self::Worker];
39
40 #[must_use]
42 pub fn as_str(self) -> &'static str {
43 match self {
44 Self::Acme => "acme",
45 Self::Admin => "admin",
46 Self::Worker => "worker",
47 }
48 }
49}
50
51impl fmt::Display for ProcessRole {
52 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53 formatter.write_str(self.as_str())
54 }
55}
56
57impl FromStr for ProcessRole {
58 type Err = String;
59
60 fn from_str(value: &str) -> Result<Self, Self::Err> {
66 match value.trim() {
67 "acme" => Ok(Self::Acme),
68 "admin" => Ok(Self::Admin),
69 "worker" => Ok(Self::Worker),
70 other => Err(format!(
71 "unknown role `{other}` (expected one of: acme, admin, worker)"
72 )),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct RoleSet {
80 acme: bool,
81 admin: bool,
82 worker: bool,
83}
84
85impl Default for RoleSet {
86 fn default() -> Self {
89 Self {
90 acme: true,
91 admin: true,
92 worker: true,
93 }
94 }
95}
96
97impl RoleSet {
98 #[must_use]
100 pub fn has(self, role: ProcessRole) -> bool {
101 match role {
102 ProcessRole::Acme => self.acme,
103 ProcessRole::Admin => self.admin,
104 ProcessRole::Worker => self.worker,
105 }
106 }
107
108 #[must_use]
110 pub fn labels(self) -> Vec<&'static str> {
111 ProcessRole::ALL
112 .into_iter()
113 .filter(|role| self.has(*role))
114 .map(ProcessRole::as_str)
115 .collect()
116 }
117
118 pub fn parse(value: Option<&str>) -> Result<Self, String> {
126 let Some(value) = value else {
127 return Ok(Self::default());
128 };
129
130 let mut set = Self {
131 acme: false,
132 admin: false,
133 worker: false,
134 };
135 for name in value.split(',').filter(|name| !name.trim().is_empty()) {
136 match name.parse::<ProcessRole>()? {
137 ProcessRole::Acme => set.acme = true,
138 ProcessRole::Admin => set.admin = true,
139 ProcessRole::Worker => set.worker = true,
140 }
141 }
142
143 if !(set.acme || set.admin || set.worker) {
144 return Err("--role names no role (expected one of: acme, admin, worker)".to_string());
145 }
146 Ok(set)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn no_flag_is_every_role() {
156 let set = RoleSet::parse(None).unwrap();
157 for role in ProcessRole::ALL {
158 assert!(set.has(role), "{role} must be on by default");
159 }
160 assert_eq!(set, RoleSet::default());
161 }
162
163 #[test]
164 fn a_list_selects_exactly_what_it_names() {
165 let set = RoleSet::parse(Some("acme,worker")).unwrap();
166 assert!(set.has(ProcessRole::Acme));
167 assert!(set.has(ProcessRole::Worker));
168 assert!(!set.has(ProcessRole::Admin));
169 assert_eq!(set.labels(), vec!["acme", "worker"]);
170 }
171
172 #[test]
173 fn whitespace_and_repeats_are_tolerated() {
174 let set = RoleSet::parse(Some(" admin , admin ,worker")).unwrap();
175 assert_eq!(set.labels(), vec!["admin", "worker"]);
176 }
177
178 #[test]
182 fn an_unknown_role_is_refused_by_name() {
183 let error = RoleSet::parse(Some("acme,wroker")).unwrap_err();
184 assert!(error.contains("wroker"), "{error}");
185 assert!(error.contains("acme, admin, worker"), "{error}");
186 }
187
188 #[test]
189 fn a_list_naming_no_role_is_refused() {
190 assert!(RoleSet::parse(Some("")).unwrap_err().contains("no role"));
191 assert!(RoleSet::parse(Some(" , ")).unwrap_err().contains("no role"));
192 }
193
194 #[test]
195 fn every_role_round_trips_through_its_spelling() {
196 for role in ProcessRole::ALL {
197 assert_eq!(role.as_str().parse::<ProcessRole>().unwrap(), role);
198 assert_eq!(role.to_string(), role.as_str());
199 }
200 }
201}