use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum Class {
Read,
Write,
DeviceManagement,
}
impl Class {
pub(crate) fn name(self) -> &'static str {
match self {
Self::Read => "read",
Self::Write => "write",
Self::DeviceManagement => "device-management",
}
}
}
use Class::{DeviceManagement, Read, Write};
const COMMANDS: &[(&str, Class)] = &[
("discover", Read),
("whois", Read),
("find", Read),
("whohas", Read),
("read", Read),
("rp", Read),
("readm", Read),
("rpm", Read),
("read-range", Read),
("rr", Read),
("alarms", Read),
("file-read", Read),
("bdt", Read),
("fdt", Read),
("whois-router", Read),
("subscribe", Read),
("cov", Read),
("register", Read),
("mstp-ports", Read),
("capture", Read),
("sc-check", Read),
("tui", Read),
("help", Read),
("write", Write),
("wp", Write),
("writem", Write),
("wpm", Write),
("create-object", Write),
("delete-object", Write),
("file-write", Write),
("ack-alarm", Write),
("ack", Write),
("time-sync", Write),
("ts", Write),
("unregister", Write),
("control", DeviceManagement),
("dcc", DeviceManagement),
("reinit", DeviceManagement),
];
pub(crate) fn of_subscribe(lifetime: u32) -> Class {
if lifetime == 0 {
Write
} else {
Read
}
}
pub(crate) fn of(command: &str) -> Option<Class> {
COMMANDS
.iter()
.find(|(name, _)| *name == command)
.map(|&(_, class)| class)
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
fn clap_names() -> Vec<(String, Vec<String>)> {
crate::parse_on_large_stack(|| {
crate::args::Cli::command()
.get_subcommands()
.map(|sub| {
(
sub.get_name().to_string(),
sub.get_all_aliases().map(str::to_string).collect(),
)
})
.collect()
})
}
#[test]
fn every_command_and_alias_has_a_class() {
let clap = clap_names();
assert!(clap.len() > 20, "{clap:?}");
for (name, aliases) in &clap {
let class = of(name).unwrap_or_else(|| panic!("subcommand {name} has no class"));
for alias in aliases {
assert_eq!(of(alias), Some(class), "alias {alias} of {name}");
}
}
for (name, _) in COMMANDS {
let known = clap
.iter()
.any(|(n, aliases)| n == name || aliases.iter().any(|a| a == name))
|| *name == "help";
assert!(known, "{name} is classified but is no command");
}
}
#[test]
fn the_mutating_commands_are_pinned() {
let mutating: Vec<(&str, &str)> = COMMANDS
.iter()
.filter(|(_, class)| *class != Read)
.map(|&(name, class)| (name, class.name()))
.collect();
assert_eq!(
mutating,
[
("write", "write"),
("wp", "write"),
("writem", "write"),
("wpm", "write"),
("create-object", "write"),
("delete-object", "write"),
("file-write", "write"),
("ack-alarm", "write"),
("ack", "write"),
("time-sync", "write"),
("ts", "write"),
("unregister", "write"),
("control", "device-management"),
("dcc", "device-management"),
("reinit", "device-management"),
]
);
assert_eq!(of("subscribe"), Some(Read));
assert_eq!(of_subscribe(300), Read);
assert_eq!(of_subscribe(1), Read);
assert_eq!(of_subscribe(0), Write);
assert_eq!(of("register"), Some(Read));
assert_eq!(of("no-such-command"), None);
}
}