use jsonschema::validate;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)]
pub enum ValidationMode {
Disabled,
#[default]
Warn,
Enforce,
}
#[derive(Debug, Clone)]
pub struct ValidationOptions {
pub request_mode: ValidationMode,
pub aggregate_errors: bool,
pub validate_responses: bool,
pub overrides: HashMap<String, ValidationMode>,
pub admin_skip_prefixes: Vec<String>,
pub response_template_expand: bool,
pub validation_status: Option<u16>,
}
impl Default for ValidationOptions {
fn default() -> Self {
Self {
request_mode: ValidationMode::Enforce,
aggregate_errors: true,
validate_responses: false,
overrides: HashMap::new(),
admin_skip_prefixes: Vec::new(),
response_template_expand: false,
validation_status: None,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ValidationError {
pub field: String,
pub message: String,
pub expected: Option<Value>,
pub actual: Option<Value>,
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub is_valid: bool,
pub errors: Vec<ValidationError>,
pub warnings: Vec<ValidationError>,
}
#[derive(Debug, Default)]
pub struct ValidationContext {
errors: Vec<ValidationError>,
warnings: Vec<ValidationError>,
}
impl ValidationContext {
pub fn new() -> Self {
Self::default()
}
pub fn add_error(&mut self, field: String, message: String) {
self.errors.push(ValidationError {
field,
message,
expected: None,
actual: None,
});
}
pub fn add_error_with_values(
&mut self,
field: String,
message: String,
expected: Value,
actual: Value,
) {
self.errors.push(ValidationError {
field,
message,
expected: Some(expected),
actual: Some(actual),
});
}
pub fn add_warning(&mut self, field: String, message: String) {
self.warnings.push(ValidationError {
field,
message,
expected: None,
actual: None,
});
}
pub fn result(&self) -> ValidationResult {
ValidationResult {
is_valid: self.errors.is_empty(),
errors: self.errors.clone(),
warnings: self.warnings.clone(),
}
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
}
pub fn validate_json_value(value: &Value, schema: &Value) -> ValidationResult {
let mut ctx = ValidationContext::new();
validate_against_schema(value, schema, &mut ctx);
ctx.result()
}
fn validate_against_schema(value: &Value, schema: &Value, ctx: &mut ValidationContext) {
if let Err(error) = validate(schema, value) {
let field = error.instance_path.to_string();
let message = error.to_string();
ctx.add_error(field, message);
}
}