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 UnknownTool,
32 EnvRead,
34 EnvSet,
36 Mcp,
38}
39
40impl Kind {
41 pub const ALL: [Self; 10] = [
43 Self::Shell,
44 Self::FsRead,
45 Self::FsWrite,
46 Self::Net,
47 Self::Fetch,
48 Self::WebSearch,
49 Self::UnknownTool,
50 Self::EnvRead,
51 Self::EnvSet,
52 Self::Mcp,
53 ];
54
55 #[must_use]
57 pub fn as_str(self) -> &'static str {
58 match self {
59 Self::Shell => "shell",
60 Self::FsRead => "fs.read",
61 Self::FsWrite => "fs.write",
62 Self::Net => "net",
63 Self::Fetch => "fetch",
64 Self::WebSearch => "web.search",
65 Self::UnknownTool => "tool.unknown",
66 Self::EnvRead => "env.read",
67 Self::EnvSet => "env.set",
68 Self::Mcp => "mcp",
69 }
70 }
71
72 #[must_use]
76 pub fn rule_kinds(self) -> &'static [Self] {
77 match self {
78 Self::Fetch => &[Self::Fetch, Self::Net],
79 Self::Shell => &[Self::Shell],
80 Self::FsRead => &[Self::FsRead],
81 Self::FsWrite => &[Self::FsWrite],
82 Self::Net => &[Self::Net],
83 Self::WebSearch => &[Self::WebSearch],
84 Self::UnknownTool => &[Self::UnknownTool],
85 Self::EnvRead => &[Self::EnvRead],
86 Self::EnvSet => &[Self::EnvSet],
87 Self::Mcp => &[Self::Mcp],
88 }
89 }
90}
91
92impl fmt::Display for Kind {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 f.write_str(self.as_str())
95 }
96}
97
98impl serde::Serialize for Kind {
100 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
101 serializer.serialize_str(self.as_str())
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
107#[error("unknown action kind `{0}`")]
108pub struct UnknownKind(pub String);
109
110impl FromStr for Kind {
111 type Err = UnknownKind;
112
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 Self::ALL
115 .into_iter()
116 .find(|k| k.as_str() == s)
117 .ok_or_else(|| UnknownKind(s.to_owned()))
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn names_round_trip() {
127 for kind in Kind::ALL {
128 assert_eq!(kind.as_str().parse::<Kind>(), Ok(kind));
129 }
130 assert_eq!(
131 "network".parse::<Kind>(),
132 Err(UnknownKind("network".into()))
133 );
134 }
135
136 #[test]
137 fn only_fetch_borrows_another_kinds_rules() {
138 assert_eq!(Kind::Fetch.rule_kinds(), &[Kind::Fetch, Kind::Net]);
139 for kind in Kind::ALL.into_iter().filter(|k| *k != Kind::Fetch) {
140 assert_eq!(kind.rule_kinds(), &[kind]);
141 }
142 }
143}