mockforge_core/openapi_routes/
validation.rs1use jsonschema::validate;
7use serde::Deserialize;
8use serde_json::Value;
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)]
13pub enum ValidationMode {
14 Disabled,
16 #[default]
18 Warn,
19 Enforce,
21}
22
23#[derive(Debug, Clone)]
25pub struct ValidationOptions {
26 pub request_mode: ValidationMode,
28 pub aggregate_errors: bool,
30 pub validate_responses: bool,
32 pub overrides: HashMap<String, ValidationMode>,
34 pub admin_skip_prefixes: Vec<String>,
36 pub response_template_expand: bool,
38 pub validation_status: Option<u16>,
40}
41
42impl Default for ValidationOptions {
43 fn default() -> Self {
44 Self {
45 request_mode: ValidationMode::Enforce,
46 aggregate_errors: true,
47 validate_responses: false,
48 overrides: HashMap::new(),
49 admin_skip_prefixes: Vec::new(),
50 response_template_expand: false,
51 validation_status: None,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Deserialize)]
58pub struct ValidationError {
59 pub field: String,
61 pub message: String,
63 pub expected: Option<Value>,
65 pub actual: Option<Value>,
67}
68
69#[derive(Debug, Clone)]
71pub struct ValidationResult {
72 pub is_valid: bool,
74 pub errors: Vec<ValidationError>,
76 pub warnings: Vec<ValidationError>,
78}
79
80#[derive(Debug, Default)]
82pub struct ValidationContext {
83 errors: Vec<ValidationError>,
84 warnings: Vec<ValidationError>,
85}
86
87impl ValidationContext {
88 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn add_error(&mut self, field: String, message: String) {
95 self.errors.push(ValidationError {
96 field,
97 message,
98 expected: None,
99 actual: None,
100 });
101 }
102
103 pub fn add_error_with_values(
105 &mut self,
106 field: String,
107 message: String,
108 expected: Value,
109 actual: Value,
110 ) {
111 self.errors.push(ValidationError {
112 field,
113 message,
114 expected: Some(expected),
115 actual: Some(actual),
116 });
117 }
118
119 pub fn add_warning(&mut self, field: String, message: String) {
121 self.warnings.push(ValidationError {
122 field,
123 message,
124 expected: None,
125 actual: None,
126 });
127 }
128
129 pub fn result(&self) -> ValidationResult {
131 ValidationResult {
132 is_valid: self.errors.is_empty(),
133 errors: self.errors.clone(),
134 warnings: self.warnings.clone(),
135 }
136 }
137
138 pub fn has_errors(&self) -> bool {
140 !self.errors.is_empty()
141 }
142
143 pub fn has_warnings(&self) -> bool {
145 !self.warnings.is_empty()
146 }
147}
148
149pub fn validate_json_value(value: &Value, schema: &Value) -> ValidationResult {
151 let mut ctx = ValidationContext::new();
152
153 validate_against_schema(value, schema, &mut ctx);
155
156 ctx.result()
157}
158
159fn validate_against_schema(value: &Value, schema: &Value, ctx: &mut ValidationContext) {
161 if let Err(error) = validate(schema, value) {
163 let field = error.instance_path.to_string();
164 let message = error.to_string();
165 ctx.add_error(field, message);
166 }
167}