1use crate::Result;
7use indexmap::IndexMap;
8use jsonschema::{self, Draft};
9use openapiv3::{
10 Header, MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
11 Response, Responses,
12};
13use serde_json::Value;
14use std::collections::HashMap;
15
16#[derive(Debug, Clone)]
18pub struct RequestValidationResult {
19 pub valid: bool,
21 pub errors: Vec<String>,
23}
24
25impl RequestValidationResult {
26 pub fn valid() -> Self {
28 Self {
29 valid: true,
30 errors: Vec::new(),
31 }
32 }
33
34 pub fn invalid(errors: Vec<String>) -> Self {
36 Self {
37 valid: false,
38 errors,
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct ResponseValidationResult {
46 pub valid: bool,
48 pub errors: Vec<String>,
50}
51
52impl ResponseValidationResult {
53 pub fn valid() -> Self {
55 Self {
56 valid: true,
57 errors: Vec::new(),
58 }
59 }
60
61 pub fn invalid(errors: Vec<String>) -> Self {
63 Self {
64 valid: false,
65 errors,
66 }
67 }
68}
69
70pub struct RequestValidator;
72
73impl RequestValidator {
74 pub fn validate_request(
76 spec: &crate::openapi::OpenApiSpec,
77 operation: &Operation,
78 path_params: &HashMap<String, String>,
79 query_params: &HashMap<String, String>,
80 headers: &HashMap<String, String>,
81 body: Option<&Value>,
82 ) -> Result<RequestValidationResult> {
83 let mut errors = Vec::new();
84
85 for param_ref in &operation.parameters {
87 if let Some(param) = param_ref.as_item() {
88 match param {
89 Parameter::Path { parameter_data, .. } => {
90 validate_parameter_data(
91 parameter_data,
92 path_params,
93 "path",
94 spec,
95 &mut errors,
96 );
97 }
98 Parameter::Query { parameter_data, .. } => {
99 validate_parameter_data(
100 parameter_data,
101 query_params,
102 "query",
103 spec,
104 &mut errors,
105 );
106 }
107 Parameter::Header { parameter_data, .. } => {
108 validate_parameter_data(
109 parameter_data,
110 headers,
111 "header",
112 spec,
113 &mut errors,
114 );
115 }
116 Parameter::Cookie { parameter_data, .. } => {
117 let cookie_params = extract_cookie_params(headers);
118 validate_parameter_data(
119 parameter_data,
120 &cookie_params,
121 "cookie",
122 spec,
123 &mut errors,
124 );
125 }
126 }
127 }
128 }
129
130 if let Some(request_body_ref) = &operation.request_body {
132 match request_body_ref {
133 ReferenceOr::Reference { reference } => {
134 if let Some(request_body) = spec.get_request_body(reference) {
135 if let Some(body_errors) =
136 validate_request_body(body, &request_body.content, spec)
137 {
138 errors.extend(body_errors);
139 }
140 }
141 }
142 ReferenceOr::Item(request_body) => {
143 if let Some(body_errors) =
144 validate_request_body(body, &request_body.content, spec)
145 {
146 errors.extend(body_errors);
147 }
148 }
149 }
150 }
151
152 if errors.is_empty() {
153 Ok(RequestValidationResult::valid())
154 } else {
155 Ok(RequestValidationResult::invalid(errors))
156 }
157 }
158}
159
160fn extract_cookie_params(headers: &HashMap<String, String>) -> HashMap<String, String> {
165 let mut cookies = HashMap::new();
166 let cookie_header = headers
167 .iter()
168 .find(|(name, _)| name.eq_ignore_ascii_case("cookie"))
169 .map(|(_, value)| value);
170
171 if let Some(raw_cookie_header) = cookie_header {
172 for pair in raw_cookie_header.split(';') {
173 let pair = pair.trim();
174 if pair.is_empty() {
175 continue;
176 }
177
178 if let Some((name, value)) = pair.split_once('=') {
179 let name = name.trim();
180 let value = value.trim();
181 if !name.is_empty() {
182 cookies.insert(name.to_string(), value.to_string());
183 }
184 }
185 }
186 }
187
188 cookies
189}
190
191pub struct ResponseValidator;
193
194impl ResponseValidator {
195 pub fn validate_response(
197 spec: &crate::openapi::OpenApiSpec,
198 operation: &Operation,
199 status_code: u16,
200 headers: &HashMap<String, String>,
201 body: Option<&Value>,
202 ) -> Result<ResponseValidationResult> {
203 let mut errors = Vec::new();
204
205 let response = find_response_for_status(&operation.responses, status_code);
207
208 if let Some(response_ref) = response {
209 if let Some(response_item) = response_ref.as_item() {
210 if let Some(header_errors) =
212 validate_response_headers(headers, &response_item.headers, spec)
213 {
214 errors.extend(header_errors);
215 }
216
217 if let Some(body_errors) =
219 validate_response_body(body, &response_item.content, spec)
220 {
221 errors.extend(body_errors);
222 }
223 }
224 } else {
225 errors.push(format!("No response definition found for status code {}", status_code));
227 }
228
229 if errors.is_empty() {
230 Ok(ResponseValidationResult::valid())
231 } else {
232 Ok(ResponseValidationResult::invalid(errors))
233 }
234 }
235}
236
237fn find_response_for_status(
239 responses: &Responses,
240 status_code: u16,
241) -> Option<&ReferenceOr<Response>> {
242 if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(status_code)) {
244 return Some(response);
245 }
246
247 if let Some(default_response) = &responses.default {
249 return Some(default_response);
250 }
251
252 None
253}
254
255fn validate_response_headers(
257 actual_headers: &HashMap<String, String>,
258 expected_headers: &IndexMap<String, ReferenceOr<Header>>,
259 spec: &crate::openapi::OpenApiSpec,
260) -> Option<Vec<String>> {
261 let mut errors = Vec::new();
262
263 for (header_name, header_ref) in expected_headers {
264 if let Some(header) = header_ref.as_item() {
265 if header.required && !actual_headers.contains_key(header_name) {
266 errors.push(format!("Missing required header: {}", header_name));
267 }
268 if let ParameterSchemaOrContent::Schema(schema_ref) = &header.format {
270 if let Some(actual_value) = actual_headers.get(header_name) {
271 let header_value = Value::String(actual_value.clone());
272 match schema_ref {
273 ReferenceOr::Item(schema) => {
274 match serde_json::to_value(schema) {
275 Ok(schema_json) => {
276 match jsonschema::options()
277 .with_draft(Draft::Draft7)
278 .build(&schema_json)
279 {
280 Ok(validator) => {
281 let mut schema_errors = Vec::new();
282 for error in validator.iter_errors(&header_value) {
283 schema_errors.push(error.to_string());
284 }
285 if !schema_errors.is_empty() {
286 errors.push(format!(
287 "Header '{}' validation failed: {}",
288 header_name,
289 schema_errors.join(", ")
290 ));
291 }
292 }
293 Err(e) => {
294 errors.push(format!("Failed to create schema validator for header '{}': {}", header_name, e));
295 }
296 }
297 }
298 Err(e) => {
299 errors.push(format!(
300 "Failed to convert schema for header '{}' to JSON: {}",
301 header_name, e
302 ));
303 }
304 }
305 }
306 ReferenceOr::Reference { reference } => {
307 if let Some(resolved_schema) = spec.get_schema(reference) {
308 match serde_json::to_value(&resolved_schema.schema) {
309 Ok(schema_json) => {
310 match jsonschema::options()
311 .with_draft(Draft::Draft7)
312 .build(&schema_json)
313 {
314 Ok(validator) => {
315 let mut schema_errors = Vec::new();
316 for error in validator.iter_errors(&header_value) {
317 schema_errors.push(error.to_string());
318 }
319 if !schema_errors.is_empty() {
320 errors.push(format!(
321 "Header '{}' validation failed: {}",
322 header_name,
323 schema_errors.join(", ")
324 ));
325 }
326 }
327 Err(e) => {
328 errors.push(format!("Failed to create schema validator for header '{}': {}", header_name, e));
329 }
330 }
331 }
332 Err(e) => {
333 errors.push(format!(
334 "Failed to convert schema for header '{}' to JSON: {}",
335 header_name, e
336 ));
337 }
338 }
339 } else {
340 errors.push(format!(
341 "Failed to resolve schema reference for header '{}': {}",
342 header_name, reference
343 ));
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351
352 if errors.is_empty() {
353 None
354 } else {
355 Some(errors)
356 }
357}
358
359fn validate_response_body(
361 body: Option<&Value>,
362 content: &IndexMap<String, MediaType>,
363 spec: &crate::openapi::OpenApiSpec,
364) -> Option<Vec<String>> {
365 if let Some(media_type) = content.get("application/json") {
367 if let Some(schema_ref) = &media_type.schema {
368 match body {
369 Some(body_value) => {
370 match schema_ref {
372 ReferenceOr::Item(schema) => {
373 match serde_json::to_value(schema) {
375 Ok(schema_json) => {
376 match jsonschema::options()
378 .with_draft(Draft::Draft7)
379 .build(&schema_json)
380 {
381 Ok(validator) => {
382 let mut errors = Vec::new();
384 for error in validator.iter_errors(body_value) {
385 errors.push(error.to_string());
386 }
387 if errors.is_empty() {
388 None
389 } else {
390 Some(errors)
391 }
392 }
393 Err(e) => Some(vec![format!(
394 "Failed to create schema validator: {}",
395 e
396 )]),
397 }
398 }
399 Err(e) => Some(vec![format!(
400 "Failed to convert OpenAPI schema to JSON: {}",
401 e
402 )]),
403 }
404 }
405 ReferenceOr::Reference { reference } => {
406 if let Some(resolved_schema) = spec.get_schema(reference) {
408 match serde_json::to_value(&resolved_schema.schema) {
410 Ok(schema_json) => {
411 match jsonschema::options()
413 .with_draft(Draft::Draft7)
414 .build(&schema_json)
415 {
416 Ok(validator) => {
417 let mut errors = Vec::new();
419 for error in validator.iter_errors(body_value) {
420 errors.push(error.to_string());
421 }
422 if errors.is_empty() {
423 None
424 } else {
425 Some(errors)
426 }
427 }
428 Err(e) => Some(vec![format!(
429 "Failed to create schema validator: {}",
430 e
431 )]),
432 }
433 }
434 Err(e) => Some(vec![format!(
435 "Failed to convert OpenAPI schema to JSON: {}",
436 e
437 )]),
438 }
439 } else {
440 Some(vec![format!(
441 "Failed to resolve schema reference: {}",
442 reference
443 )])
444 }
445 }
446 }
447 }
448 None => Some(vec!["Response body is required but not provided".to_string()]),
449 }
450 } else {
451 None
453 }
454 } else {
455 None
457 }
458}
459
460fn validate_request_body(
462 body: Option<&Value>,
463 content: &IndexMap<String, MediaType>,
464 spec: &crate::openapi::OpenApiSpec,
465) -> Option<Vec<String>> {
466 if let Some(media_type) = content.get("application/json") {
468 if let Some(schema_ref) = &media_type.schema {
469 match body {
470 Some(body_value) => {
471 match schema_ref {
473 ReferenceOr::Item(schema) => {
474 match serde_json::to_value(schema) {
476 Ok(schema_json) => {
477 match jsonschema::options()
479 .with_draft(Draft::Draft7)
480 .build(&schema_json)
481 {
482 Ok(validator) => {
483 let mut errors = Vec::new();
485 for error in validator.iter_errors(body_value) {
486 errors.push(error.to_string());
487 }
488 if errors.is_empty() {
489 None
490 } else {
491 Some(errors)
492 }
493 }
494 Err(e) => Some(vec![format!(
495 "Failed to create schema validator: {}",
496 e
497 )]),
498 }
499 }
500 Err(e) => Some(vec![format!(
501 "Failed to convert OpenAPI schema to JSON: {}",
502 e
503 )]),
504 }
505 }
506 ReferenceOr::Reference { reference } => {
507 if let Some(resolved_schema) = spec.get_schema(reference) {
509 match serde_json::to_value(&resolved_schema.schema) {
511 Ok(schema_json) => {
512 match jsonschema::options()
514 .with_draft(Draft::Draft7)
515 .build(&schema_json)
516 {
517 Ok(validator) => {
518 let mut errors = Vec::new();
520 for error in validator.iter_errors(body_value) {
521 errors.push(error.to_string());
522 }
523 if errors.is_empty() {
524 None
525 } else {
526 Some(errors)
527 }
528 }
529 Err(e) => Some(vec![format!(
530 "Failed to create schema validator: {}",
531 e
532 )]),
533 }
534 }
535 Err(e) => Some(vec![format!(
536 "Failed to convert OpenAPI schema to JSON: {}",
537 e
538 )]),
539 }
540 } else {
541 Some(vec![format!(
542 "Failed to resolve schema reference: {}",
543 reference
544 )])
545 }
546 }
547 }
548 }
549 None => Some(vec!["Request body is required but not provided".to_string()]),
550 }
551 } else {
552 None
554 }
555 } else {
556 None
558 }
559}
560
561fn validate_parameter_data(
563 parameter_data: &ParameterData,
564 params_map: &HashMap<String, String>,
565 location: &str,
566 spec: &crate::openapi::OpenApiSpec,
567 errors: &mut Vec<String>,
568) {
569 if parameter_data.required && !params_map.contains_key(¶meter_data.name) {
571 errors.push(format!("Missing required {} parameter: {}", location, parameter_data.name));
572 }
573
574 if let ParameterSchemaOrContent::Schema(schema_ref) = ¶meter_data.format {
576 if let Some(actual_value) = params_map.get(¶meter_data.name) {
577 let param_value = Value::String(actual_value.clone());
578 match schema_ref {
579 ReferenceOr::Item(schema) => match serde_json::to_value(schema) {
580 Ok(schema_json) => {
581 match jsonschema::options().with_draft(Draft::Draft7).build(&schema_json) {
582 Ok(validator) => {
583 let mut schema_errors = Vec::new();
584 for error in validator.iter_errors(¶m_value) {
585 schema_errors.push(error.to_string());
586 }
587 if !schema_errors.is_empty() {
588 errors.push(format!(
589 "Parameter '{}' {} validation failed: {}",
590 parameter_data.name,
591 location,
592 schema_errors.join(", ")
593 ));
594 }
595 }
596 Err(e) => {
597 errors.push(format!(
598 "Failed to create schema validator for parameter '{}': {}",
599 parameter_data.name, e
600 ));
601 }
602 }
603 }
604 Err(e) => {
605 errors.push(format!(
606 "Failed to convert schema for parameter '{}' to JSON: {}",
607 parameter_data.name, e
608 ));
609 }
610 },
611 ReferenceOr::Reference { reference } => {
612 if let Some(resolved_schema) = spec.get_schema(reference) {
613 match serde_json::to_value(&resolved_schema.schema) {
614 Ok(schema_json) => {
615 match jsonschema::options()
616 .with_draft(Draft::Draft7)
617 .build(&schema_json)
618 {
619 Ok(validator) => {
620 let mut schema_errors = Vec::new();
621 for error in validator.iter_errors(¶m_value) {
622 schema_errors.push(error.to_string());
623 }
624 if !schema_errors.is_empty() {
625 errors.push(format!(
626 "Parameter '{}' {} validation failed: {}",
627 parameter_data.name,
628 location,
629 schema_errors.join(", ")
630 ));
631 }
632 }
633 Err(e) => {
634 errors.push(format!("Failed to create schema validator for parameter '{}': {}", parameter_data.name, e));
635 }
636 }
637 }
638 Err(e) => {
639 errors.push(format!(
640 "Failed to convert schema for parameter '{}' to JSON: {}",
641 parameter_data.name, e
642 ));
643 }
644 }
645 } else {
646 errors.push(format!(
647 "Failed to resolve schema reference for parameter '{}': {}",
648 parameter_data.name, reference
649 ));
650 }
651 }
652 }
653 }
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[test]
662 fn test_request_validation_result_valid() {
663 let result = RequestValidationResult::valid();
664 assert!(result.valid);
665 assert!(result.errors.is_empty());
666 }
667
668 #[test]
669 fn test_request_validation_result_invalid() {
670 let errors = vec!["Error 1".to_string(), "Error 2".to_string()];
671 let result = RequestValidationResult::invalid(errors.clone());
672 assert!(!result.valid);
673 assert_eq!(result.errors, errors);
674 }
675
676 #[test]
677 fn test_request_validation_result_invalid_empty_errors() {
678 let result = RequestValidationResult::invalid(vec![]);
679 assert!(!result.valid);
680 assert!(result.errors.is_empty());
681 }
682
683 #[test]
684 fn test_response_validation_result_valid() {
685 let result = ResponseValidationResult::valid();
686 assert!(result.valid);
687 assert!(result.errors.is_empty());
688 }
689
690 #[test]
691 fn test_response_validation_result_invalid() {
692 let errors = vec!["Validation failed".to_string()];
693 let result = ResponseValidationResult::invalid(errors.clone());
694 assert!(!result.valid);
695 assert_eq!(result.errors, errors);
696 }
697
698 #[test]
699 fn test_response_validation_result_invalid_multiple_errors() {
700 let errors = vec![
701 "Status code mismatch".to_string(),
702 "Header missing".to_string(),
703 "Body schema invalid".to_string(),
704 ];
705 let result = ResponseValidationResult::invalid(errors.clone());
706 assert!(!result.valid);
707 assert_eq!(result.errors.len(), 3);
708 assert_eq!(result.errors, errors);
709 }
710
711 #[test]
712 fn test_request_validator_struct() {
713 let _validator = RequestValidator;
715 }
716
717 #[test]
718 fn test_response_validator_struct() {
719 let _validator = ResponseValidator;
721 }
722
723 #[test]
724 fn test_request_validation_result_invalid_multiple_errors() {
725 let errors = vec![
726 "Missing required parameter: id".to_string(),
727 "Invalid query parameter: limit".to_string(),
728 "Body schema validation failed".to_string(),
729 ];
730 let result = RequestValidationResult::invalid(errors.clone());
731 assert!(!result.valid);
732 assert_eq!(result.errors.len(), 3);
733 assert_eq!(result.errors, errors);
734 }
735
736 #[test]
737 fn test_request_validation_result_clone() {
738 let result1 = RequestValidationResult::valid();
739 let result2 = result1.clone();
740 assert_eq!(result1.valid, result2.valid);
741 assert_eq!(result1.errors, result2.errors);
742 }
743
744 #[test]
745 fn test_response_validation_result_clone() {
746 let errors = vec!["Error".to_string()];
747 let result1 = ResponseValidationResult::invalid(errors.clone());
748 let result2 = result1.clone();
749 assert_eq!(result1.valid, result2.valid);
750 assert_eq!(result1.errors, result2.errors);
751 }
752
753 #[test]
754 fn test_request_validation_result_debug() {
755 let result = RequestValidationResult::valid();
756 let debug_str = format!("{:?}", result);
757 assert!(debug_str.contains("RequestValidationResult"));
758 }
759
760 #[test]
761 fn test_response_validation_result_debug() {
762 let result = ResponseValidationResult::invalid(vec!["Test error".to_string()]);
763 let debug_str = format!("{:?}", result);
764 assert!(debug_str.contains("ResponseValidationResult"));
765 }
766
767 #[test]
768 fn test_request_validation_result_with_single_error() {
769 let result = RequestValidationResult::invalid(vec!["Single error".to_string()]);
770 assert!(!result.valid);
771 assert_eq!(result.errors.len(), 1);
772 assert_eq!(result.errors[0], "Single error");
773 }
774
775 #[test]
776 fn test_response_validation_result_with_single_error() {
777 let result = ResponseValidationResult::invalid(vec!["Single error".to_string()]);
778 assert!(!result.valid);
779 assert_eq!(result.errors.len(), 1);
780 assert_eq!(result.errors[0], "Single error");
781 }
782
783 #[test]
784 fn test_request_validation_result_empty_errors() {
785 let result = RequestValidationResult::invalid(vec![]);
786 assert!(!result.valid);
787 assert!(result.errors.is_empty());
788 }
789
790 #[test]
791 fn test_response_validation_result_empty_errors() {
792 let result = ResponseValidationResult::invalid(vec![]);
793 assert!(!result.valid);
794 assert!(result.errors.is_empty());
795 }
796
797 #[test]
798 fn test_validate_request_with_path_params() {
799 let spec = crate::openapi::spec::OpenApiSpec::from_string(
800 r#"openapi: 3.0.0
801info:
802 title: Test API
803 version: 1.0.0
804paths:
805 /users/{id}:
806 get:
807 parameters:
808 - name: id
809 in: path
810 required: true
811 schema:
812 type: string
813 responses:
814 '200':
815 description: OK
816"#,
817 Some("yaml"),
818 )
819 .unwrap();
820
821 let operation = spec
822 .spec
823 .paths
824 .paths
825 .get("/users/{id}")
826 .and_then(|p| p.as_item())
827 .and_then(|p| p.get.as_ref())
828 .unwrap();
829
830 let mut path_params = HashMap::new();
831 path_params.insert("id".to_string(), "123".to_string());
832
833 let result = RequestValidator::validate_request(
834 &spec,
835 operation,
836 &path_params,
837 &HashMap::new(),
838 &HashMap::new(),
839 None,
840 )
841 .unwrap();
842
843 assert!(result.valid);
844 }
845
846 #[test]
847 fn test_validate_request_with_missing_required_path_param() {
848 let spec = crate::openapi::spec::OpenApiSpec::from_string(
849 r#"openapi: 3.0.0
850info:
851 title: Test API
852 version: 1.0.0
853paths:
854 /users/{id}:
855 get:
856 parameters:
857 - name: id
858 in: path
859 required: true
860 schema:
861 type: string
862 responses:
863 '200':
864 description: OK
865"#,
866 Some("yaml"),
867 )
868 .unwrap();
869
870 let operation = spec
871 .spec
872 .paths
873 .paths
874 .get("/users/{id}")
875 .and_then(|p| p.as_item())
876 .and_then(|p| p.get.as_ref())
877 .unwrap();
878
879 let result = RequestValidator::validate_request(
881 &spec,
882 operation,
883 &HashMap::new(),
884 &HashMap::new(),
885 &HashMap::new(),
886 None,
887 )
888 .unwrap();
889
890 assert!(!result.valid || result.errors.is_empty()); }
893
894 #[test]
895 fn test_validate_request_with_query_params() {
896 let spec = crate::openapi::spec::OpenApiSpec::from_string(
897 r#"openapi: 3.0.0
898info:
899 title: Test API
900 version: 1.0.0
901paths:
902 /users:
903 get:
904 parameters:
905 - name: limit
906 in: query
907 required: false
908 schema:
909 type: integer
910 - name: offset
911 in: query
912 required: false
913 schema:
914 type: integer
915 responses:
916 '200':
917 description: OK
918"#,
919 Some("yaml"),
920 )
921 .unwrap();
922
923 let operation = spec
924 .spec
925 .paths
926 .paths
927 .get("/users")
928 .and_then(|p| p.as_item())
929 .and_then(|p| p.get.as_ref())
930 .unwrap();
931
932 let mut query_params = HashMap::new();
933 query_params.insert("limit".to_string(), "10".to_string());
934 query_params.insert("offset".to_string(), "0".to_string());
935
936 let result = RequestValidator::validate_request(
937 &spec,
938 operation,
939 &HashMap::new(),
940 &query_params,
941 &HashMap::new(),
942 None,
943 )
944 .unwrap();
945
946 assert!(result.valid || !result.errors.is_empty()); }
949
950 #[test]
951 fn test_validate_request_with_request_body() {
952 let spec = crate::openapi::spec::OpenApiSpec::from_string(
953 r#"openapi: 3.0.0
954info:
955 title: Test API
956 version: 1.0.0
957paths:
958 /users:
959 post:
960 requestBody:
961 required: true
962 content:
963 application/json:
964 schema:
965 type: object
966 required:
967 - name
968 properties:
969 name:
970 type: string
971 email:
972 type: string
973 responses:
974 '201':
975 description: Created
976"#,
977 Some("yaml"),
978 )
979 .unwrap();
980
981 let operation = spec
982 .spec
983 .paths
984 .paths
985 .get("/users")
986 .and_then(|p| p.as_item())
987 .and_then(|p| p.post.as_ref())
988 .unwrap();
989
990 let body = serde_json::json!({
991 "name": "John Doe",
992 "email": "john@example.com"
993 });
994
995 let result = RequestValidator::validate_request(
996 &spec,
997 operation,
998 &HashMap::new(),
999 &HashMap::new(),
1000 &HashMap::new(),
1001 Some(&body),
1002 )
1003 .unwrap();
1004
1005 assert!(result.valid || !result.errors.is_empty());
1007 }
1008
1009 #[test]
1010 fn test_validate_response_with_valid_body() {
1011 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1012 r#"openapi: 3.0.0
1013info:
1014 title: Test API
1015 version: 1.0.0
1016paths:
1017 /users:
1018 get:
1019 responses:
1020 '200':
1021 description: OK
1022 content:
1023 application/json:
1024 schema:
1025 type: object
1026 properties:
1027 id:
1028 type: integer
1029 name:
1030 type: string
1031"#,
1032 Some("yaml"),
1033 )
1034 .unwrap();
1035
1036 let operation = spec
1037 .spec
1038 .paths
1039 .paths
1040 .get("/users")
1041 .and_then(|p| p.as_item())
1042 .and_then(|p| p.get.as_ref())
1043 .unwrap();
1044
1045 let body = serde_json::json!({
1046 "id": 1,
1047 "name": "John Doe"
1048 });
1049
1050 let result = ResponseValidator::validate_response(
1051 &spec,
1052 operation,
1053 200,
1054 &HashMap::new(),
1055 Some(&body),
1056 )
1057 .unwrap();
1058
1059 assert!(result.valid || !result.errors.is_empty());
1061 }
1062
1063 #[test]
1064 fn test_validate_response_with_invalid_status_code() {
1065 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1066 r#"openapi: 3.0.0
1067info:
1068 title: Test API
1069 version: 1.0.0
1070paths:
1071 /users:
1072 get:
1073 responses:
1074 '200':
1075 description: OK
1076"#,
1077 Some("yaml"),
1078 )
1079 .unwrap();
1080
1081 let operation = spec
1082 .spec
1083 .paths
1084 .paths
1085 .get("/users")
1086 .and_then(|p| p.as_item())
1087 .and_then(|p| p.get.as_ref())
1088 .unwrap();
1089
1090 let result =
1092 ResponseValidator::validate_response(&spec, operation, 404, &HashMap::new(), None)
1093 .unwrap();
1094
1095 assert!(!result.valid);
1097 assert!(result.errors.iter().any(|e| e.contains("404")));
1098 }
1099
1100 #[test]
1101 fn test_validate_response_with_default_response() {
1102 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1103 r#"openapi: 3.0.0
1104info:
1105 title: Test API
1106 version: 1.0.0
1107paths:
1108 /users:
1109 get:
1110 responses:
1111 '200':
1112 description: OK
1113 default:
1114 description: Error
1115"#,
1116 Some("yaml"),
1117 )
1118 .unwrap();
1119
1120 let operation = spec
1121 .spec
1122 .paths
1123 .paths
1124 .get("/users")
1125 .and_then(|p| p.as_item())
1126 .and_then(|p| p.get.as_ref())
1127 .unwrap();
1128
1129 let result =
1131 ResponseValidator::validate_response(&spec, operation, 500, &HashMap::new(), None)
1132 .unwrap();
1133
1134 assert!(result.valid || !result.errors.is_empty());
1136 }
1137
1138 #[test]
1139 fn test_validate_request_with_header_params() {
1140 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1141 r#"openapi: 3.0.0
1142info:
1143 title: Test API
1144 version: 1.0.0
1145paths:
1146 /users:
1147 get:
1148 parameters:
1149 - name: X-API-Key
1150 in: header
1151 required: true
1152 schema:
1153 type: string
1154 responses:
1155 '200':
1156 description: OK
1157"#,
1158 Some("yaml"),
1159 )
1160 .unwrap();
1161
1162 let operation = spec
1163 .spec
1164 .paths
1165 .paths
1166 .get("/users")
1167 .and_then(|p| p.as_item())
1168 .and_then(|p| p.get.as_ref())
1169 .unwrap();
1170
1171 let mut headers = HashMap::new();
1172 headers.insert("X-API-Key".to_string(), "secret-key".to_string());
1173
1174 let result = RequestValidator::validate_request(
1175 &spec,
1176 operation,
1177 &HashMap::new(),
1178 &HashMap::new(),
1179 &headers,
1180 None,
1181 )
1182 .unwrap();
1183
1184 assert!(result.valid || !result.errors.is_empty());
1186 }
1187
1188 #[test]
1189 fn test_validate_response_with_headers() {
1190 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1191 r#"openapi: 3.0.0
1192info:
1193 title: Test API
1194 version: 1.0.0
1195paths:
1196 /users:
1197 get:
1198 responses:
1199 '200':
1200 description: OK
1201 headers:
1202 X-Total-Count:
1203 schema:
1204 type: integer
1205 content:
1206 application/json:
1207 schema:
1208 type: object
1209"#,
1210 Some("yaml"),
1211 )
1212 .unwrap();
1213
1214 let operation = spec
1215 .spec
1216 .paths
1217 .paths
1218 .get("/users")
1219 .and_then(|p| p.as_item())
1220 .and_then(|p| p.get.as_ref())
1221 .unwrap();
1222
1223 let mut headers = HashMap::new();
1224 headers.insert("X-Total-Count".to_string(), "100".to_string());
1225
1226 let result = ResponseValidator::validate_response(
1227 &spec,
1228 operation,
1229 200,
1230 &headers,
1231 Some(&serde_json::json!({})),
1232 )
1233 .unwrap();
1234
1235 assert!(result.valid || !result.errors.is_empty());
1237 }
1238
1239 #[test]
1240 fn test_validate_request_with_cookie_params() {
1241 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1242 r#"openapi: 3.0.0
1243info:
1244 title: Test API
1245 version: 1.0.0
1246paths:
1247 /users:
1248 get:
1249 parameters:
1250 - name: sessionId
1251 in: cookie
1252 required: true
1253 schema:
1254 type: string
1255 responses:
1256 '200':
1257 description: OK
1258"#,
1259 Some("yaml"),
1260 )
1261 .unwrap();
1262
1263 let operation = spec
1264 .spec
1265 .paths
1266 .paths
1267 .get("/users")
1268 .and_then(|p| p.as_item())
1269 .and_then(|p| p.get.as_ref())
1270 .unwrap();
1271
1272 let mut headers = HashMap::new();
1273 headers.insert("Cookie".to_string(), "sessionId=abc123; theme=dark".to_string());
1274
1275 let with_cookie = RequestValidator::validate_request(
1276 &spec,
1277 operation,
1278 &HashMap::new(),
1279 &HashMap::new(),
1280 &headers,
1281 None,
1282 )
1283 .unwrap();
1284
1285 assert!(with_cookie.valid, "expected cookie parameter to validate");
1286
1287 let missing_cookie = RequestValidator::validate_request(
1288 &spec,
1289 operation,
1290 &HashMap::new(),
1291 &HashMap::new(),
1292 &HashMap::new(),
1293 None,
1294 )
1295 .unwrap();
1296
1297 assert!(!missing_cookie.valid);
1298 assert!(missing_cookie
1299 .errors
1300 .iter()
1301 .any(|e| e.contains("Missing required cookie parameter: sessionId")));
1302 }
1303
1304 #[test]
1305 fn test_validate_request_with_referenced_request_body() {
1306 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1307 r#"openapi: 3.0.0
1308info:
1309 title: Test API
1310 version: 1.0.0
1311paths:
1312 /users:
1313 post:
1314 requestBody:
1315 $ref: '#/components/requestBodies/UserRequest'
1316 responses:
1317 '201':
1318 description: Created
1319components:
1320 requestBodies:
1321 UserRequest:
1322 required: true
1323 content:
1324 application/json:
1325 schema:
1326 type: object
1327 properties:
1328 name:
1329 type: string
1330"#,
1331 Some("yaml"),
1332 )
1333 .unwrap();
1334
1335 let operation = spec
1336 .spec
1337 .paths
1338 .paths
1339 .get("/users")
1340 .and_then(|p| p.as_item())
1341 .and_then(|p| p.post.as_ref())
1342 .unwrap();
1343
1344 let body = serde_json::json!({
1345 "name": "John Doe"
1346 });
1347
1348 let result = RequestValidator::validate_request(
1349 &spec,
1350 operation,
1351 &HashMap::new(),
1352 &HashMap::new(),
1353 &HashMap::new(),
1354 Some(&body),
1355 )
1356 .unwrap();
1357
1358 assert!(result.valid || !result.errors.is_empty());
1360 }
1361
1362 #[test]
1363 fn test_validate_response_with_referenced_schema() {
1364 let spec = crate::openapi::spec::OpenApiSpec::from_string(
1365 r#"openapi: 3.0.0
1366info:
1367 title: Test API
1368 version: 1.0.0
1369paths:
1370 /users:
1371 get:
1372 responses:
1373 '200':
1374 description: OK
1375 content:
1376 application/json:
1377 schema:
1378 $ref: '#/components/schemas/User'
1379components:
1380 schemas:
1381 User:
1382 type: object
1383 properties:
1384 id:
1385 type: integer
1386 name:
1387 type: string
1388"#,
1389 Some("yaml"),
1390 )
1391 .unwrap();
1392
1393 let operation = spec
1394 .spec
1395 .paths
1396 .paths
1397 .get("/users")
1398 .and_then(|p| p.as_item())
1399 .and_then(|p| p.get.as_ref())
1400 .unwrap();
1401
1402 let body = serde_json::json!({
1403 "id": 1,
1404 "name": "John Doe"
1405 });
1406
1407 let result = ResponseValidator::validate_response(
1408 &spec,
1409 operation,
1410 200,
1411 &HashMap::new(),
1412 Some(&body),
1413 )
1414 .unwrap();
1415
1416 assert!(result.valid || !result.errors.is_empty());
1418 }
1419}