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