use super::ConstraintError;
use jsonschema::error::ValidationErrorKind;
use jsonschema::{ReferencingError, Retrieve, Uri, Validator};
use serde_json::Value;
const QUOTED_ERRORS: usize = 3;
struct NoFetch;
impl Retrieve for NoFetch {
fn retrieve(&self, uri: &Uri<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
Err(format!(
"{} is outside the schema, and no reader fetches it",
uri.as_str()
)
.into())
}
}
fn validator(schema: &Value) -> Result<Validator, ConstraintError> {
jsonschema::options()
.with_retriever(NoFetch)
.build(schema)
.map_err(|e| match &e.kind {
ValidationErrorKind::Referencing(ReferencingError::Unretrievable { .. }) => {
ConstraintError::SchemaUnsupported(format!(
"the schema has a $ref to a resource it does not hold: {e}"
))
},
_ => ConstraintError::SchemaInvalid(format!(
"the schema is not a valid JSON Schema: {e}"
)),
})
}
pub fn check_schema(schema: &Value) -> Result<(), ConstraintError> {
validator(schema).map(|_| ())
}
pub fn second_reader(schema: &Value, text: &str) -> Result<(), ConstraintError> {
let doc: Value = serde_json::from_str(text).map_err(|e| {
ConstraintError::Violation(format!("the output is not one JSON document: {e}"))
})?;
let validator = validator(schema)?;
let errors: Vec<String> = validator
.iter_errors(&doc)
.take(QUOTED_ERRORS)
.map(|e| format!("{e} (at {})", e.instance_path))
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(ConstraintError::Violation(format!(
"the output fails the schema in a validator independent of the engine: {}",
errors.join("; ")
)))
}
}