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