use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ProcessRole {
Acme,
Admin,
Worker,
}
impl ProcessRole {
pub const ALL: [Self; 3] = [Self::Acme, Self::Admin, Self::Worker];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Acme => "acme",
Self::Admin => "admin",
Self::Worker => "worker",
}
}
}
impl fmt::Display for ProcessRole {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for ProcessRole {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.trim() {
"acme" => Ok(Self::Acme),
"admin" => Ok(Self::Admin),
"worker" => Ok(Self::Worker),
other => Err(format!(
"unknown role `{other}` (expected one of: acme, admin, worker)"
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RoleSet {
acme: bool,
admin: bool,
worker: bool,
}
impl Default for RoleSet {
fn default() -> Self {
Self {
acme: true,
admin: true,
worker: true,
}
}
}
impl RoleSet {
#[must_use]
pub fn has(self, role: ProcessRole) -> bool {
match role {
ProcessRole::Acme => self.acme,
ProcessRole::Admin => self.admin,
ProcessRole::Worker => self.worker,
}
}
#[must_use]
pub fn labels(self) -> Vec<&'static str> {
ProcessRole::ALL
.into_iter()
.filter(|role| self.has(*role))
.map(ProcessRole::as_str)
.collect()
}
pub fn parse(value: Option<&str>) -> Result<Self, String> {
let Some(value) = value else {
return Ok(Self::default());
};
let mut set = Self {
acme: false,
admin: false,
worker: false,
};
for name in value.split(',').filter(|name| !name.trim().is_empty()) {
match name.parse::<ProcessRole>()? {
ProcessRole::Acme => set.acme = true,
ProcessRole::Admin => set.admin = true,
ProcessRole::Worker => set.worker = true,
}
}
if !(set.acme || set.admin || set.worker) {
return Err("--role names no role (expected one of: acme, admin, worker)".to_string());
}
Ok(set)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_flag_is_every_role() {
let set = RoleSet::parse(None).unwrap();
for role in ProcessRole::ALL {
assert!(set.has(role), "{role} must be on by default");
}
assert_eq!(set, RoleSet::default());
}
#[test]
fn a_list_selects_exactly_what_it_names() {
let set = RoleSet::parse(Some("acme,worker")).unwrap();
assert!(set.has(ProcessRole::Acme));
assert!(set.has(ProcessRole::Worker));
assert!(!set.has(ProcessRole::Admin));
assert_eq!(set.labels(), vec!["acme", "worker"]);
}
#[test]
fn whitespace_and_repeats_are_tolerated() {
let set = RoleSet::parse(Some(" admin , admin ,worker")).unwrap();
assert_eq!(set.labels(), vec!["admin", "worker"]);
}
#[test]
fn an_unknown_role_is_refused_by_name() {
let error = RoleSet::parse(Some("acme,wroker")).unwrap_err();
assert!(error.contains("wroker"), "{error}");
assert!(error.contains("acme, admin, worker"), "{error}");
}
#[test]
fn a_list_naming_no_role_is_refused() {
assert!(RoleSet::parse(Some("")).unwrap_err().contains("no role"));
assert!(RoleSet::parse(Some(" , ")).unwrap_err().contains("no role"));
}
#[test]
fn every_role_round_trips_through_its_spelling() {
for role in ProcessRole::ALL {
assert_eq!(role.as_str().parse::<ProcessRole>().unwrap(), role);
assert_eq!(role.to_string(), role.as_str());
}
}
}