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