1use std::fmt;
8use std::str::FromStr;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[non_exhaustive]
13pub enum Kind {
14 Shell,
16 FsRead,
18 FsWrite,
20 Net,
22 Fetch,
25 WebSearch,
29 EnvRead,
31 EnvSet,
33 Mcp,
35}
36
37impl Kind {
38 pub const ALL: [Self; 9] = [
40 Self::Shell,
41 Self::FsRead,
42 Self::FsWrite,
43 Self::Net,
44 Self::Fetch,
45 Self::WebSearch,
46 Self::EnvRead,
47 Self::EnvSet,
48 Self::Mcp,
49 ];
50
51 #[must_use]
53 pub fn as_str(self) -> &'static str {
54 match self {
55 Self::Shell => "shell",
56 Self::FsRead => "fs.read",
57 Self::FsWrite => "fs.write",
58 Self::Net => "net",
59 Self::Fetch => "fetch",
60 Self::WebSearch => "web.search",
61 Self::EnvRead => "env.read",
62 Self::EnvSet => "env.set",
63 Self::Mcp => "mcp",
64 }
65 }
66
67 #[must_use]
71 pub fn rule_kinds(self) -> &'static [Self] {
72 match self {
73 Self::Fetch => &[Self::Fetch, Self::Net],
74 Self::Shell => &[Self::Shell],
75 Self::FsRead => &[Self::FsRead],
76 Self::FsWrite => &[Self::FsWrite],
77 Self::Net => &[Self::Net],
78 Self::WebSearch => &[Self::WebSearch],
79 Self::EnvRead => &[Self::EnvRead],
80 Self::EnvSet => &[Self::EnvSet],
81 Self::Mcp => &[Self::Mcp],
82 }
83 }
84}
85
86impl fmt::Display for Kind {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.write_str(self.as_str())
89 }
90}
91
92impl serde::Serialize for Kind {
94 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
95 serializer.serialize_str(self.as_str())
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
101#[error("unknown action kind `{0}`")]
102pub struct UnknownKind(pub String);
103
104impl FromStr for Kind {
105 type Err = UnknownKind;
106
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 Self::ALL
109 .into_iter()
110 .find(|k| k.as_str() == s)
111 .ok_or_else(|| UnknownKind(s.to_owned()))
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn names_round_trip() {
121 for kind in Kind::ALL {
122 assert_eq!(kind.as_str().parse::<Kind>(), Ok(kind));
123 }
124 assert_eq!(
125 "network".parse::<Kind>(),
126 Err(UnknownKind("network".into()))
127 );
128 }
129
130 #[test]
131 fn only_fetch_borrows_another_kinds_rules() {
132 assert_eq!(Kind::Fetch.rule_kinds(), &[Kind::Fetch, Kind::Net]);
133 for kind in Kind::ALL.into_iter().filter(|k| *k != Kind::Fetch) {
134 assert_eq!(kind.rule_kinds(), &[kind]);
135 }
136 }
137}