use parking_lot::RwLock;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::sync::Arc;
use crate::catalog::manager::CatalogManager;
use crate::catalog::operations::QueryType;
use crate::exec::ExecutionError;
use crate::schema::enforcement::config::SchemaEnforcementConfig;
use crate::schema::types::GraphTypeDefinition;
use crate::schema::types::SchemaEnforcementMode;
use crate::schema::validator::SchemaValidator;
pub struct RuntimeValidator {
catalog_manager: Arc<RwLock<CatalogManager>>,
schema_validator: SchemaValidator,
enforcement_config: SchemaEnforcementConfig,
}
impl RuntimeValidator {
pub fn new(catalog_manager: Arc<RwLock<CatalogManager>>) -> Self {
let schema_validator = SchemaValidator::new(catalog_manager.clone());
Self {
catalog_manager,
schema_validator,
enforcement_config: SchemaEnforcementConfig::default(),
}
}
#[allow(dead_code)] pub fn with_config(
catalog_manager: Arc<RwLock<CatalogManager>>,
config: SchemaEnforcementConfig,
) -> Self {
let schema_validator = SchemaValidator::new(catalog_manager.clone());
Self {
catalog_manager,
schema_validator,
enforcement_config: config,
}
}
pub fn validate_insert(
&self,
graph_name: &str,
label: &str,
properties: &HashMap<String, Value>,
) -> Result<(), ExecutionError> {
if !self.enforcement_config.validate_on_write {
return Ok(());
}
let graph_type = match self.get_graph_type(graph_name) {
Ok(Some(gt)) => gt,
Ok(None) => {
match self.enforcement_config.mode {
SchemaEnforcementMode::Strict => {
return Err(ExecutionError::SchemaValidation(format!(
"No schema defined for graph '{}'",
graph_name
)));
}
SchemaEnforcementMode::Advisory => {
log::warn!(
"No schema defined for graph '{}', skipping validation",
graph_name
);
return Ok(());
}
SchemaEnforcementMode::Disabled => return Ok(()),
}
}
Err(e) => return Err(e),
};
match self
.schema_validator
.validate_node_with_type(&graph_type, label, properties)
{
Ok(()) => Ok(()),
Err(validation_error) => match self.enforcement_config.mode {
SchemaEnforcementMode::Strict => Err(ExecutionError::SchemaValidation(
validation_error.to_string(),
)),
SchemaEnforcementMode::Advisory => {
log::warn!("Schema validation warning: {}", validation_error);
Ok(())
}
SchemaEnforcementMode::Disabled => Ok(()),
},
}
}
#[allow(dead_code)] pub fn validate_update(
&self,
graph_name: &str,
label: &str,
properties: &HashMap<String, Value>,
is_partial: bool,
) -> Result<(), ExecutionError> {
if !self.enforcement_config.validate_on_write {
return Ok(());
}
let graph_type = match self.get_graph_type(graph_name) {
Ok(Some(gt)) => gt,
Ok(None) => {
match self.enforcement_config.mode {
SchemaEnforcementMode::Strict => {
return Err(ExecutionError::SchemaValidation(format!(
"No schema defined for graph '{}'",
graph_name
)));
}
_ => return Ok(()),
}
}
Err(e) => return Err(e),
};
if is_partial {
match self
.schema_validator
.validate_partial_node(&graph_type, label, properties)
{
Ok(()) => Ok(()),
Err(validation_error) => match self.enforcement_config.mode {
SchemaEnforcementMode::Strict => Err(ExecutionError::SchemaValidation(
validation_error.to_string(),
)),
SchemaEnforcementMode::Advisory => {
log::warn!("Schema validation warning: {}", validation_error);
Ok(())
}
SchemaEnforcementMode::Disabled => Ok(()),
},
}
} else {
self.validate_insert(graph_name, label, properties)
}
}
#[allow(dead_code)] pub fn validate_edge_insert(
&self,
graph_name: &str,
edge_type: &str,
from_label: &str,
to_label: &str,
properties: &HashMap<String, Value>,
) -> Result<(), ExecutionError> {
if !self.enforcement_config.validate_on_write {
return Ok(());
}
let graph_type = match self.get_graph_type(graph_name) {
Ok(Some(gt)) => gt,
Ok(None) => match self.enforcement_config.mode {
SchemaEnforcementMode::Strict => {
return Err(ExecutionError::SchemaValidation(format!(
"No schema defined for graph '{}'",
graph_name
)));
}
_ => return Ok(()),
},
Err(e) => return Err(e),
};
match self.schema_validator.validate_edge(
&graph_type,
edge_type,
from_label,
to_label,
properties,
) {
Ok(()) => Ok(()),
Err(validation_error) => match self.enforcement_config.mode {
SchemaEnforcementMode::Strict => Err(ExecutionError::SchemaValidation(
validation_error.to_string(),
)),
SchemaEnforcementMode::Advisory => {
log::warn!("Schema validation warning: {}", validation_error);
Ok(())
}
SchemaEnforcementMode::Disabled => Ok(()),
},
}
}
fn get_graph_type(
&self,
graph_name: &str,
) -> Result<Option<GraphTypeDefinition>, ExecutionError> {
let catalog_manager = self.catalog_manager.read();
match catalog_manager.query_read_only(
"graph",
QueryType::GetGraph,
json!({ "name": graph_name }),
) {
Ok(response) => {
if let Some(data) = response.data() {
if let Some(graph_type_name) = data.get("graph_type").and_then(|v| v.as_str()) {
match catalog_manager.query_read_only(
"graph_type",
QueryType::GetGraphType,
json!({ "name": graph_type_name }),
) {
Ok(type_response) => {
if let Some(type_data) = type_response.data() {
match serde_json::from_value::<GraphTypeDefinition>(
type_data.clone(),
) {
Ok(graph_type) => Ok(Some(graph_type)),
Err(_) => Ok(None),
}
} else {
Ok(None)
}
}
Err(_) => Ok(None),
}
} else {
Ok(None)
}
} else {
Ok(None)
}
}
Err(_) => {
Ok(None)
}
}
}
#[allow(dead_code)] pub fn set_enforcement_mode(&mut self, mode: SchemaEnforcementMode) {
self.enforcement_config.mode = mode;
}
#[allow(dead_code)] pub fn set_validate_on_write(&mut self, enabled: bool) {
self.enforcement_config.validate_on_write = enabled;
}
#[allow(dead_code)] pub fn set_validate_on_read(&mut self, enabled: bool) {
self.enforcement_config.validate_on_read = enabled;
}
#[allow(dead_code)] pub fn get_config(&self) -> &SchemaEnforcementConfig {
&self.enforcement_config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runtime_validator_creation() {
}
#[test]
fn test_enforcement_modes() {
let config_strict = SchemaEnforcementConfig {
mode: SchemaEnforcementMode::Strict,
validate_on_write: true,
validate_on_read: false,
allow_unknown_properties: false,
auto_create_indexes: false,
log_warnings: true,
allow_schema_drift: false,
};
assert_eq!(config_strict.mode, SchemaEnforcementMode::Strict);
assert!(config_strict.validate_on_write);
}
}