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