1use indexmap::IndexMap;
7use jsonschema::{self, Draft};
8use mockforge_foundation::error::Result;
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::spec::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::spec::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::spec::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::spec::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::spec::OpenApiSpec,
465) -> Option<Vec<String>> {
466 let media_type = content.get("application/json")?;
468 let schema_ref = media_type.schema.as_ref()?;
469 let body_value = match body {
470 Some(b) => b,
471 None => return Some(vec!["Request body is required but not provided".to_string()]),
472 };
473
474 let root_schema = match schema_ref {
485 ReferenceOr::Item(s) => s.clone(),
486 ReferenceOr::Reference { reference } => match spec.get_schema(reference) {
487 Some(s) => s.schema.clone(),
488 None => {
489 return Some(vec![format!("Failed to resolve schema reference: {reference}")]);
490 }
491 },
492 };
493
494 match crate::schema_ref_resolver::build_validator(&root_schema, &spec.spec) {
495 Ok(validator) => {
496 let errors: Vec<String> =
497 validator.iter_errors(body_value).map(|e| e.to_string()).collect();
498 if errors.is_empty() {
499 None
500 } else {
501 Some(errors)
502 }
503 }
504 Err(e) => Some(vec![e]),
505 }
506}
507
508fn validate_parameter_data(
510 parameter_data: &ParameterData,
511 params_map: &HashMap<String, String>,
512 location: &str,
513 spec: &crate::spec::OpenApiSpec,
514 errors: &mut Vec<String>,
515) {
516 if parameter_data.required && !params_map.contains_key(¶meter_data.name) {
518 errors.push(format!("Missing required {} parameter: {}", location, parameter_data.name));
519 }
520
521 if let ParameterSchemaOrContent::Schema(schema_ref) = ¶meter_data.format {
523 if let Some(actual_value) = params_map.get(¶meter_data.name) {
524 let param_value = Value::String(actual_value.clone());
525 match schema_ref {
526 ReferenceOr::Item(schema) => match serde_json::to_value(schema) {
527 Ok(schema_json) => {
528 match jsonschema::options().with_draft(Draft::Draft7).build(&schema_json) {
529 Ok(validator) => {
530 let mut schema_errors = Vec::new();
531 for error in validator.iter_errors(¶m_value) {
532 schema_errors.push(error.to_string());
533 }
534 if !schema_errors.is_empty() {
535 errors.push(format!(
536 "Parameter '{}' {} validation failed: {}",
537 parameter_data.name,
538 location,
539 schema_errors.join(", ")
540 ));
541 }
542 }
543 Err(e) => {
544 errors.push(format!(
545 "Failed to create schema validator for parameter '{}': {}",
546 parameter_data.name, e
547 ));
548 }
549 }
550 }
551 Err(e) => {
552 errors.push(format!(
553 "Failed to convert schema for parameter '{}' to JSON: {}",
554 parameter_data.name, e
555 ));
556 }
557 },
558 ReferenceOr::Reference { reference } => {
559 if let Some(resolved_schema) = spec.get_schema(reference) {
560 match serde_json::to_value(&resolved_schema.schema) {
561 Ok(schema_json) => {
562 match jsonschema::options()
563 .with_draft(Draft::Draft7)
564 .build(&schema_json)
565 {
566 Ok(validator) => {
567 let mut schema_errors = Vec::new();
568 for error in validator.iter_errors(¶m_value) {
569 schema_errors.push(error.to_string());
570 }
571 if !schema_errors.is_empty() {
572 errors.push(format!(
573 "Parameter '{}' {} validation failed: {}",
574 parameter_data.name,
575 location,
576 schema_errors.join(", ")
577 ));
578 }
579 }
580 Err(e) => {
581 errors.push(format!("Failed to create schema validator for parameter '{}': {}", parameter_data.name, e));
582 }
583 }
584 }
585 Err(e) => {
586 errors.push(format!(
587 "Failed to convert schema for parameter '{}' to JSON: {}",
588 parameter_data.name, e
589 ));
590 }
591 }
592 } else {
593 errors.push(format!(
594 "Failed to resolve schema reference for parameter '{}': {}",
595 parameter_data.name, reference
596 ));
597 }
598 }
599 }
600 }
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607
608 #[test]
609 fn test_request_validation_result_valid() {
610 let result = RequestValidationResult::valid();
611 assert!(result.valid);
612 assert!(result.errors.is_empty());
613 }
614
615 #[test]
616 fn test_request_validation_result_invalid() {
617 let errors = vec!["Error 1".to_string(), "Error 2".to_string()];
618 let result = RequestValidationResult::invalid(errors.clone());
619 assert!(!result.valid);
620 assert_eq!(result.errors, errors);
621 }
622
623 #[test]
624 fn test_request_validation_result_invalid_empty_errors() {
625 let result = RequestValidationResult::invalid(vec![]);
626 assert!(!result.valid);
627 assert!(result.errors.is_empty());
628 }
629
630 #[test]
631 fn test_response_validation_result_valid() {
632 let result = ResponseValidationResult::valid();
633 assert!(result.valid);
634 assert!(result.errors.is_empty());
635 }
636
637 #[test]
638 fn test_response_validation_result_invalid() {
639 let errors = vec!["Validation failed".to_string()];
640 let result = ResponseValidationResult::invalid(errors.clone());
641 assert!(!result.valid);
642 assert_eq!(result.errors, errors);
643 }
644
645 #[test]
646 fn test_response_validation_result_invalid_multiple_errors() {
647 let errors = vec![
648 "Status code mismatch".to_string(),
649 "Header missing".to_string(),
650 "Body schema invalid".to_string(),
651 ];
652 let result = ResponseValidationResult::invalid(errors.clone());
653 assert!(!result.valid);
654 assert_eq!(result.errors.len(), 3);
655 assert_eq!(result.errors, errors);
656 }
657
658 #[test]
659 fn test_request_validator_struct() {
660 let _validator = RequestValidator;
662 }
663
664 #[test]
665 fn test_response_validator_struct() {
666 let _validator = ResponseValidator;
668 }
669
670 #[test]
671 fn test_request_validation_result_invalid_multiple_errors() {
672 let errors = vec![
673 "Missing required parameter: id".to_string(),
674 "Invalid query parameter: limit".to_string(),
675 "Body schema validation failed".to_string(),
676 ];
677 let result = RequestValidationResult::invalid(errors.clone());
678 assert!(!result.valid);
679 assert_eq!(result.errors.len(), 3);
680 assert_eq!(result.errors, errors);
681 }
682
683 #[test]
684 fn test_request_validation_result_clone() {
685 let result1 = RequestValidationResult::valid();
686 let result2 = result1.clone();
687 assert_eq!(result1.valid, result2.valid);
688 assert_eq!(result1.errors, result2.errors);
689 }
690
691 #[test]
692 fn test_response_validation_result_clone() {
693 let errors = vec!["Error".to_string()];
694 let result1 = ResponseValidationResult::invalid(errors.clone());
695 let result2 = result1.clone();
696 assert_eq!(result1.valid, result2.valid);
697 assert_eq!(result1.errors, result2.errors);
698 }
699
700 #[test]
701 fn test_request_validation_result_debug() {
702 let result = RequestValidationResult::valid();
703 let debug_str = format!("{:?}", result);
704 assert!(debug_str.contains("RequestValidationResult"));
705 }
706
707 #[test]
708 fn test_response_validation_result_debug() {
709 let result = ResponseValidationResult::invalid(vec!["Test error".to_string()]);
710 let debug_str = format!("{:?}", result);
711 assert!(debug_str.contains("ResponseValidationResult"));
712 }
713
714 #[test]
715 fn test_request_validation_result_with_single_error() {
716 let result = RequestValidationResult::invalid(vec!["Single error".to_string()]);
717 assert!(!result.valid);
718 assert_eq!(result.errors.len(), 1);
719 assert_eq!(result.errors[0], "Single error");
720 }
721
722 #[test]
723 fn test_response_validation_result_with_single_error() {
724 let result = ResponseValidationResult::invalid(vec!["Single error".to_string()]);
725 assert!(!result.valid);
726 assert_eq!(result.errors.len(), 1);
727 assert_eq!(result.errors[0], "Single error");
728 }
729
730 #[test]
731 fn test_request_validation_result_empty_errors() {
732 let result = RequestValidationResult::invalid(vec![]);
733 assert!(!result.valid);
734 assert!(result.errors.is_empty());
735 }
736
737 #[test]
738 fn test_response_validation_result_empty_errors() {
739 let result = ResponseValidationResult::invalid(vec![]);
740 assert!(!result.valid);
741 assert!(result.errors.is_empty());
742 }
743
744 #[test]
745 fn test_validate_request_with_path_params() {
746 let spec = crate::spec::OpenApiSpec::from_string(
747 r#"openapi: 3.0.0
748info:
749 title: Test API
750 version: 1.0.0
751paths:
752 /users/{id}:
753 get:
754 parameters:
755 - name: id
756 in: path
757 required: true
758 schema:
759 type: string
760 responses:
761 '200':
762 description: OK
763"#,
764 Some("yaml"),
765 )
766 .unwrap();
767
768 let operation = spec
769 .spec
770 .paths
771 .paths
772 .get("/users/{id}")
773 .and_then(|p| p.as_item())
774 .and_then(|p| p.get.as_ref())
775 .unwrap();
776
777 let mut path_params = HashMap::new();
778 path_params.insert("id".to_string(), "123".to_string());
779
780 let result = RequestValidator::validate_request(
781 &spec,
782 operation,
783 &path_params,
784 &HashMap::new(),
785 &HashMap::new(),
786 None,
787 )
788 .unwrap();
789
790 assert!(result.valid);
791 }
792
793 #[test]
794 fn test_validate_request_with_missing_required_path_param() {
795 let spec = crate::spec::OpenApiSpec::from_string(
796 r#"openapi: 3.0.0
797info:
798 title: Test API
799 version: 1.0.0
800paths:
801 /users/{id}:
802 get:
803 parameters:
804 - name: id
805 in: path
806 required: true
807 schema:
808 type: string
809 responses:
810 '200':
811 description: OK
812"#,
813 Some("yaml"),
814 )
815 .unwrap();
816
817 let operation = spec
818 .spec
819 .paths
820 .paths
821 .get("/users/{id}")
822 .and_then(|p| p.as_item())
823 .and_then(|p| p.get.as_ref())
824 .unwrap();
825
826 let result = RequestValidator::validate_request(
828 &spec,
829 operation,
830 &HashMap::new(),
831 &HashMap::new(),
832 &HashMap::new(),
833 None,
834 )
835 .unwrap();
836
837 assert!(!result.valid || result.errors.is_empty()); }
840
841 #[test]
842 fn test_validate_request_with_query_params() {
843 let spec = crate::spec::OpenApiSpec::from_string(
844 r#"openapi: 3.0.0
845info:
846 title: Test API
847 version: 1.0.0
848paths:
849 /users:
850 get:
851 parameters:
852 - name: limit
853 in: query
854 required: false
855 schema:
856 type: integer
857 - name: offset
858 in: query
859 required: false
860 schema:
861 type: integer
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")
875 .and_then(|p| p.as_item())
876 .and_then(|p| p.get.as_ref())
877 .unwrap();
878
879 let mut query_params = HashMap::new();
880 query_params.insert("limit".to_string(), "10".to_string());
881 query_params.insert("offset".to_string(), "0".to_string());
882
883 let result = RequestValidator::validate_request(
884 &spec,
885 operation,
886 &HashMap::new(),
887 &query_params,
888 &HashMap::new(),
889 None,
890 )
891 .unwrap();
892
893 assert!(result.valid || !result.errors.is_empty()); }
896
897 #[test]
898 fn test_validate_request_with_request_body() {
899 let spec = crate::spec::OpenApiSpec::from_string(
900 r#"openapi: 3.0.0
901info:
902 title: Test API
903 version: 1.0.0
904paths:
905 /users:
906 post:
907 requestBody:
908 required: true
909 content:
910 application/json:
911 schema:
912 type: object
913 required:
914 - name
915 properties:
916 name:
917 type: string
918 email:
919 type: string
920 responses:
921 '201':
922 description: Created
923"#,
924 Some("yaml"),
925 )
926 .unwrap();
927
928 let operation = spec
929 .spec
930 .paths
931 .paths
932 .get("/users")
933 .and_then(|p| p.as_item())
934 .and_then(|p| p.post.as_ref())
935 .unwrap();
936
937 let body = serde_json::json!({
938 "name": "John Doe",
939 "email": "john@example.com"
940 });
941
942 let result = RequestValidator::validate_request(
943 &spec,
944 operation,
945 &HashMap::new(),
946 &HashMap::new(),
947 &HashMap::new(),
948 Some(&body),
949 )
950 .unwrap();
951
952 assert!(result.valid || !result.errors.is_empty());
954 }
955
956 #[test]
957 fn test_validate_response_with_valid_body() {
958 let spec = crate::spec::OpenApiSpec::from_string(
959 r#"openapi: 3.0.0
960info:
961 title: Test API
962 version: 1.0.0
963paths:
964 /users:
965 get:
966 responses:
967 '200':
968 description: OK
969 content:
970 application/json:
971 schema:
972 type: object
973 properties:
974 id:
975 type: integer
976 name:
977 type: string
978"#,
979 Some("yaml"),
980 )
981 .unwrap();
982
983 let operation = spec
984 .spec
985 .paths
986 .paths
987 .get("/users")
988 .and_then(|p| p.as_item())
989 .and_then(|p| p.get.as_ref())
990 .unwrap();
991
992 let body = serde_json::json!({
993 "id": 1,
994 "name": "John Doe"
995 });
996
997 let result = ResponseValidator::validate_response(
998 &spec,
999 operation,
1000 200,
1001 &HashMap::new(),
1002 Some(&body),
1003 )
1004 .unwrap();
1005
1006 assert!(result.valid || !result.errors.is_empty());
1008 }
1009
1010 #[test]
1011 fn test_validate_response_with_invalid_status_code() {
1012 let spec = crate::spec::OpenApiSpec::from_string(
1013 r#"openapi: 3.0.0
1014info:
1015 title: Test API
1016 version: 1.0.0
1017paths:
1018 /users:
1019 get:
1020 responses:
1021 '200':
1022 description: OK
1023"#,
1024 Some("yaml"),
1025 )
1026 .unwrap();
1027
1028 let operation = spec
1029 .spec
1030 .paths
1031 .paths
1032 .get("/users")
1033 .and_then(|p| p.as_item())
1034 .and_then(|p| p.get.as_ref())
1035 .unwrap();
1036
1037 let result =
1039 ResponseValidator::validate_response(&spec, operation, 404, &HashMap::new(), None)
1040 .unwrap();
1041
1042 assert!(!result.valid);
1044 assert!(result.errors.iter().any(|e| e.contains("404")));
1045 }
1046
1047 #[test]
1048 fn test_validate_response_with_default_response() {
1049 let spec = crate::spec::OpenApiSpec::from_string(
1050 r#"openapi: 3.0.0
1051info:
1052 title: Test API
1053 version: 1.0.0
1054paths:
1055 /users:
1056 get:
1057 responses:
1058 '200':
1059 description: OK
1060 default:
1061 description: Error
1062"#,
1063 Some("yaml"),
1064 )
1065 .unwrap();
1066
1067 let operation = spec
1068 .spec
1069 .paths
1070 .paths
1071 .get("/users")
1072 .and_then(|p| p.as_item())
1073 .and_then(|p| p.get.as_ref())
1074 .unwrap();
1075
1076 let result =
1078 ResponseValidator::validate_response(&spec, operation, 500, &HashMap::new(), None)
1079 .unwrap();
1080
1081 assert!(result.valid || !result.errors.is_empty());
1083 }
1084
1085 #[test]
1086 fn test_validate_request_with_header_params() {
1087 let spec = crate::spec::OpenApiSpec::from_string(
1088 r#"openapi: 3.0.0
1089info:
1090 title: Test API
1091 version: 1.0.0
1092paths:
1093 /users:
1094 get:
1095 parameters:
1096 - name: X-API-Key
1097 in: header
1098 required: true
1099 schema:
1100 type: string
1101 responses:
1102 '200':
1103 description: OK
1104"#,
1105 Some("yaml"),
1106 )
1107 .unwrap();
1108
1109 let operation = spec
1110 .spec
1111 .paths
1112 .paths
1113 .get("/users")
1114 .and_then(|p| p.as_item())
1115 .and_then(|p| p.get.as_ref())
1116 .unwrap();
1117
1118 let mut headers = HashMap::new();
1119 headers.insert("X-API-Key".to_string(), "secret-key".to_string());
1120
1121 let result = RequestValidator::validate_request(
1122 &spec,
1123 operation,
1124 &HashMap::new(),
1125 &HashMap::new(),
1126 &headers,
1127 None,
1128 )
1129 .unwrap();
1130
1131 assert!(result.valid || !result.errors.is_empty());
1133 }
1134
1135 #[test]
1136 fn test_validate_response_with_headers() {
1137 let spec = crate::spec::OpenApiSpec::from_string(
1138 r#"openapi: 3.0.0
1139info:
1140 title: Test API
1141 version: 1.0.0
1142paths:
1143 /users:
1144 get:
1145 responses:
1146 '200':
1147 description: OK
1148 headers:
1149 X-Total-Count:
1150 schema:
1151 type: integer
1152 content:
1153 application/json:
1154 schema:
1155 type: object
1156"#,
1157 Some("yaml"),
1158 )
1159 .unwrap();
1160
1161 let operation = spec
1162 .spec
1163 .paths
1164 .paths
1165 .get("/users")
1166 .and_then(|p| p.as_item())
1167 .and_then(|p| p.get.as_ref())
1168 .unwrap();
1169
1170 let mut headers = HashMap::new();
1171 headers.insert("X-Total-Count".to_string(), "100".to_string());
1172
1173 let result = ResponseValidator::validate_response(
1174 &spec,
1175 operation,
1176 200,
1177 &headers,
1178 Some(&serde_json::json!({})),
1179 )
1180 .unwrap();
1181
1182 assert!(result.valid || !result.errors.is_empty());
1184 }
1185
1186 #[test]
1187 fn test_validate_request_with_cookie_params() {
1188 let spec = crate::spec::OpenApiSpec::from_string(
1189 r#"openapi: 3.0.0
1190info:
1191 title: Test API
1192 version: 1.0.0
1193paths:
1194 /users:
1195 get:
1196 parameters:
1197 - name: sessionId
1198 in: cookie
1199 required: true
1200 schema:
1201 type: string
1202 responses:
1203 '200':
1204 description: OK
1205"#,
1206 Some("yaml"),
1207 )
1208 .unwrap();
1209
1210 let operation = spec
1211 .spec
1212 .paths
1213 .paths
1214 .get("/users")
1215 .and_then(|p| p.as_item())
1216 .and_then(|p| p.get.as_ref())
1217 .unwrap();
1218
1219 let mut headers = HashMap::new();
1220 headers.insert("Cookie".to_string(), "sessionId=abc123; theme=dark".to_string());
1221
1222 let with_cookie = RequestValidator::validate_request(
1223 &spec,
1224 operation,
1225 &HashMap::new(),
1226 &HashMap::new(),
1227 &headers,
1228 None,
1229 )
1230 .unwrap();
1231
1232 assert!(with_cookie.valid, "expected cookie parameter to validate");
1233
1234 let missing_cookie = RequestValidator::validate_request(
1235 &spec,
1236 operation,
1237 &HashMap::new(),
1238 &HashMap::new(),
1239 &HashMap::new(),
1240 None,
1241 )
1242 .unwrap();
1243
1244 assert!(!missing_cookie.valid);
1245 assert!(missing_cookie
1246 .errors
1247 .iter()
1248 .any(|e| e.contains("Missing required cookie parameter: sessionId")));
1249 }
1250
1251 #[test]
1252 fn test_validate_request_with_referenced_request_body() {
1253 let spec = crate::spec::OpenApiSpec::from_string(
1254 r#"openapi: 3.0.0
1255info:
1256 title: Test API
1257 version: 1.0.0
1258paths:
1259 /users:
1260 post:
1261 requestBody:
1262 $ref: '#/components/requestBodies/UserRequest'
1263 responses:
1264 '201':
1265 description: Created
1266components:
1267 requestBodies:
1268 UserRequest:
1269 required: true
1270 content:
1271 application/json:
1272 schema:
1273 type: object
1274 properties:
1275 name:
1276 type: string
1277"#,
1278 Some("yaml"),
1279 )
1280 .unwrap();
1281
1282 let operation = spec
1283 .spec
1284 .paths
1285 .paths
1286 .get("/users")
1287 .and_then(|p| p.as_item())
1288 .and_then(|p| p.post.as_ref())
1289 .unwrap();
1290
1291 let body = serde_json::json!({
1292 "name": "John Doe"
1293 });
1294
1295 let result = RequestValidator::validate_request(
1296 &spec,
1297 operation,
1298 &HashMap::new(),
1299 &HashMap::new(),
1300 &HashMap::new(),
1301 Some(&body),
1302 )
1303 .unwrap();
1304
1305 assert!(result.valid || !result.errors.is_empty());
1307 }
1308
1309 #[test]
1310 fn test_validate_response_with_referenced_schema() {
1311 let spec = crate::spec::OpenApiSpec::from_string(
1312 r#"openapi: 3.0.0
1313info:
1314 title: Test API
1315 version: 1.0.0
1316paths:
1317 /users:
1318 get:
1319 responses:
1320 '200':
1321 description: OK
1322 content:
1323 application/json:
1324 schema:
1325 $ref: '#/components/schemas/User'
1326components:
1327 schemas:
1328 User:
1329 type: object
1330 properties:
1331 id:
1332 type: integer
1333 name:
1334 type: string
1335"#,
1336 Some("yaml"),
1337 )
1338 .unwrap();
1339
1340 let operation = spec
1341 .spec
1342 .paths
1343 .paths
1344 .get("/users")
1345 .and_then(|p| p.as_item())
1346 .and_then(|p| p.get.as_ref())
1347 .unwrap();
1348
1349 let body = serde_json::json!({
1350 "id": 1,
1351 "name": "John Doe"
1352 });
1353
1354 let result = ResponseValidator::validate_response(
1355 &spec,
1356 operation,
1357 200,
1358 &HashMap::new(),
1359 Some(&body),
1360 )
1361 .unwrap();
1362
1363 assert!(result.valid || !result.errors.is_empty());
1365 }
1366}