agent_skills/
allowed_tools.rs1use std::fmt;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7#[derive(Debug, Clone, PartialEq, Eq, Default)]
25pub struct AllowedTools(Vec<String>);
26
27impl AllowedTools {
28 #[must_use]
32 pub fn new(tools: &str) -> Self {
33 let tools: Vec<String> = tools
34 .split_whitespace()
35 .filter(|s| !s.is_empty())
36 .map(String::from)
37 .collect();
38 Self(tools)
39 }
40
41 #[must_use]
43 pub const fn from_vec(tools: Vec<String>) -> Self {
44 Self(tools)
45 }
46
47 #[must_use]
49 pub fn as_slice(&self) -> &[String] {
50 &self.0
51 }
52
53 #[must_use]
55 pub const fn is_empty(&self) -> bool {
56 self.0.is_empty()
57 }
58
59 #[must_use]
61 pub const fn len(&self) -> usize {
62 self.0.len()
63 }
64
65 pub fn iter(&self) -> impl Iterator<Item = &str> {
67 self.0.iter().map(String::as_str)
68 }
69
70 #[must_use]
72 pub fn contains(&self, tool: &str) -> bool {
73 self.0.iter().any(|t| t == tool)
74 }
75}
76
77impl fmt::Display for AllowedTools {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}", self.0.join(" "))
80 }
81}
82
83impl Serialize for AllowedTools {
84 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85 where
86 S: Serializer,
87 {
88 self.0.join(" ").serialize(serializer)
90 }
91}
92
93impl<'de> Deserialize<'de> for AllowedTools {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: Deserializer<'de>,
97 {
98 let s = String::deserialize(deserializer)?;
99 Ok(Self::new(&s))
100 }
101}
102
103impl IntoIterator for AllowedTools {
104 type Item = String;
105 type IntoIter = std::vec::IntoIter<String>;
106
107 fn into_iter(self) -> Self::IntoIter {
108 self.0.into_iter()
109 }
110}
111
112impl<'a> IntoIterator for &'a AllowedTools {
113 type Item = &'a String;
114 type IntoIter = std::slice::Iter<'a, String>;
115
116 fn into_iter(self) -> Self::IntoIter {
117 self.0.iter()
118 }
119}
120
121impl FromIterator<String> for AllowedTools {
122 fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
123 Self(iter.into_iter().collect())
124 }
125}
126
127#[cfg(test)]
128#[allow(clippy::unwrap_used, clippy::expect_used)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn parses_space_delimited_tools() {
134 let tools = AllowedTools::new("Bash(git:*) Read Write");
135 assert_eq!(tools.as_slice().len(), 3);
136 assert_eq!(tools.as_slice()[0], "Bash(git:*)");
137 assert_eq!(tools.as_slice()[1], "Read");
138 assert_eq!(tools.as_slice()[2], "Write");
139 }
140
141 #[test]
142 fn empty_string_creates_empty_tools() {
143 let tools = AllowedTools::new("");
144 assert!(tools.is_empty());
145 assert_eq!(tools.len(), 0);
146 }
147
148 #[test]
149 fn whitespace_only_creates_empty_tools() {
150 let tools = AllowedTools::new(" \t\n ");
151 assert!(tools.is_empty());
152 }
153
154 #[test]
155 fn handles_multiple_spaces() {
156 let tools = AllowedTools::new("Read Write Execute");
157 assert_eq!(tools.len(), 3);
158 }
159
160 #[test]
161 fn from_vec_works() {
162 let tools = AllowedTools::from_vec(vec!["Read".to_string(), "Write".to_string()]);
163 assert_eq!(tools.len(), 2);
164 }
165
166 #[test]
167 fn iter_works() {
168 let tools = AllowedTools::new("Read Write");
169 let items: Vec<_> = tools.iter().collect();
170 assert_eq!(items, vec!["Read", "Write"]);
171 }
172
173 #[test]
174 fn contains_works() {
175 let tools = AllowedTools::new("Read Write");
176 assert!(tools.contains("Read"));
177 assert!(tools.contains("Write"));
178 assert!(!tools.contains("Execute"));
179 }
180
181 #[test]
182 fn display_works() {
183 let tools = AllowedTools::new("Read Write");
184 assert_eq!(format!("{tools}"), "Read Write");
185 }
186
187 #[test]
188 fn into_iter_works() {
189 let tools = AllowedTools::new("Read Write");
190 let items: Vec<String> = tools.into_iter().collect();
191 assert_eq!(items.len(), 2);
192 }
193
194 #[test]
195 fn ref_into_iter_works() {
196 let tools = AllowedTools::new("Read Write");
197 let items: Vec<_> = (&tools).into_iter().collect();
198 assert_eq!(items.len(), 2);
199 }
200
201 #[test]
202 fn collect_works() {
203 let items = vec!["Read".to_string(), "Write".to_string()];
204 let tools: AllowedTools = items.into_iter().collect();
205 assert_eq!(tools.len(), 2);
206 }
207}