1use crate::model::CommandDef;
4
5pub fn filter_commands(
9 commands: Vec<CommandDef>,
10 include: &[String],
11 exclude: &[String],
12 methods: &[String],
13) -> Vec<CommandDef> {
14 let mut result = commands;
15 if !methods.is_empty() {
16 let upper: Vec<String> = methods.iter().map(|m| m.to_uppercase()).collect();
17 result.retain(|c| {
18 c.method
19 .as_ref()
20 .map(|m| upper.iter().any(|u| u == &m.to_uppercase()))
21 .unwrap_or(true)
22 });
23 }
24 if !include.is_empty() {
25 result.retain(|c| include.iter().any(|pat| glob_match(pat, &c.name)));
26 }
27 if !exclude.is_empty() {
28 result.retain(|c| !exclude.iter().any(|pat| glob_match(pat, &c.name)));
29 }
30 result
31}
32
33fn glob_match(pattern: &str, name: &str) -> bool {
35 if !pattern.contains('*') {
36 return pattern == name;
37 }
38 let parts: Vec<&str> = pattern.split('*').collect();
39 if parts.len() == 1 {
40 return pattern == name;
41 }
42 let mut rest = name;
43 if !parts[0].is_empty() {
44 if !rest.starts_with(parts[0]) {
45 return false;
46 }
47 rest = &rest[parts[0].len()..];
48 }
49 for (i, part) in parts.iter().enumerate().skip(1) {
50 if part.is_empty() {
51 if i == parts.len() - 1 {
52 return true;
53 }
54 continue;
55 }
56 if i == parts.len() - 1 {
57 return rest.ends_with(part);
58 }
59 if let Some(idx) = rest.find(part) {
60 rest = &rest[idx + part.len()..];
61 } else {
62 return false;
63 }
64 }
65 true
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 fn cmds() -> Vec<CommandDef> {
73 vec![
74 CommandDef {
75 name: "list-pets".into(),
76 method: Some("GET".into()),
77 ..Default::default()
78 },
79 CommandDef {
80 name: "create-pet".into(),
81 method: Some("POST".into()),
82 ..Default::default()
83 },
84 CommandDef {
85 name: "delete-pet".into(),
86 method: Some("DELETE".into()),
87 ..Default::default()
88 },
89 CommandDef {
90 name: "update-pet".into(),
91 method: Some("PUT".into()),
92 ..Default::default()
93 },
94 CommandDef {
95 name: "echo".into(),
96 tool_name: Some("echo".into()),
97 ..Default::default()
98 },
99 ]
100 }
101
102 fn names(cmds: &[CommandDef]) -> Vec<&str> {
103 cmds.iter().map(|c| c.name.as_str()).collect()
104 }
105
106 #[test]
107 fn glob_basics() {
108 assert!(glob_match("list-*", "list-pets"));
109 assert!(!glob_match("list-*", "create-pet"));
110 assert!(glob_match("*-pet", "get-pet"));
111 assert!(glob_match("*", "anything"));
112 }
113
114 #[test]
115 fn no_filters() {
116 let c = cmds();
117 assert_eq!(names(&filter_commands(c.clone(), &[], &[], &[])), names(&c));
118 }
119
120 #[test]
121 fn methods_filter() {
122 let methods = vec!["GET".into(), "POST".into()];
123 let result = filter_commands(cmds(), &[], &[], &methods);
124 let n = names(&result);
125 assert!(n.contains(&"list-pets"));
126 assert!(n.contains(&"create-pet"));
127 assert!(!n.contains(&"delete-pet"));
128 assert!(!n.contains(&"update-pet"));
129 assert!(n.contains(&"echo")); }
131
132 #[test]
133 fn include_filter() {
134 let include = vec!["list-*".into()];
135 let result = filter_commands(cmds(), &include, &[], &[]);
136 assert_eq!(names(&result), vec!["list-pets"]);
137 }
138
139 #[test]
140 fn exclude_filter() {
141 let exclude = vec!["delete-*".into(), "update-*".into()];
142 let result = filter_commands(cmds(), &[], &exclude, &[]);
143 let n = names(&result);
144 assert!(n.contains(&"list-pets"));
145 assert!(n.contains(&"create-pet"));
146 assert!(!n.contains(&"delete-pet"));
147 assert!(!n.contains(&"update-pet"));
148 }
149
150 #[test]
151 fn combined_filters() {
152 let methods = vec!["GET".into(), "POST".into()];
153 let exclude = vec!["create-*".into()];
154 let result = filter_commands(cmds(), &[], &exclude, &methods);
155 assert_eq!(names(&result), vec!["list-pets", "echo"]);
156 }
157
158 #[test]
159 fn include_and_exclude() {
160 let include = vec!["*-pet".into()];
161 let exclude = vec!["delete-*".into()];
162 let result = filter_commands(cmds(), &include, &exclude, &[]);
163 let n = names(&result);
164 assert!(n.contains(&"create-pet"));
165 assert!(n.contains(&"update-pet"));
166 assert!(!n.contains(&"delete-pet"));
167 assert!(!n.contains(&"list-pets"));
168 }
169
170 #[test]
171 fn case_insensitive_methods() {
172 let methods = vec!["get".into()];
173 let result = filter_commands(cmds(), &[], &[], &methods);
174 assert!(names(&result).contains(&"list-pets"));
175 }
176}