core_policy/
resource_matcher.rs1use crate::Resource;
48use alloc::boxed::Box;
49use alloc::collections::BTreeMap;
50use alloc::string::String;
51use alloc::vec::Vec;
52
53pub trait ResourceMatcher: Send + Sync {
55 fn matches(&self, pattern: &Resource, target: &Resource) -> bool;
57
58 fn priority(&self) -> u32 {
60 0
61 }
62
63 fn name(&self) -> &str {
65 "ResourceMatcher"
66 }
67}
68
69pub struct ResourceMatcherRegistry {
71 matchers: BTreeMap<String, Box<dyn ResourceMatcher>>,
72}
73
74impl ResourceMatcherRegistry {
75 pub fn new() -> Self {
77 Self {
78 matchers: BTreeMap::new(),
79 }
80 }
81
82 pub fn register(
87 &mut self,
88 resource_type: impl Into<String>,
89 matcher: Box<dyn ResourceMatcher>,
90 ) -> Option<Box<dyn ResourceMatcher>> {
91 self.matchers.insert(resource_type.into(), matcher)
92 }
93
94 pub fn unregister(&mut self, resource_type: &str) -> Option<Box<dyn ResourceMatcher>> {
100 self.matchers.remove(resource_type)
101 }
102
103 pub fn has_matcher(&self, resource_type: &str) -> bool {
105 self.matchers.contains_key(resource_type)
106 }
107
108 pub fn matches(&self, pattern: &Resource, target: &Resource) -> bool {
113 if let Resource::Custom { resource_type, .. } = pattern {
115 if let Some(matcher) = self.matchers.get(resource_type) {
116 return matcher.matches(pattern, target);
117 }
118 }
119
120 pattern.matches(target)
122 }
123
124 pub fn list_matchers(&self) -> Vec<String> {
126 self.matchers.keys().cloned().collect()
127 }
128
129 pub fn count(&self) -> usize {
131 self.matchers.len()
132 }
133
134 pub fn clear(&mut self) {
136 self.matchers.clear();
137 }
138}
139
140impl Default for ResourceMatcherRegistry {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use alloc::string::ToString;
150
151 struct AlwaysMatcher;
153 impl ResourceMatcher for AlwaysMatcher {
154 fn matches(&self, _pattern: &Resource, _target: &Resource) -> bool {
155 true
156 }
157 }
158
159 struct NeverMatcher;
161 impl ResourceMatcher for NeverMatcher {
162 fn matches(&self, _pattern: &Resource, _target: &Resource) -> bool {
163 false
164 }
165 }
166
167 struct ExactMatcher;
169 impl ResourceMatcher for ExactMatcher {
170 fn matches(&self, pattern: &Resource, target: &Resource) -> bool {
171 match (pattern, target) {
172 (
173 Resource::Custom {
174 resource_type: t1,
175 path: p1,
176 },
177 Resource::Custom {
178 resource_type: t2,
179 path: p2,
180 },
181 ) => t1 == t2 && p1 == p2,
182 _ => false,
183 }
184 }
185 }
186
187 struct PriorityMatcher;
189 impl ResourceMatcher for PriorityMatcher {
190 fn matches(&self, _: &Resource, _: &Resource) -> bool {
191 true
192 }
193
194 fn priority(&self) -> u32 {
195 100
196 }
197 }
198
199 #[test]
200 fn test_registry_new() {
201 let registry = ResourceMatcherRegistry::new();
202 assert_eq!(registry.count(), 0);
203 }
204
205 #[test]
206 fn test_register_matcher() {
207 let mut registry = ResourceMatcherRegistry::new();
208
209 registry.register("test", Box::new(AlwaysMatcher));
210 assert_eq!(registry.count(), 1);
211 assert!(registry.has_matcher("test"));
212 }
213
214 #[test]
215 fn test_register_duplicate_replaces() {
216 let mut registry = ResourceMatcherRegistry::new();
217
218 let old = registry.register("test", Box::new(AlwaysMatcher));
219 assert!(old.is_none());
220
221 let old = registry.register("test", Box::new(NeverMatcher));
222 assert!(old.is_some());
223 assert_eq!(registry.count(), 1);
224 }
225
226 #[test]
227 fn test_unregister_matcher() {
228 let mut registry = ResourceMatcherRegistry::new();
229
230 registry.register("test", Box::new(AlwaysMatcher));
231 assert_eq!(registry.count(), 1);
232
233 let removed = registry.unregister("test");
234 assert!(removed.is_some());
235 assert_eq!(registry.count(), 0);
236 }
237
238 #[test]
239 fn test_unregister_nonexistent() {
240 let mut registry = ResourceMatcherRegistry::new();
241
242 let removed = registry.unregister("nonexistent");
243 assert!(removed.is_none());
244 }
245
246 #[test]
247 fn test_has_matcher() {
248 let mut registry = ResourceMatcherRegistry::new();
249
250 assert!(!registry.has_matcher("test"));
251
252 registry.register("test", Box::new(AlwaysMatcher));
253 assert!(registry.has_matcher("test"));
254
255 registry.unregister("test");
256 assert!(!registry.has_matcher("test"));
257 }
258
259 #[test]
260 fn test_matches_with_custom_matcher() {
261 let mut registry = ResourceMatcherRegistry::new();
262 registry.register("test", Box::new(AlwaysMatcher));
263
264 let pattern = Resource::Custom {
265 resource_type: "test".into(),
266 path: "anything".into(),
267 };
268 let target = Resource::Custom {
269 resource_type: "test".into(),
270 path: "different".into(),
271 };
272
273 assert!(registry.matches(&pattern, &target));
274 }
275
276 #[test]
277 fn test_matches_without_custom_matcher_uses_default() {
278 let registry = ResourceMatcherRegistry::new();
279
280 let pattern = Resource::File("/home/*".into());
281 let target = Resource::File("/home/user".into());
282
283 assert!(registry.matches(&pattern, &target));
285 }
286
287 #[test]
288 fn test_exact_matcher() {
289 let mut registry = ResourceMatcherRegistry::new();
290 registry.register("exact", Box::new(ExactMatcher));
291
292 let pattern = Resource::Custom {
293 resource_type: "exact".into(),
294 path: "/path/to/file".into(),
295 };
296 let target_match = Resource::Custom {
297 resource_type: "exact".into(),
298 path: "/path/to/file".into(),
299 };
300 let target_no_match = Resource::Custom {
301 resource_type: "exact".into(),
302 path: "/different/path".into(),
303 };
304
305 assert!(registry.matches(&pattern, &target_match));
306 assert!(!registry.matches(&pattern, &target_no_match));
307 }
308
309 #[test]
310 fn test_list_matchers() {
311 let mut registry = ResourceMatcherRegistry::new();
312
313 registry.register("s3", Box::new(AlwaysMatcher));
314 registry.register("docker", Box::new(NeverMatcher));
315
316 let list = registry.list_matchers();
317 assert_eq!(list.len(), 2);
318 assert!(list.contains(&"s3".to_string()));
319 assert!(list.contains(&"docker".to_string()));
320 }
321
322 #[test]
323 fn test_count() {
324 let mut registry = ResourceMatcherRegistry::new();
325 assert_eq!(registry.count(), 0);
326
327 registry.register("a", Box::new(AlwaysMatcher));
328 assert_eq!(registry.count(), 1);
329
330 registry.register("b", Box::new(NeverMatcher));
331 assert_eq!(registry.count(), 2);
332
333 registry.unregister("a");
334 assert_eq!(registry.count(), 1);
335 }
336
337 #[test]
338 fn test_clear() {
339 let mut registry = ResourceMatcherRegistry::new();
340
341 registry.register("a", Box::new(AlwaysMatcher));
342 registry.register("b", Box::new(NeverMatcher));
343 assert_eq!(registry.count(), 2);
344
345 registry.clear();
346 assert_eq!(registry.count(), 0);
347 }
348
349 #[test]
350 fn test_matcher_priority() {
351 let matcher = PriorityMatcher;
352 assert_eq!(matcher.priority(), 100);
353 }
354
355 #[test]
356 fn test_fallback_to_default_file_matching() {
357 let registry = ResourceMatcherRegistry::new();
358
359 let pattern = Resource::File("/data/*.txt".into());
361 let target = Resource::File("/data/file.txt".into());
362 assert!(registry.matches(&pattern, &target));
363 }
364
365 #[test]
366 fn test_fallback_to_default_usb_matching() {
367 let registry = ResourceMatcherRegistry::new();
368
369 let pattern = Resource::Usb("usb-*".into());
371 let target = Resource::Usb("usb-keyboard".into());
372 assert!(registry.matches(&pattern, &target));
373 }
374
375 #[test]
376 fn test_fallback_to_default_all_matching() {
377 let registry = ResourceMatcherRegistry::new();
378
379 let pattern = Resource::All;
381 let target = Resource::File("/anything".into());
382 assert!(registry.matches(&pattern, &target));
383 }
384}