chore: update

Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
This commit is contained in:
Dmitry Dygalo 2024-04-29 21:41:58 +02:00
parent 8a7a77ca1a
commit 0bedb7b20f
No known key found for this signature in database
GPG Key ID: 26834366E8FDCFEF
3 changed files with 63 additions and 58 deletions

View File

@ -202,7 +202,7 @@ pub(crate) fn compile_validators<'a>(
// it may override existing keyword behavior
if let Some(factory) = context.config.get_keyword_factory(keyword) {
// TODO: Pass other arguments
let validator = CustomKeyword::new(factory.init(subschema));
let validator = CustomKeyword::new(factory.init(subschema)?);
let validator: BoxedValidator = Box::new(validator);
// let validator = compile(
// &context,
@ -357,9 +357,6 @@ mod tests {
}
fn is_valid(&self, instance: &Value) -> bool {
// if subschema.as_str().map_or(true, |str| str != "ascii-keys") {
// return false; // Invalid schema
// }
for (key, _value) in instance.as_object().unwrap() {
if !key.is_ascii() {
return false;
@ -369,22 +366,26 @@ mod tests {
}
}
fn custom_object_type_factory(
schema: &Value,
) -> Result<Box<dyn Keyword>, ValidationError<'_>> {
if schema.as_str().map_or(true, |key| key != "ascii-keys") {
// let error = ValidationError {
// instance: Cow::Borrowed(instance),
// kind: crate::error::ValidationErrorKind::Schema,
// instance_path,
// schema_path: subschema_path,
// };
// return Box::new(Some(error).into_iter()); // Invalid schema
}
Ok(Box::new(CustomObjectValidator))
}
// Define a JSON schema that enforces the top level object has ASCII keys and has at least 1 property
let schema =
json!({ "custom-object-type": "ascii-keys", "type": "object", "minProperties": 1 });
let json_schema = JSONSchema::options()
.with_keyword("custom-object-type", |schema: &Value| -> Box<dyn Keyword> {
if schema.as_str().map_or(true, |key| key != "ascii-keys") {
// let error = ValidationError {
// instance: Cow::Borrowed(instance),
// kind: crate::error::ValidationErrorKind::Schema,
// instance_path,
// schema_path: subschema_path,
// };
// return Box::new(Some(error).into_iter()); // Invalid schema
}
Box::new(CustomObjectValidator)
})
.with_keyword("custom-object-type", custom_object_type_factory)
.compile(&schema)
.unwrap();
@ -513,26 +514,28 @@ mod tests {
}
}
fn custom_minimum_factory(schema: &Value) -> Result<Box<dyn Keyword>, ValidationError<'_>> {
let limit = match schema {
Value::Number(limit) => limit.as_f64().expect("Always valid"),
_ => {
todo!()
// let error = ValidationError {
// instance: Cow::Borrowed(instance),
// kind: crate::error::ValidationErrorKind::Schema,
// instance_path,
// schema_path: subschema_path,
// };
// return Box::new(Some(error).into_iter()); // Invalid schema
}
};
Ok(Box::new(CustomMinimumValidator { limit }))
}
// define compilation options that include the custom format and the overridden keyword
let mut options = JSONSchema::options();
let options = options
.with_format("currency", currency_format_checker)
.with_keyword("minimum", |schema: &Value| -> Box<dyn Keyword> {
let limit = match schema {
Value::Number(limit) => limit.as_f64().expect("Always valid"),
_ => {
todo!()
// let error = ValidationError {
// instance: Cow::Borrowed(instance),
// kind: crate::error::ValidationErrorKind::Schema,
// instance_path,
// schema_path: subschema_path,
// };
// return Box::new(Some(error).into_iter()); // Invalid schema
}
};
Box::new(CustomMinimumValidator { limit })
});
.with_keyword("minimum", custom_minimum_factory);
// Define a schema that includes both the custom format and the overridden keyword
let schema = json!({ "minimum": 2, "type": "string", "format": "currency" });
@ -605,24 +608,24 @@ mod tests {
}
}
// define compilation options that include the custom format and the overridden keyword
let count = Mutex::new(0);
let mut options = JSONSchema::options();
let options = options.with_keyword("countme", |schema: &Value| -> Box<dyn Keyword> {
// TODO: Return Result
fn countme_factory(schema: &Value) -> Result<Box<dyn Keyword>, ValidationError<'_>> {
let amount = match schema {
Value::Number(x) => x.as_i64().expect("countme value must be integer"),
_ => panic!("Validator requires numeric values"),
};
Box::new(CountingValidator {
Ok(Box::new(CountingValidator {
amount,
count: Mutex::new(0),
})
});
}))
}
// define compilation options that include the custom format and the overridden keyword
let count = Mutex::new(0);
// Define a schema that includes the custom keyword and therefore should increase the count
let schema = json!({ "countme": 3, "type": "string" });
let compiled = options.compile(&schema).unwrap();
let compiled = JSONSchema::options()
.with_keyword("countme", countme_factory)
.compile(&schema)
.unwrap();
// TODO: Communicate the increment changes via `validate` output, e.g. fail after N
// increments, etc.

View File

@ -2,7 +2,7 @@ use crate::compilation::context::CompilationContext;
use crate::keywords::CompilationResult;
use crate::paths::{InstancePath, JSONPointer, PathChunk};
use crate::validator::Validate;
use crate::ErrorIterator;
use crate::{ErrorIterator, ValidationError};
use serde_json::Value;
use std::fmt::{Display, Formatter};
use std::sync::Arc;
@ -128,17 +128,19 @@ pub trait CustomKeywordValidator: Send + Sync {
}
pub trait KeywordFactory: Send + Sync + sealed::Sealed {
// TODO: Should return `Result`
fn init(&self, schema: &Value) -> Box<dyn Keyword>;
fn init<'a>(&self, schema: &'a Value) -> Result<Box<dyn Keyword>, ValidationError<'a>>;
}
impl<F> sealed::Sealed for F where F: Fn(&Value) -> Box<dyn Keyword> + Send + Sync {}
impl<F> sealed::Sealed for F where
F: for<'a> Fn(&'a Value) -> Result<Box<dyn Keyword>, ValidationError<'a>> + Send + Sync
{
}
impl<F> KeywordFactory for F
where
F: Fn(&Value) -> Box<dyn Keyword> + Send + Sync,
F: for<'a> Fn(&'a Value) -> Result<Box<dyn Keyword>, ValidationError<'a>> + Send + Sync,
{
fn init(&self, schema: &Value) -> Box<dyn Keyword> {
fn init<'a>(&self, schema: &'a Value) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
self(schema)
}
}

View File

@ -107,14 +107,14 @@ pub enum PathChunk {
#[derive(Debug, Clone)]
pub(crate) struct InstancePath<'a> {
pub(crate) chunk: Option<PathChunk>,
pub(crate) chunk: PathChunk,
pub(crate) parent: Option<&'a InstancePath<'a>>,
}
impl<'a> InstancePath<'a> {
pub(crate) const fn new() -> Self {
InstancePath {
chunk: None,
chunk: PathChunk::Index(0),
parent: None,
}
}
@ -122,7 +122,7 @@ impl<'a> InstancePath<'a> {
#[inline]
pub(crate) fn push(&'a self, chunk: impl Into<PathChunk>) -> Self {
InstancePath {
chunk: Some(chunk.into()),
chunk: chunk.into(),
parent: Some(self),
}
}
@ -130,14 +130,14 @@ impl<'a> InstancePath<'a> {
pub(crate) fn to_vec(&'a self) -> Vec<PathChunk> {
// The path capacity should be the average depth so we avoid extra allocations
let mut result = Vec::with_capacity(6);
let mut current = self;
if let Some(chunk) = &current.chunk {
result.push(chunk.clone())
let mut head = self;
if head.parent.is_some() {
result.push(head.chunk.clone())
}
while let Some(next) = current.parent {
current = next;
if let Some(chunk) = &current.chunk {
result.push(chunk.clone())
while let Some(next) = head.parent {
head = next;
if head.parent.is_some() {
result.push(head.chunk.clone());
}
}
result.reverse();