mockforge_core/openapi_routes/
validation.rs

1//! Request/response validation logic
2//!
3//! This module provides validation functionality for OpenAPI-based routes,
4//! including request validation, response validation, and error handling.
5
6use jsonschema::validate;
7use serde::Deserialize;
8use serde_json::Value;
9use std::collections::HashMap;
10
11/// Validation mode for requests and responses
12#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)]
13pub enum ValidationMode {
14    Disabled,
15    #[default]
16    Warn,
17    Enforce,
18}
19
20/// Validation options for OpenAPI route validation
21#[derive(Debug, Clone)]
22pub struct ValidationOptions {
23    pub request_mode: ValidationMode,
24    pub aggregate_errors: bool,
25    pub validate_responses: bool,
26    pub overrides: HashMap<String, ValidationMode>,
27    /// Skip validation for request paths starting with any of these prefixes
28    pub admin_skip_prefixes: Vec<String>,
29    /// Expand templating tokens in responses/examples
30    pub response_template_expand: bool,
31    /// HTTP status code to return when validation fails
32    pub validation_status: Option<u16>,
33}
34
35impl Default for ValidationOptions {
36    fn default() -> Self {
37        Self {
38            request_mode: ValidationMode::Enforce,
39            aggregate_errors: true,
40            validate_responses: false,
41            overrides: HashMap::new(),
42            admin_skip_prefixes: Vec::new(),
43            response_template_expand: false,
44            validation_status: None,
45        }
46    }
47}
48
49/// Validation error information
50#[derive(Debug, Clone, Deserialize)]
51pub struct ValidationError {
52    pub field: String,
53    pub message: String,
54    pub expected: Option<Value>,
55    pub actual: Option<Value>,
56}
57
58/// Validation result
59#[derive(Debug, Clone)]
60pub struct ValidationResult {
61    pub is_valid: bool,
62    pub errors: Vec<ValidationError>,
63    pub warnings: Vec<ValidationError>,
64}
65
66/// Validation context for tracking errors during validation
67#[derive(Debug, Default)]
68pub struct ValidationContext {
69    errors: Vec<ValidationError>,
70    warnings: Vec<ValidationError>,
71}
72
73impl ValidationContext {
74    /// Create a new validation context
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// Add an error to the validation context
80    pub fn add_error(&mut self, field: String, message: String) {
81        self.errors.push(ValidationError {
82            field,
83            message,
84            expected: None,
85            actual: None,
86        });
87    }
88
89    /// Add an error with expected and actual values
90    pub fn add_error_with_values(
91        &mut self,
92        field: String,
93        message: String,
94        expected: Value,
95        actual: Value,
96    ) {
97        self.errors.push(ValidationError {
98            field,
99            message,
100            expected: Some(expected),
101            actual: Some(actual),
102        });
103    }
104
105    /// Add a warning to the validation context
106    pub fn add_warning(&mut self, field: String, message: String) {
107        self.warnings.push(ValidationError {
108            field,
109            message,
110            expected: None,
111            actual: None,
112        });
113    }
114
115    /// Get the validation result
116    pub fn result(&self) -> ValidationResult {
117        ValidationResult {
118            is_valid: self.errors.is_empty(),
119            errors: self.errors.clone(),
120            warnings: self.warnings.clone(),
121        }
122    }
123
124    /// Check if validation has errors
125    pub fn has_errors(&self) -> bool {
126        !self.errors.is_empty()
127    }
128
129    /// Check if validation has warnings
130    pub fn has_warnings(&self) -> bool {
131        !self.warnings.is_empty()
132    }
133}
134
135/// Validate a JSON value against a schema
136pub fn validate_json_value(value: &Value, schema: &Value) -> ValidationResult {
137    let mut ctx = ValidationContext::new();
138
139    // Basic validation - check required fields and types
140    validate_against_schema(value, schema, &mut ctx);
141
142    ctx.result()
143}
144
145/// Validate a value against a JSON schema
146fn validate_against_schema(value: &Value, schema: &Value, ctx: &mut ValidationContext) {
147    // Use proper JSON Schema validation
148    if let Err(error) = validate(schema, value) {
149        let field = error.instance_path.to_string();
150        let message = error.to_string();
151        ctx.add_error(field, message);
152    }
153}