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