use std::error::Error;
use std::path::PathBuf;
use colored::Colorize;
use jsonschema::error::ValidationErrorKind;
use serde_json::Value;
use std::convert::TryFrom;
use crate::datamodel::DataModel;
use jsonschema::validator_for;
#[derive(Debug)]
pub struct ValidationError {
pub instance_path: String,
pub schema_path: String,
pub message: String,
pub kind: ValidationErrorKind,
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Validation Error: Instance {} violates schema at {}: {}",
self.instance_path.red().bold(),
self.schema_path.green().bold(),
self.message.yellow().bold()
)
}
}
impl From<jsonschema::ValidationError<'_>> for ValidationError {
fn from(err: jsonschema::ValidationError) -> Self {
ValidationError {
instance_path: err.instance_path.to_string(),
schema_path: err.schema_path.to_string(),
message: err.to_string(),
kind: err.kind,
}
}
}
pub fn validate_json<T: Into<DatasetInput>>(
dataset: T,
model: &DataModel,
root: Option<String>,
) -> Result<Vec<ValidationError>, Box<dyn Error>> {
let dataset_input: DatasetInput = dataset.into();
let value: Value = dataset_input.try_into()?;
let schema = model.json_schema(root, false)?;
let schema_value: Value = serde_json::from_str(&schema)?;
let validator = validator_for(&schema_value)?;
let result = validator.iter_errors(&value);
let mut errors: Vec<ValidationError> = Vec::new();
for err in result {
errors.push(ValidationError::from(err));
}
Ok(errors)
}
pub enum DatasetInput {
Path(PathBuf),
Value(Value),
String(String),
}
impl From<PathBuf> for DatasetInput {
fn from(path: PathBuf) -> Self {
DatasetInput::Path(path)
}
}
impl From<Value> for DatasetInput {
fn from(value: Value) -> Self {
DatasetInput::Value(value)
}
}
impl From<&mut Value> for DatasetInput {
fn from(value: &mut Value) -> Self {
DatasetInput::Value(value.clone())
}
}
impl From<String> for DatasetInput {
fn from(string: String) -> Self {
DatasetInput::String(string)
}
}
impl TryFrom<DatasetInput> for Value {
type Error = Box<dyn Error>;
fn try_from(input: DatasetInput) -> Result<Self, Self::Error> {
match input {
DatasetInput::Path(path) => {
let content = std::fs::read_to_string(path)?;
let value: Value = serde_json::from_str(&content)?;
Ok(value)
}
DatasetInput::Value(value) => Ok(value),
DatasetInput::String(string) => {
let value: Value = serde_json::from_str(&string)?;
Ok(value)
}
}
}
}