use jsonschema::validator_for;
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum SchemaValidationMode {
#[default]
Off,
Strict,
Lenient,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CollectionSchema {
pub name: String,
pub schema: Value,
pub validation_mode: SchemaValidationMode,
}
impl CollectionSchema {
pub fn new(name: String, schema: Value, validation_mode: SchemaValidationMode) -> Self {
Self {
name,
schema,
validation_mode,
}
}
pub fn validation_mode(&self) -> SchemaValidationMode {
self.validation_mode
}
pub fn is_enabled(&self) -> bool {
self.validation_mode != SchemaValidationMode::Off
}
}
#[derive(Clone)]
pub struct SchemaValidator {
schema: CollectionSchema,
validator: Option<jsonschema::Validator>,
}
impl SchemaValidator {
pub fn new(schema: CollectionSchema) -> Result<Self, SchemaCompilationError> {
let validator = if schema.is_enabled() {
Some(
validator_for(&schema.schema)
.map_err(|e| SchemaCompilationError::InvalidSchema(e.to_string()))?,
)
} else {
None
};
Ok(Self { schema, validator })
}
pub fn validate(&self, document: &Value) -> Result<(), SchemaValidationError> {
if let Some(ref validator) = self.validator {
match self.schema.validation_mode {
SchemaValidationMode::Off => Ok(()),
SchemaValidationMode::Strict => {
let mut violations = Vec::new();
for error in validator.iter_errors(document) {
violations.push(ValidationViolation {
instance_path: error.instance_path().to_string(),
schema_path: error.schema_path().to_string(),
error: error.to_string(),
});
}
if violations.is_empty() {
Ok(())
} else {
Err(SchemaValidationError::SchemaViolations(violations))
}
}
SchemaValidationMode::Lenient => {
for error in validator.iter_errors(document) {
tracing::warn!(
schema = %self.schema.name,
path = %error.instance_path(),
violation = %error,
"Schema validation warning (lenient mode)"
);
}
Ok(())
}
}
} else {
Ok(())
}
}
pub fn schema(&self) -> &CollectionSchema {
&self.schema
}
}
#[derive(Debug, thiserror::Error)]
pub enum SchemaCompilationError {
#[error("Invalid JSON Schema: {0}")]
InvalidSchema(String),
}
#[derive(Debug, thiserror::Error, serde::Serialize)]
pub enum SchemaValidationError {
#[error("Schema validation failed with violations")]
SchemaViolations(Vec<ValidationViolation>),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ValidationViolation {
pub instance_path: String,
pub schema_path: String,
pub error: String,
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub is_valid: bool,
pub violations: Vec<ValidationViolation>,
}
impl ValidationResult {
pub fn valid() -> Self {
Self {
is_valid: true,
violations: Vec::new(),
}
}
pub fn invalid(violations: Vec<ValidationViolation>) -> Self {
Self {
is_valid: false,
violations,
}
}
}