1use std::fmt;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use crate::error::OpenApiError;
6use crate::normalize_tag;
7use crate::server::ToolMetadata;
8use crate::tool_generator::ToolGenerator;
9use oas3::Spec;
10use reqwest::Method;
11use serde_json::Value;
12use url::Url;
13
14#[derive(Debug, Clone)]
15pub enum OpenApiSpecLocation {
16 File(PathBuf),
17 Url(Url),
18}
19
20impl FromStr for OpenApiSpecLocation {
21 type Err = OpenApiError;
22
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 if s.starts_with("http://") || s.starts_with("https://") {
25 let url =
26 Url::parse(s).map_err(|e| OpenApiError::InvalidUrl(format!("Invalid URL: {e}")))?;
27 Ok(OpenApiSpecLocation::Url(url))
28 } else {
29 let path = PathBuf::from(s);
30 Ok(OpenApiSpecLocation::File(path))
31 }
32 }
33}
34
35impl OpenApiSpecLocation {
36 pub async fn load_spec(&self) -> Result<OpenApiSpec, OpenApiError> {
37 match self {
38 OpenApiSpecLocation::File(path) => {
39 OpenApiSpec::from_file(path.to_str().ok_or_else(|| {
40 OpenApiError::InvalidPath("Invalid file path encoding".to_string())
41 })?)
42 .await
43 }
44 OpenApiSpecLocation::Url(url) => OpenApiSpec::from_url(url).await,
45 }
46 }
47}
48
49impl fmt::Display for OpenApiSpecLocation {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 OpenApiSpecLocation::File(path) => write!(f, "{}", path.display()),
53 OpenApiSpecLocation::Url(url) => write!(f, "{url}"),
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
61pub struct OpenApiSpec {
62 pub spec: Spec,
63}
64
65impl OpenApiSpec {
66 pub async fn from_url(url: &Url) -> Result<Self, OpenApiError> {
68 let client = reqwest::Client::new();
69 let response = client.get(url.clone()).send().await?;
70 let text = response.text().await?;
71 let spec: Spec = serde_json::from_str(&text)?;
72
73 Ok(OpenApiSpec { spec })
74 }
75
76 pub async fn from_file(path: &str) -> Result<Self, OpenApiError> {
78 let content = tokio::fs::read_to_string(path).await?;
79 let spec: Spec = serde_json::from_str(&content)?;
80
81 Ok(OpenApiSpec { spec })
82 }
83
84 pub fn from_value(json_value: Value) -> Result<Self, OpenApiError> {
86 let spec: Spec = serde_json::from_value(json_value)?;
87 Ok(OpenApiSpec { spec })
88 }
89
90 pub fn to_tool_metadata(
92 &self,
93 tag_filter: Option<&[String]>,
94 method_filter: Option<&[reqwest::Method]>,
95 ) -> Result<Vec<ToolMetadata>, OpenApiError> {
96 let mut tools = Vec::new();
97
98 if let Some(paths) = &self.spec.paths {
99 for (path, path_item) in paths {
100 let operations = [
102 (Method::GET, &path_item.get),
103 (Method::POST, &path_item.post),
104 (Method::PUT, &path_item.put),
105 (Method::DELETE, &path_item.delete),
106 (Method::PATCH, &path_item.patch),
107 (Method::HEAD, &path_item.head),
108 (Method::OPTIONS, &path_item.options),
109 (Method::TRACE, &path_item.trace),
110 ];
111
112 for (method, operation_ref) in operations {
113 if let Some(operation) = operation_ref {
114 if let Some(filter_methods) = method_filter {
116 if !filter_methods.contains(&method) {
117 continue; }
119 }
120
121 if let Some(filter_tags) = tag_filter {
123 if !operation.tags.is_empty() {
124 let normalized_filter_tags: Vec<String> =
126 filter_tags.iter().map(|tag| normalize_tag(tag)).collect();
127
128 let has_matching_tag = operation.tags.iter().any(|operation_tag| {
129 let normalized_operation_tag = normalize_tag(operation_tag);
130 normalized_filter_tags.contains(&normalized_operation_tag)
131 });
132
133 if !has_matching_tag {
134 continue; }
136 } else {
137 continue; }
139 }
140
141 let tool_metadata = ToolGenerator::generate_tool_metadata(
142 operation,
143 method.to_string(),
144 path.clone(),
145 &self.spec,
146 )?;
147 tools.push(tool_metadata);
148 }
149 }
150 }
151 }
152
153 Ok(tools)
154 }
155
156 pub fn get_operation(
158 &self,
159 operation_id: &str,
160 ) -> Option<(&oas3::spec::Operation, String, String)> {
161 if let Some(paths) = &self.spec.paths {
162 for (path, path_item) in paths {
163 let operations = [
164 (Method::GET, &path_item.get),
165 (Method::POST, &path_item.post),
166 (Method::PUT, &path_item.put),
167 (Method::DELETE, &path_item.delete),
168 (Method::PATCH, &path_item.patch),
169 (Method::HEAD, &path_item.head),
170 (Method::OPTIONS, &path_item.options),
171 (Method::TRACE, &path_item.trace),
172 ];
173
174 for (method, operation_ref) in operations {
175 if let Some(operation) = operation_ref {
176 let default_id = format!(
177 "{}_{}",
178 method,
179 path.replace('/', "_").replace(['{', '}'], "")
180 );
181 let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
182
183 if op_id == operation_id {
184 return Some((operation, method.to_string(), path.clone()));
185 }
186 }
187 }
188 }
189 }
190 None
191 }
192
193 pub fn get_operation_ids(&self) -> Vec<String> {
195 let mut operation_ids = Vec::new();
196
197 if let Some(paths) = &self.spec.paths {
198 for (path, path_item) in paths {
199 let operations = [
200 (Method::GET, &path_item.get),
201 (Method::POST, &path_item.post),
202 (Method::PUT, &path_item.put),
203 (Method::DELETE, &path_item.delete),
204 (Method::PATCH, &path_item.patch),
205 (Method::HEAD, &path_item.head),
206 (Method::OPTIONS, &path_item.options),
207 (Method::TRACE, &path_item.trace),
208 ];
209
210 for (method, operation_ref) in operations {
211 if let Some(operation) = operation_ref {
212 let default_id = format!(
213 "{}_{}",
214 method,
215 path.replace('/', "_").replace(['{', '}'], "")
216 );
217 let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
218 operation_ids.push(op_id.to_string());
219 }
220 }
221 }
222 }
223
224 operation_ids
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use serde_json::json;
232
233 fn create_test_spec_with_tags() -> OpenApiSpec {
234 let spec_json = json!({
235 "openapi": "3.0.3",
236 "info": {
237 "title": "Test API",
238 "version": "1.0.0"
239 },
240 "paths": {
241 "/pets": {
242 "get": {
243 "operationId": "listPets",
244 "tags": ["pet", "list"],
245 "responses": {
246 "200": {
247 "description": "List of pets"
248 }
249 }
250 },
251 "post": {
252 "operationId": "createPet",
253 "tags": ["pet"],
254 "responses": {
255 "201": {
256 "description": "Pet created"
257 }
258 }
259 }
260 },
261 "/users": {
262 "get": {
263 "operationId": "listUsers",
264 "tags": ["user"],
265 "responses": {
266 "200": {
267 "description": "List of users"
268 }
269 }
270 }
271 },
272 "/admin": {
273 "get": {
274 "operationId": "adminPanel",
275 "tags": ["admin", "management"],
276 "responses": {
277 "200": {
278 "description": "Admin panel"
279 }
280 }
281 }
282 },
283 "/public": {
284 "get": {
285 "operationId": "publicEndpoint",
286 "responses": {
287 "200": {
288 "description": "Public endpoint with no tags"
289 }
290 }
291 }
292 }
293 }
294 });
295
296 OpenApiSpec::from_value(spec_json).expect("Failed to create test spec")
297 }
298
299 fn create_test_spec_with_mixed_case_tags() -> OpenApiSpec {
300 let spec_json = json!({
301 "openapi": "3.0.3",
302 "info": {
303 "title": "Test API with Mixed Case Tags",
304 "version": "1.0.0"
305 },
306 "paths": {
307 "/camel": {
308 "get": {
309 "operationId": "camelCaseOperation",
310 "tags": ["userManagement"],
311 "responses": {
312 "200": {
313 "description": "camelCase tag"
314 }
315 }
316 }
317 },
318 "/pascal": {
319 "get": {
320 "operationId": "pascalCaseOperation",
321 "tags": ["UserManagement"],
322 "responses": {
323 "200": {
324 "description": "PascalCase tag"
325 }
326 }
327 }
328 },
329 "/snake": {
330 "get": {
331 "operationId": "snakeCaseOperation",
332 "tags": ["user_management"],
333 "responses": {
334 "200": {
335 "description": "snake_case tag"
336 }
337 }
338 }
339 },
340 "/screaming": {
341 "get": {
342 "operationId": "screamingCaseOperation",
343 "tags": ["USER_MANAGEMENT"],
344 "responses": {
345 "200": {
346 "description": "SCREAMING_SNAKE_CASE tag"
347 }
348 }
349 }
350 },
351 "/kebab": {
352 "get": {
353 "operationId": "kebabCaseOperation",
354 "tags": ["user-management"],
355 "responses": {
356 "200": {
357 "description": "kebab-case tag"
358 }
359 }
360 }
361 },
362 "/mixed": {
363 "get": {
364 "operationId": "mixedCaseOperation",
365 "tags": ["XMLHttpRequest", "HTTPSConnection", "APIKey"],
366 "responses": {
367 "200": {
368 "description": "Mixed case with acronyms"
369 }
370 }
371 }
372 }
373 }
374 });
375
376 OpenApiSpec::from_value(spec_json).expect("Failed to create test spec")
377 }
378
379 fn create_test_spec_with_methods() -> OpenApiSpec {
380 let spec_json = json!({
381 "openapi": "3.0.3",
382 "info": {
383 "title": "Test API with Multiple Methods",
384 "version": "1.0.0"
385 },
386 "paths": {
387 "/users": {
388 "get": {
389 "operationId": "listUsers",
390 "tags": ["user"],
391 "responses": {
392 "200": {
393 "description": "List of users"
394 }
395 }
396 },
397 "post": {
398 "operationId": "createUser",
399 "tags": ["user"],
400 "responses": {
401 "201": {
402 "description": "User created"
403 }
404 }
405 },
406 "put": {
407 "operationId": "updateUser",
408 "tags": ["user"],
409 "responses": {
410 "200": {
411 "description": "User updated"
412 }
413 }
414 },
415 "delete": {
416 "operationId": "deleteUser",
417 "tags": ["user"],
418 "responses": {
419 "204": {
420 "description": "User deleted"
421 }
422 }
423 }
424 },
425 "/pets": {
426 "get": {
427 "operationId": "listPets",
428 "tags": ["pet"],
429 "responses": {
430 "200": {
431 "description": "List of pets"
432 }
433 }
434 },
435 "post": {
436 "operationId": "createPet",
437 "tags": ["pet"],
438 "responses": {
439 "201": {
440 "description": "Pet created"
441 }
442 }
443 },
444 "patch": {
445 "operationId": "patchPet",
446 "tags": ["pet"],
447 "responses": {
448 "200": {
449 "description": "Pet patched"
450 }
451 }
452 }
453 },
454 "/health": {
455 "head": {
456 "operationId": "healthCheck",
457 "tags": ["health"],
458 "responses": {
459 "200": {
460 "description": "Health check"
461 }
462 }
463 },
464 "options": {
465 "operationId": "healthOptions",
466 "tags": ["health"],
467 "responses": {
468 "200": {
469 "description": "Health options"
470 }
471 }
472 }
473 }
474 }
475 });
476
477 OpenApiSpec::from_value(spec_json).expect("Failed to create test spec")
478 }
479
480 #[test]
481 fn test_tag_filtering_no_filter() {
482 let spec = create_test_spec_with_tags();
483 let tools = spec
484 .to_tool_metadata(None, None)
485 .expect("Failed to generate tools");
486
487 assert_eq!(tools.len(), 5);
489
490 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
491 assert!(tool_names.contains(&"listPets"));
492 assert!(tool_names.contains(&"createPet"));
493 assert!(tool_names.contains(&"listUsers"));
494 assert!(tool_names.contains(&"adminPanel"));
495 assert!(tool_names.contains(&"publicEndpoint"));
496 }
497
498 #[test]
499 fn test_tag_filtering_single_tag() {
500 let spec = create_test_spec_with_tags();
501 let filter_tags = vec!["pet".to_string()];
502 let tools = spec
503 .to_tool_metadata(Some(&filter_tags), None)
504 .expect("Failed to generate tools");
505
506 assert_eq!(tools.len(), 2);
508
509 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
510 assert!(tool_names.contains(&"listPets"));
511 assert!(tool_names.contains(&"createPet"));
512 assert!(!tool_names.contains(&"listUsers"));
513 assert!(!tool_names.contains(&"adminPanel"));
514 assert!(!tool_names.contains(&"publicEndpoint"));
515 }
516
517 #[test]
518 fn test_tag_filtering_multiple_tags() {
519 let spec = create_test_spec_with_tags();
520 let filter_tags = vec!["pet".to_string(), "user".to_string()];
521 let tools = spec
522 .to_tool_metadata(Some(&filter_tags), None)
523 .expect("Failed to generate tools");
524
525 assert_eq!(tools.len(), 3);
527
528 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
529 assert!(tool_names.contains(&"listPets"));
530 assert!(tool_names.contains(&"createPet"));
531 assert!(tool_names.contains(&"listUsers"));
532 assert!(!tool_names.contains(&"adminPanel"));
533 assert!(!tool_names.contains(&"publicEndpoint"));
534 }
535
536 #[test]
537 fn test_tag_filtering_or_logic() {
538 let spec = create_test_spec_with_tags();
539 let filter_tags = vec!["list".to_string()]; let tools = spec
541 .to_tool_metadata(Some(&filter_tags), None)
542 .expect("Failed to generate tools");
543
544 assert_eq!(tools.len(), 1);
546
547 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
548 assert!(tool_names.contains(&"listPets")); assert!(!tool_names.contains(&"createPet")); }
551
552 #[test]
553 fn test_tag_filtering_no_matching_tags() {
554 let spec = create_test_spec_with_tags();
555 let filter_tags = vec!["nonexistent".to_string()];
556 let tools = spec
557 .to_tool_metadata(Some(&filter_tags), None)
558 .expect("Failed to generate tools");
559
560 assert_eq!(tools.len(), 0);
562 }
563
564 #[test]
565 fn test_tag_filtering_excludes_operations_without_tags() {
566 let spec = create_test_spec_with_tags();
567 let filter_tags = vec!["admin".to_string()];
568 let tools = spec
569 .to_tool_metadata(Some(&filter_tags), None)
570 .expect("Failed to generate tools");
571
572 assert_eq!(tools.len(), 1);
574
575 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
576 assert!(tool_names.contains(&"adminPanel"));
577 assert!(!tool_names.contains(&"publicEndpoint")); }
579
580 #[test]
581 fn test_tag_normalization_all_cases_match() {
582 let spec = create_test_spec_with_mixed_case_tags();
583 let filter_tags = vec!["user-management".to_string()]; let tools = spec
585 .to_tool_metadata(Some(&filter_tags), None)
586 .expect("Failed to generate tools");
587
588 assert_eq!(tools.len(), 5);
590
591 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
592 assert!(tool_names.contains(&"camelCaseOperation")); assert!(tool_names.contains(&"pascalCaseOperation")); assert!(tool_names.contains(&"snakeCaseOperation")); assert!(tool_names.contains(&"screamingCaseOperation")); assert!(tool_names.contains(&"kebabCaseOperation")); assert!(!tool_names.contains(&"mixedCaseOperation")); }
599
600 #[test]
601 fn test_tag_normalization_camel_case_filter() {
602 let spec = create_test_spec_with_mixed_case_tags();
603 let filter_tags = vec!["userManagement".to_string()]; let tools = spec
605 .to_tool_metadata(Some(&filter_tags), None)
606 .expect("Failed to generate tools");
607
608 assert_eq!(tools.len(), 5);
610
611 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
612 assert!(tool_names.contains(&"camelCaseOperation"));
613 assert!(tool_names.contains(&"pascalCaseOperation"));
614 assert!(tool_names.contains(&"snakeCaseOperation"));
615 assert!(tool_names.contains(&"screamingCaseOperation"));
616 assert!(tool_names.contains(&"kebabCaseOperation"));
617 }
618
619 #[test]
620 fn test_tag_normalization_snake_case_filter() {
621 let spec = create_test_spec_with_mixed_case_tags();
622 let filter_tags = vec!["user_management".to_string()]; let tools = spec
624 .to_tool_metadata(Some(&filter_tags), None)
625 .expect("Failed to generate tools");
626
627 assert_eq!(tools.len(), 5);
629 }
630
631 #[test]
632 fn test_tag_normalization_acronyms() {
633 let spec = create_test_spec_with_mixed_case_tags();
634 let filter_tags = vec!["xml-http-request".to_string()]; let tools = spec
636 .to_tool_metadata(Some(&filter_tags), None)
637 .expect("Failed to generate tools");
638
639 assert_eq!(tools.len(), 1);
641
642 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
643 assert!(tool_names.contains(&"mixedCaseOperation"));
644 }
645
646 #[test]
647 fn test_tag_normalization_multiple_mixed_filters() {
648 let spec = create_test_spec_with_mixed_case_tags();
649 let filter_tags = vec![
650 "user-management".to_string(), "HTTPSConnection".to_string(), ];
653 let tools = spec
654 .to_tool_metadata(Some(&filter_tags), None)
655 .expect("Failed to generate tools");
656
657 assert_eq!(tools.len(), 6);
659
660 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
661 assert!(tool_names.contains(&"camelCaseOperation"));
662 assert!(tool_names.contains(&"pascalCaseOperation"));
663 assert!(tool_names.contains(&"snakeCaseOperation"));
664 assert!(tool_names.contains(&"screamingCaseOperation"));
665 assert!(tool_names.contains(&"kebabCaseOperation"));
666 assert!(tool_names.contains(&"mixedCaseOperation"));
667 }
668
669 #[test]
670 fn test_tag_filtering_empty_filter_list() {
671 let spec = create_test_spec_with_tags();
672 let filter_tags: Vec<String> = vec![];
673 let tools = spec
674 .to_tool_metadata(Some(&filter_tags), None)
675 .expect("Failed to generate tools");
676
677 assert_eq!(tools.len(), 0);
679 }
680
681 #[test]
682 fn test_tag_filtering_complex_scenario() {
683 let spec = create_test_spec_with_tags();
684 let filter_tags = vec!["management".to_string(), "list".to_string()];
685 let tools = spec
686 .to_tool_metadata(Some(&filter_tags), None)
687 .expect("Failed to generate tools");
688
689 assert_eq!(tools.len(), 2);
691
692 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
693 assert!(tool_names.contains(&"adminPanel"));
694 assert!(tool_names.contains(&"listPets"));
695 assert!(!tool_names.contains(&"createPet"));
696 assert!(!tool_names.contains(&"listUsers"));
697 assert!(!tool_names.contains(&"publicEndpoint"));
698 }
699
700 #[test]
701 fn test_method_filtering_no_filter() {
702 let spec = create_test_spec_with_methods();
703 let tools = spec
704 .to_tool_metadata(None, None)
705 .expect("Failed to generate tools");
706
707 assert_eq!(tools.len(), 9);
709
710 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
711 assert!(tool_names.contains(&"listUsers")); assert!(tool_names.contains(&"createUser")); assert!(tool_names.contains(&"updateUser")); assert!(tool_names.contains(&"deleteUser")); assert!(tool_names.contains(&"listPets")); assert!(tool_names.contains(&"createPet")); assert!(tool_names.contains(&"patchPet")); assert!(tool_names.contains(&"healthCheck")); assert!(tool_names.contains(&"healthOptions")); }
721
722 #[test]
723 fn test_method_filtering_single_method() {
724 use reqwest::Method;
725
726 let spec = create_test_spec_with_methods();
727 let filter_methods = vec![Method::GET];
728 let tools = spec
729 .to_tool_metadata(None, Some(&filter_methods))
730 .expect("Failed to generate tools");
731
732 assert_eq!(tools.len(), 2);
734
735 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
736 assert!(tool_names.contains(&"listUsers")); assert!(tool_names.contains(&"listPets")); assert!(!tool_names.contains(&"createUser")); assert!(!tool_names.contains(&"updateUser")); assert!(!tool_names.contains(&"deleteUser")); assert!(!tool_names.contains(&"createPet")); assert!(!tool_names.contains(&"patchPet")); assert!(!tool_names.contains(&"healthCheck")); assert!(!tool_names.contains(&"healthOptions")); }
746
747 #[test]
748 fn test_method_filtering_multiple_methods() {
749 use reqwest::Method;
750
751 let spec = create_test_spec_with_methods();
752 let filter_methods = vec![Method::GET, Method::POST];
753 let tools = spec
754 .to_tool_metadata(None, Some(&filter_methods))
755 .expect("Failed to generate tools");
756
757 assert_eq!(tools.len(), 4);
759
760 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
761 assert!(tool_names.contains(&"listUsers")); assert!(tool_names.contains(&"createUser")); assert!(tool_names.contains(&"listPets")); assert!(tool_names.contains(&"createPet")); assert!(!tool_names.contains(&"updateUser")); assert!(!tool_names.contains(&"deleteUser")); assert!(!tool_names.contains(&"patchPet")); assert!(!tool_names.contains(&"healthCheck")); assert!(!tool_names.contains(&"healthOptions")); }
771
772 #[test]
773 fn test_method_filtering_uncommon_methods() {
774 use reqwest::Method;
775
776 let spec = create_test_spec_with_methods();
777 let filter_methods = vec![Method::HEAD, Method::OPTIONS, Method::PATCH];
778 let tools = spec
779 .to_tool_metadata(None, Some(&filter_methods))
780 .expect("Failed to generate tools");
781
782 assert_eq!(tools.len(), 3);
784
785 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
786 assert!(tool_names.contains(&"patchPet")); assert!(tool_names.contains(&"healthCheck")); assert!(tool_names.contains(&"healthOptions")); assert!(!tool_names.contains(&"listUsers")); assert!(!tool_names.contains(&"createUser")); assert!(!tool_names.contains(&"updateUser")); assert!(!tool_names.contains(&"deleteUser")); assert!(!tool_names.contains(&"listPets")); assert!(!tool_names.contains(&"createPet")); }
796
797 #[test]
798 fn test_method_and_tag_filtering_combined() {
799 use reqwest::Method;
800
801 let spec = create_test_spec_with_methods();
802 let filter_tags = vec!["user".to_string()];
803 let filter_methods = vec![Method::GET, Method::POST];
804 let tools = spec
805 .to_tool_metadata(Some(&filter_tags), Some(&filter_methods))
806 .expect("Failed to generate tools");
807
808 assert_eq!(tools.len(), 2);
810
811 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
812 assert!(tool_names.contains(&"listUsers")); assert!(tool_names.contains(&"createUser")); assert!(!tool_names.contains(&"updateUser")); assert!(!tool_names.contains(&"deleteUser")); assert!(!tool_names.contains(&"listPets")); assert!(!tool_names.contains(&"createPet")); assert!(!tool_names.contains(&"patchPet")); assert!(!tool_names.contains(&"healthCheck")); assert!(!tool_names.contains(&"healthOptions")); }
822
823 #[test]
824 fn test_method_filtering_no_matching_methods() {
825 use reqwest::Method;
826
827 let spec = create_test_spec_with_methods();
828 let filter_methods = vec![Method::TRACE]; let tools = spec
830 .to_tool_metadata(None, Some(&filter_methods))
831 .expect("Failed to generate tools");
832
833 assert_eq!(tools.len(), 0);
835 }
836
837 #[test]
838 fn test_method_filtering_empty_filter_list() {
839 let spec = create_test_spec_with_methods();
840 let filter_methods: Vec<reqwest::Method> = vec![];
841 let tools = spec
842 .to_tool_metadata(None, Some(&filter_methods))
843 .expect("Failed to generate tools");
844
845 assert_eq!(tools.len(), 0);
847 }
848}