1use crate::error::Error;
2use crate::normalize_tag;
3use crate::tool::ToolMetadata;
4use crate::tool_generator::ToolGenerator;
5use bon::Builder;
6use oas3::Spec as Oas3Spec;
7use reqwest::Method;
8use serde::de::IntoDeserializer;
9use serde_json::Value;
10
11#[derive(Debug, Clone)]
14pub struct Spec {
15 pub spec: Oas3Spec,
16}
17
18impl Spec {
19 pub fn from_value(json_value: Value) -> Result<Self, Error> {
26 let spec: Oas3Spec = serde_path_to_error::deserialize(json_value.into_deserializer())
27 .map_err(|err| Error::JsonAtPath {
28 path: err.path().to_string(),
29 source: err.into_inner(),
30 })?;
31 Ok(Spec { spec })
32 }
33
34 pub fn to_tool_metadata(
36 &self,
37 filters: Option<&Filters>,
38 skip_tool_descriptions: bool,
39 skip_parameter_descriptions: bool,
40 parameter_examples_in_description: bool,
41 ) -> Result<Vec<ToolMetadata>, Error> {
42 let mut tools = Vec::new();
43
44 if let Some(paths) = &self.spec.paths {
45 for (path, path_item) in paths {
46 let operations = [
48 (Method::GET, &path_item.get),
49 (Method::POST, &path_item.post),
50 (Method::PUT, &path_item.put),
51 (Method::DELETE, &path_item.delete),
52 (Method::PATCH, &path_item.patch),
53 (Method::HEAD, &path_item.head),
54 (Method::OPTIONS, &path_item.options),
55 (Method::TRACE, &path_item.trace),
56 ];
57
58 for (method, operation_ref) in operations {
59 if let Some(operation) = operation_ref {
60 if let Some(filters) = filters {
61 match &filters.methods {
63 Some(Filter::Include(m)) if !m.contains(&method) => continue,
64 Some(Filter::Exclude(m)) if m.contains(&method) => continue,
65 _ => {}
66 }
67
68 match (&filters.tags, operation.tags.is_empty()) {
70 (Some(Filter::Include(tags)), false) => {
71 let normalized_filter_tags: Vec<String> =
72 tags.iter().map(|tag| normalize_tag(tag)).collect();
73
74 let has_matching_tag =
75 operation.tags.iter().any(|operation_tag| {
76 let normalized_operation_tag =
77 normalize_tag(operation_tag);
78 normalized_filter_tags
79 .contains(&normalized_operation_tag)
80 });
81
82 if !has_matching_tag {
83 continue; }
85 }
86 (Some(Filter::Exclude(tags)), false) => {
87 let normalized_filter_tags: Vec<String> =
88 tags.iter().map(|tag| normalize_tag(tag)).collect();
89
90 let has_matching_tag =
91 operation.tags.iter().any(|operation_tag| {
92 let normalized_operation_tag =
93 normalize_tag(operation_tag);
94 normalized_filter_tags
95 .contains(&normalized_operation_tag)
96 });
97
98 if has_matching_tag {
99 continue; }
101 }
102 (_, true) => continue, _ => {}
104 }
105
106 match (operation.operation_id.as_ref(), &filters.operations_id) {
108 (Some(op), Some(Filter::Include(ops))) if !ops.contains(op) => {
109 continue;
110 }
111 (Some(op), Some(Filter::Exclude(ops))) if ops.contains(op) => {
112 continue;
113 }
114 _ => {}
115 }
116 }
117
118 let tool_metadata = ToolGenerator::generate_tool_metadata(
119 operation,
120 method.to_string(),
121 path.clone(),
122 &self.spec,
123 skip_tool_descriptions,
124 skip_parameter_descriptions,
125 parameter_examples_in_description,
126 )?;
127 tools.push(tool_metadata);
128 }
129 }
130 }
131 }
132
133 Ok(tools)
134 }
135
136 #[allow(
142 clippy::too_many_arguments,
143 reason = "mirrors the existing description/skip flags threaded through tool generation"
144 )]
145 pub fn to_openapi_tools(
146 &self,
147 filters: Option<&Filters>,
148 base_url: Option<url::Url>,
149 default_headers: Option<reqwest::header::HeaderMap>,
150 skip_tool_descriptions: bool,
151 skip_parameter_descriptions: bool,
152 parameter_examples_in_description: bool,
153 insecure: bool,
154 ) -> Result<Vec<crate::tool::Tool>, Error> {
155 let tools_metadata = self.to_tool_metadata(
157 filters,
158 skip_tool_descriptions,
159 skip_parameter_descriptions,
160 parameter_examples_in_description,
161 )?;
162
163 crate::tool_generator::ToolGenerator::generate_openapi_tools(
165 tools_metadata,
166 base_url,
167 default_headers,
168 insecure,
169 )
170 }
171
172 pub fn get_operation(
174 &self,
175 operation_id: &str,
176 ) -> Option<(&oas3::spec::Operation, String, String)> {
177 if let Some(paths) = &self.spec.paths {
178 for (path, path_item) in paths {
179 let operations = [
180 (Method::GET, &path_item.get),
181 (Method::POST, &path_item.post),
182 (Method::PUT, &path_item.put),
183 (Method::DELETE, &path_item.delete),
184 (Method::PATCH, &path_item.patch),
185 (Method::HEAD, &path_item.head),
186 (Method::OPTIONS, &path_item.options),
187 (Method::TRACE, &path_item.trace),
188 ];
189
190 for (method, operation_ref) in operations {
191 if let Some(operation) = operation_ref {
192 let default_id = format!(
193 "{}_{}",
194 method,
195 path.replace('/', "_").replace(['{', '}'], "")
196 );
197 let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
198
199 if op_id == operation_id {
200 return Some((operation, method.to_string(), path.clone()));
201 }
202 }
203 }
204 }
205 }
206 None
207 }
208
209 pub fn get_operation_ids(&self) -> Vec<String> {
211 let mut operation_ids = Vec::new();
212
213 if let Some(paths) = &self.spec.paths {
214 for (path, path_item) in paths {
215 let operations = [
216 (Method::GET, &path_item.get),
217 (Method::POST, &path_item.post),
218 (Method::PUT, &path_item.put),
219 (Method::DELETE, &path_item.delete),
220 (Method::PATCH, &path_item.patch),
221 (Method::HEAD, &path_item.head),
222 (Method::OPTIONS, &path_item.options),
223 (Method::TRACE, &path_item.trace),
224 ];
225
226 for (method, operation_ref) in operations {
227 if let Some(operation) = operation_ref {
228 let default_id = format!(
229 "{}_{}",
230 method,
231 path.replace('/', "_").replace(['{', '}'], "")
232 );
233 let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
234 operation_ids.push(op_id.to_string());
235 }
236 }
237 }
238 }
239
240 operation_ids
241 }
242}
243
244#[derive(Builder, Debug, Clone)]
245pub struct Filters {
246 pub tags: Option<Filter<String>>,
247 pub methods: Option<Filter<reqwest::Method>>,
248 pub operations_id: Option<Filter<String>>,
249}
250
251#[derive(Debug, Clone)]
252pub enum Filter<T> {
253 Include(Vec<T>),
254 Exclude(Vec<T>),
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use serde_json::json;
261
262 fn create_test_spec_with_tags() -> Spec {
263 let spec_json = json!({
264 "openapi": "3.0.3",
265 "info": {
266 "title": "Test API",
267 "version": "1.0.0"
268 },
269 "paths": {
270 "/pets": {
271 "get": {
272 "operationId": "listPets",
273 "tags": ["pet", "list"],
274 "responses": {
275 "200": {
276 "description": "List of pets"
277 }
278 }
279 },
280 "post": {
281 "operationId": "createPet",
282 "tags": ["pet"],
283 "responses": {
284 "201": {
285 "description": "Pet created"
286 }
287 }
288 }
289 },
290 "/users": {
291 "get": {
292 "operationId": "listUsers",
293 "tags": ["user"],
294 "responses": {
295 "200": {
296 "description": "List of users"
297 }
298 }
299 }
300 },
301 "/admin": {
302 "get": {
303 "operationId": "adminPanel",
304 "tags": ["admin", "management"],
305 "responses": {
306 "200": {
307 "description": "Admin panel"
308 }
309 }
310 }
311 },
312 "/public": {
313 "get": {
314 "operationId": "publicEndpoint",
315 "responses": {
316 "200": {
317 "description": "Public endpoint with no tags"
318 }
319 }
320 }
321 }
322 }
323 });
324
325 Spec::from_value(spec_json).expect("Failed to create test spec")
326 }
327
328 fn create_test_spec_with_mixed_case_tags() -> Spec {
329 let spec_json = json!({
330 "openapi": "3.0.3",
331 "info": {
332 "title": "Test API with Mixed Case Tags",
333 "version": "1.0.0"
334 },
335 "paths": {
336 "/camel": {
337 "get": {
338 "operationId": "camelCaseOperation",
339 "tags": ["userManagement"],
340 "responses": {
341 "200": {
342 "description": "camelCase tag"
343 }
344 }
345 }
346 },
347 "/pascal": {
348 "get": {
349 "operationId": "pascalCaseOperation",
350 "tags": ["UserManagement"],
351 "responses": {
352 "200": {
353 "description": "PascalCase tag"
354 }
355 }
356 }
357 },
358 "/snake": {
359 "get": {
360 "operationId": "snakeCaseOperation",
361 "tags": ["user_management"],
362 "responses": {
363 "200": {
364 "description": "snake_case tag"
365 }
366 }
367 }
368 },
369 "/screaming": {
370 "get": {
371 "operationId": "screamingCaseOperation",
372 "tags": ["USER_MANAGEMENT"],
373 "responses": {
374 "200": {
375 "description": "SCREAMING_SNAKE_CASE tag"
376 }
377 }
378 }
379 },
380 "/kebab": {
381 "get": {
382 "operationId": "kebabCaseOperation",
383 "tags": ["user-management"],
384 "responses": {
385 "200": {
386 "description": "kebab-case tag"
387 }
388 }
389 }
390 },
391 "/mixed": {
392 "get": {
393 "operationId": "mixedCaseOperation",
394 "tags": ["XMLHttpRequest", "HTTPSConnection", "APIKey"],
395 "responses": {
396 "200": {
397 "description": "Mixed case with acronyms"
398 }
399 }
400 }
401 }
402 }
403 });
404
405 Spec::from_value(spec_json).expect("Failed to create test spec")
406 }
407
408 fn create_test_spec_with_methods() -> Spec {
409 let spec_json = json!({
410 "openapi": "3.0.3",
411 "info": {
412 "title": "Test API with Multiple Methods",
413 "version": "1.0.0"
414 },
415 "paths": {
416 "/users": {
417 "get": {
418 "operationId": "listUsers",
419 "tags": ["user"],
420 "responses": {
421 "200": {
422 "description": "List of users"
423 }
424 }
425 },
426 "post": {
427 "operationId": "createUser",
428 "tags": ["user"],
429 "responses": {
430 "201": {
431 "description": "User created"
432 }
433 }
434 },
435 "put": {
436 "operationId": "updateUser",
437 "tags": ["user"],
438 "responses": {
439 "200": {
440 "description": "User updated"
441 }
442 }
443 },
444 "delete": {
445 "operationId": "deleteUser",
446 "tags": ["user"],
447 "responses": {
448 "204": {
449 "description": "User deleted"
450 }
451 }
452 }
453 },
454 "/pets": {
455 "get": {
456 "operationId": "listPets",
457 "tags": ["pet"],
458 "responses": {
459 "200": {
460 "description": "List of pets"
461 }
462 }
463 },
464 "post": {
465 "operationId": "createPet",
466 "tags": ["pet"],
467 "responses": {
468 "201": {
469 "description": "Pet created"
470 }
471 }
472 },
473 "patch": {
474 "operationId": "patchPet",
475 "tags": ["pet"],
476 "responses": {
477 "200": {
478 "description": "Pet patched"
479 }
480 }
481 }
482 },
483 "/health": {
484 "head": {
485 "operationId": "healthCheck",
486 "tags": ["health"],
487 "responses": {
488 "200": {
489 "description": "Health check"
490 }
491 }
492 },
493 "options": {
494 "operationId": "healthOptions",
495 "tags": ["health"],
496 "responses": {
497 "200": {
498 "description": "Health options"
499 }
500 }
501 }
502 }
503 }
504 });
505
506 Spec::from_value(spec_json).expect("Failed to create test spec")
507 }
508
509 #[test]
510 fn test_tag_filtering_no_filter() {
511 let spec = create_test_spec_with_tags();
512 let tools = spec
513 .to_tool_metadata(None, false, false, false)
514 .expect("Failed to generate tools");
515
516 assert_eq!(tools.len(), 5);
518
519 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
520 assert!(tool_names.contains(&"listPets"));
521 assert!(tool_names.contains(&"createPet"));
522 assert!(tool_names.contains(&"listUsers"));
523 assert!(tool_names.contains(&"adminPanel"));
524 assert!(tool_names.contains(&"publicEndpoint"));
525 }
526
527 #[test]
528 fn test_tag_filtering_single_tag() {
529 let spec = create_test_spec_with_tags();
530 let filters = Some(
531 Filters::builder()
532 .tags(Filter::Include(vec!["pet".to_string()]))
533 .build(),
534 );
535 let tools = spec
536 .to_tool_metadata(filters.as_ref(), false, false, false)
537 .expect("Failed to generate tools");
538
539 assert_eq!(tools.len(), 2);
541
542 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
543 assert!(tool_names.contains(&"listPets"));
544 assert!(tool_names.contains(&"createPet"));
545 assert!(!tool_names.contains(&"listUsers"));
546 assert!(!tool_names.contains(&"adminPanel"));
547 assert!(!tool_names.contains(&"publicEndpoint"));
548 }
549
550 #[test]
551 fn test_tag_filtering_multiple_tags() {
552 let spec = create_test_spec_with_tags();
553 let filters = Some(
554 Filters::builder()
555 .tags(Filter::Include(vec!["pet".to_string(), "user".to_string()]))
556 .build(),
557 );
558 let tools = spec
559 .to_tool_metadata(filters.as_ref(), false, false, false)
560 .expect("Failed to generate tools");
561
562 assert_eq!(tools.len(), 3);
564
565 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
566 assert!(tool_names.contains(&"listPets"));
567 assert!(tool_names.contains(&"createPet"));
568 assert!(tool_names.contains(&"listUsers"));
569 assert!(!tool_names.contains(&"adminPanel"));
570 assert!(!tool_names.contains(&"publicEndpoint"));
571 }
572
573 #[test]
574 fn test_tag_filtering_or_logic() {
575 let spec = create_test_spec_with_tags();
576 let filters = Some(
577 Filters::builder()
578 .tags(Filter::Include(vec!["list".to_string()]))
579 .build(),
580 );
581 let tools = spec
582 .to_tool_metadata(filters.as_ref(), false, false, false)
583 .expect("Failed to generate tools");
584
585 assert_eq!(tools.len(), 1);
587
588 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
589 assert!(tool_names.contains(&"listPets")); assert!(!tool_names.contains(&"createPet")); }
592
593 #[test]
594 fn test_tag_filtering_no_matching_tags() {
595 let spec = create_test_spec_with_tags();
596 let filters = Some(
597 Filters::builder()
598 .tags(Filter::Include(vec!["nonexistent".to_string()]))
599 .build(),
600 );
601 let tools = spec
602 .to_tool_metadata(filters.as_ref(), false, false, false)
603 .expect("Failed to generate tools");
604
605 assert_eq!(tools.len(), 0);
607 }
608
609 #[test]
610 fn test_tag_filtering_excludes_operations_without_tags() {
611 let spec = create_test_spec_with_tags();
612 let filters = Some(
613 Filters::builder()
614 .tags(Filter::Include(vec!["admin".to_string()]))
615 .build(),
616 );
617 let tools = spec
618 .to_tool_metadata(filters.as_ref(), false, false, false)
619 .expect("Failed to generate tools");
620
621 assert_eq!(tools.len(), 1);
623
624 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
625 assert!(tool_names.contains(&"adminPanel"));
626 assert!(!tool_names.contains(&"publicEndpoint")); }
628
629 #[test]
630 fn test_tag_normalization_all_cases_match() {
631 let spec = create_test_spec_with_mixed_case_tags();
632 let filters = Some(
633 Filters::builder()
634 .tags(Filter::Include(vec!["user-management".to_string()]))
635 .build(),
636 );
637 let tools = spec
638 .to_tool_metadata(filters.as_ref(), false, false, false)
639 .expect("Failed to generate tools");
640
641 assert_eq!(tools.len(), 5);
643
644 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
645 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")); }
652
653 #[test]
654 fn test_tag_normalization_camel_case_filter() {
655 let spec = create_test_spec_with_mixed_case_tags();
656 let filters = Some(
657 Filters::builder()
658 .tags(Filter::Include(vec!["userManagement".to_string()]))
659 .build(),
660 );
661 let tools = spec
662 .to_tool_metadata(filters.as_ref(), false, false, false)
663 .expect("Failed to generate tools");
664
665 assert_eq!(tools.len(), 5);
667
668 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
669 assert!(tool_names.contains(&"camelCaseOperation"));
670 assert!(tool_names.contains(&"pascalCaseOperation"));
671 assert!(tool_names.contains(&"snakeCaseOperation"));
672 assert!(tool_names.contains(&"screamingCaseOperation"));
673 assert!(tool_names.contains(&"kebabCaseOperation"));
674 }
675
676 #[test]
677 fn test_tag_normalization_snake_case_filter() {
678 let spec = create_test_spec_with_mixed_case_tags();
679 let filters = Some(
680 Filters::builder()
681 .tags(Filter::Include(vec!["user_management".to_string()]))
682 .build(),
683 );
684 let tools = spec
685 .to_tool_metadata(filters.as_ref(), false, false, false)
686 .expect("Failed to generate tools");
687
688 assert_eq!(tools.len(), 5);
690 }
691
692 #[test]
693 fn test_tag_normalization_acronyms() {
694 let spec = create_test_spec_with_mixed_case_tags();
695 let filters = Some(
696 Filters::builder()
697 .tags(Filter::Include(vec!["xml-http-request".to_string()]))
698 .build(),
699 );
700 let tools = spec
701 .to_tool_metadata(filters.as_ref(), false, false, false)
702 .expect("Failed to generate tools");
703
704 assert_eq!(tools.len(), 1);
706
707 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
708 assert!(tool_names.contains(&"mixedCaseOperation"));
709 }
710
711 #[test]
712 fn test_tag_normalization_multiple_mixed_filters() {
713 let spec = create_test_spec_with_mixed_case_tags();
714 let filters = Some(
715 Filters::builder()
716 .tags(Filter::Include(vec![
717 "user-management".to_string(),
718 "HTTPSConnection".to_string(),
719 ]))
720 .build(),
721 );
722 let tools = spec
723 .to_tool_metadata(filters.as_ref(), false, false, false)
724 .expect("Failed to generate tools");
725
726 assert_eq!(tools.len(), 6);
728
729 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
730 assert!(tool_names.contains(&"camelCaseOperation"));
731 assert!(tool_names.contains(&"pascalCaseOperation"));
732 assert!(tool_names.contains(&"snakeCaseOperation"));
733 assert!(tool_names.contains(&"screamingCaseOperation"));
734 assert!(tool_names.contains(&"kebabCaseOperation"));
735 assert!(tool_names.contains(&"mixedCaseOperation"));
736 }
737
738 #[test]
739 fn test_tag_filtering_empty_filter_list() {
740 let spec = create_test_spec_with_tags();
741 let filters = Some(Filters::builder().tags(Filter::Include(vec![])).build());
742 let tools = spec
743 .to_tool_metadata(filters.as_ref(), false, false, false)
744 .expect("Failed to generate tools");
745
746 dbg!(&tools);
748 assert_eq!(tools.len(), 0);
749 }
750
751 #[test]
752 fn test_tag_filtering_complex_scenario() {
753 let spec = create_test_spec_with_tags();
754 let filters = Some(
755 Filters::builder()
756 .tags(Filter::Include(vec![
757 "management".to_string(),
758 "list".to_string(),
759 ]))
760 .build(),
761 );
762 let tools = spec
763 .to_tool_metadata(filters.as_ref(), false, false, false)
764 .expect("Failed to generate tools");
765
766 assert_eq!(tools.len(), 2);
768
769 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
770 assert!(tool_names.contains(&"adminPanel"));
771 assert!(tool_names.contains(&"listPets"));
772 assert!(!tool_names.contains(&"createPet"));
773 assert!(!tool_names.contains(&"listUsers"));
774 assert!(!tool_names.contains(&"publicEndpoint"));
775 }
776
777 #[test]
778 fn test_method_filtering_no_filter() {
779 let spec = create_test_spec_with_methods();
780 let tools = spec
781 .to_tool_metadata(None, false, false, false)
782 .expect("Failed to generate tools");
783
784 assert_eq!(tools.len(), 9);
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(&"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")); }
798
799 #[test]
800 fn test_method_filtering_single_method() {
801 use reqwest::Method;
802
803 let spec = create_test_spec_with_methods();
804 let filters = Some(
805 Filters::builder()
806 .methods(Filter::Include(vec![Method::GET]))
807 .build(),
808 );
809 let tools = spec
810 .to_tool_metadata(filters.as_ref(), false, false, false)
811 .expect("Failed to generate tools");
812
813 assert_eq!(tools.len(), 2);
815
816 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
817 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")); }
827
828 #[test]
829 fn test_method_filtering_multiple_methods() {
830 use reqwest::Method;
831
832 let spec = create_test_spec_with_methods();
833 let filters = Some(
834 Filters::builder()
835 .methods(Filter::Include(vec![Method::GET, Method::POST]))
836 .build(),
837 );
838 let tools = spec
839 .to_tool_metadata(filters.as_ref(), false, false, false)
840 .expect("Failed to generate tools");
841
842 assert_eq!(tools.len(), 4);
844
845 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
846 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")); }
856
857 #[test]
858 fn test_method_filtering_uncommon_methods() {
859 use reqwest::Method;
860
861 let spec = create_test_spec_with_methods();
862 let filters = Some(
863 Filters::builder()
864 .methods(Filter::Include(vec![
865 Method::HEAD,
866 Method::OPTIONS,
867 Method::PATCH,
868 ]))
869 .build(),
870 );
871 let tools = spec
872 .to_tool_metadata(filters.as_ref(), false, false, false)
873 .expect("Failed to generate tools");
874
875 assert_eq!(tools.len(), 3);
877
878 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
879 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")); }
889
890 #[test]
891 fn test_method_and_tag_filtering_combined() {
892 use reqwest::Method;
893
894 let spec = create_test_spec_with_methods();
895 let filters = Some(
896 Filters::builder()
897 .tags(Filter::Include(vec!["user".to_string()]))
898 .methods(Filter::Include(vec![Method::GET, Method::POST]))
899 .build(),
900 );
901 let tools = spec
902 .to_tool_metadata(filters.as_ref(), false, false, false)
903 .expect("Failed to generate tools");
904
905 assert_eq!(tools.len(), 2);
907
908 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
909 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")); }
919
920 #[test]
921 fn test_method_filtering_no_matching_methods() {
922 use reqwest::Method;
923
924 let spec = create_test_spec_with_methods();
925 let filters = Some(
926 Filters::builder()
927 .methods(Filter::Include(vec![Method::TRACE]))
928 .build(),
929 );
930 let tools = spec
931 .to_tool_metadata(filters.as_ref(), false, false, false)
932 .expect("Failed to generate tools");
933
934 assert_eq!(tools.len(), 0);
936 }
937
938 #[test]
939 fn test_method_filtering_empty_filter_list() {
940 let spec = create_test_spec_with_methods();
941 let filters = Some(Filters::builder().methods(Filter::Include(vec![])).build());
942 let tools = spec
943 .to_tool_metadata(filters.as_ref(), false, false, false)
944 .expect("Failed to generate tools");
945
946 assert_eq!(tools.len(), 0);
948 }
949
950 #[test]
951 fn test_operations_include_filter_empty_filter_list() {
952 let spec = create_test_spec_with_methods();
953 let filters = Some(Filters::builder().methods(Filter::Include(vec![])).build());
954 let tools = spec
955 .to_tool_metadata(filters.as_ref(), false, false, false)
956 .expect("Failed to generate tools");
957
958 assert_eq!(tools.len(), 0);
960 }
961
962 #[test]
963 fn test_operations_include_filter_two_operations_filter_list() {
964 let spec = create_test_spec_with_methods();
965 let filters = Some(
966 Filters::builder()
967 .operations_id(Filter::Include(vec![
968 "listUsers".to_owned(),
969 "patchPet".to_owned(),
970 ]))
971 .build(),
972 );
973 let tools = spec
974 .to_tool_metadata(filters.as_ref(), false, false, false)
975 .expect("Failed to generate tools");
976
977 assert_eq!(tools.len(), 2);
978
979 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
980 assert!(tool_names.contains(&"listUsers")); assert!(tool_names.contains(&"patchPet")); }
983
984 #[test]
985 fn test_operations_exclude_filter_empty_filter_list() {
986 let spec = create_test_spec_with_methods();
987 let filters = Some(
988 Filters::builder()
989 .operations_id(Filter::Exclude(vec![]))
990 .build(),
991 );
992 let tools = spec
993 .to_tool_metadata(filters.as_ref(), false, false, false)
994 .expect("Failed to generate tools");
995
996 assert_eq!(tools.len(), 9);
998 }
999
1000 #[test]
1001 fn test_operations_exclude_filter_three_operations_filter_list() {
1002 let spec = create_test_spec_with_methods();
1003 let filters = Some(
1004 Filters::builder()
1005 .operations_id(Filter::Exclude(vec![
1006 "createUser".to_owned(),
1007 "deleteUser".to_owned(),
1008 "healthCheck".to_owned(),
1009 ]))
1010 .build(),
1011 );
1012 let tools = spec
1013 .to_tool_metadata(filters.as_ref(), false, false, false)
1014 .expect("Failed to generate tools");
1015
1016 assert_eq!(tools.len(), 6);
1017
1018 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1019 assert!(tool_names.contains(&"listUsers"));
1020 assert!(tool_names.contains(&"updateUser"));
1021 assert!(tool_names.contains(&"listPets"));
1022 assert!(tool_names.contains(&"createPet"));
1023 assert!(tool_names.contains(&"patchPet"));
1024 assert!(tool_names.contains(&"healthOptions"))
1025 }
1026
1027 #[test]
1028 fn test_all_filters_combined_1() {
1029 let spec = create_test_spec_with_tags();
1030 let filters = Some(
1031 Filters::builder()
1032 .tags(Filter::Include(vec![
1033 "pet".to_owned(),
1034 "user".to_owned(),
1035 "admin".to_owned(),
1036 ]))
1037 .methods(Filter::Include(vec![Method::GET, Method::POST]))
1038 .operations_id(Filter::Exclude(vec![
1039 "listPets".to_owned(),
1040 "createPet".to_owned(),
1041 "publicEndpoint".to_owned(),
1042 ]))
1043 .build(),
1044 );
1045 let tools = spec
1046 .to_tool_metadata(filters.as_ref(), false, false, false)
1047 .expect("Failed to generate tools");
1048
1049 assert_eq!(tools.len(), 2);
1050
1051 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1052
1053 assert!(tool_names.contains(&"listUsers"));
1054 assert!(tool_names.contains(&"adminPanel"));
1055 }
1056
1057 #[test]
1058 fn test_all_filters_combined_2() {
1059 let spec = create_test_spec_with_methods();
1060 let filters = Some(
1061 Filters::builder()
1062 .tags(Filter::Exclude(vec!["health".to_owned()]))
1063 .methods(Filter::Exclude(vec![Method::GET, Method::POST]))
1064 .operations_id(Filter::Include(vec![
1065 "listUsers".to_owned(),
1066 "updateUser".to_owned(),
1067 "deleteUser".to_owned(),
1068 "listPets".to_owned(),
1069 "patchPet".to_owned(),
1070 "healthCheck".to_owned(),
1071 "healthOptions".to_owned(),
1072 ]))
1073 .build(),
1074 );
1075 let tools = spec
1076 .to_tool_metadata(filters.as_ref(), false, false, false)
1077 .expect("Failed to generate tools");
1078
1079 assert_eq!(tools.len(), 3);
1080
1081 let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1082
1083 assert!(tool_names.contains(&"updateUser"));
1084 assert!(tool_names.contains(&"deleteUser"));
1085 assert!(tool_names.contains(&"patchPet"));
1086 }
1087}