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,
15 #[default]
16 Warn,
17 Enforce,
18}
19
20#[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 pub admin_skip_prefixes: Vec<String>,
29 pub response_template_expand: bool,
31 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#[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#[derive(Debug, Clone)]
60pub struct ValidationResult {
61 pub is_valid: bool,
62 pub errors: Vec<ValidationError>,
63 pub warnings: Vec<ValidationError>,
64}
65
66#[derive(Debug, Default)]
68pub struct ValidationContext {
69 errors: Vec<ValidationError>,
70 warnings: Vec<ValidationError>,
71}
72
73impl ValidationContext {
74 pub fn new() -> Self {
76 Self::default()
77 }
78
79 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 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 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 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 pub fn has_errors(&self) -> bool {
126 !self.errors.is_empty()
127 }
128
129 pub fn has_warnings(&self) -> bool {
131 !self.warnings.is_empty()
132 }
133}
134
135pub fn validate_json_value(value: &Value, schema: &Value) -> ValidationResult {
137 let mut ctx = ValidationContext::new();
138
139 validate_against_schema(value, schema, &mut ctx);
141
142 ctx.result()
143}
144
145fn validate_against_schema(value: &Value, schema: &Value, ctx: &mut ValidationContext) {
147 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}