use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Kind {
Shell,
FsRead,
FsWrite,
Net,
Fetch,
EnvRead,
EnvSet,
Mcp,
}
impl Kind {
pub const ALL: [Self; 8] = [
Self::Shell,
Self::FsRead,
Self::FsWrite,
Self::Net,
Self::Fetch,
Self::EnvRead,
Self::EnvSet,
Self::Mcp,
];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Shell => "shell",
Self::FsRead => "fs.read",
Self::FsWrite => "fs.write",
Self::Net => "net",
Self::Fetch => "fetch",
Self::EnvRead => "env.read",
Self::EnvSet => "env.set",
Self::Mcp => "mcp",
}
}
#[must_use]
pub fn rule_kinds(self) -> &'static [Self] {
match self {
Self::Fetch => &[Self::Fetch, Self::Net],
Self::Shell => &[Self::Shell],
Self::FsRead => &[Self::FsRead],
Self::FsWrite => &[Self::FsWrite],
Self::Net => &[Self::Net],
Self::EnvRead => &[Self::EnvRead],
Self::EnvSet => &[Self::EnvSet],
Self::Mcp => &[Self::Mcp],
}
}
}
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl serde::Serialize for Kind {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unknown action kind `{0}`")]
pub struct UnknownKind(pub String);
impl FromStr for Kind {
type Err = UnknownKind;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL
.into_iter()
.find(|k| k.as_str() == s)
.ok_or_else(|| UnknownKind(s.to_owned()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_round_trip() {
for kind in Kind::ALL {
assert_eq!(kind.as_str().parse::<Kind>(), Ok(kind));
}
assert_eq!(
"network".parse::<Kind>(),
Err(UnknownKind("network".into()))
);
}
#[test]
fn only_fetch_borrows_another_kinds_rules() {
assert_eq!(Kind::Fetch.rule_kinds(), &[Kind::Fetch, Kind::Net]);
for kind in Kind::ALL.into_iter().filter(|k| *k != Kind::Fetch) {
assert_eq!(kind.rule_kinds(), &[kind]);
}
}
}