chore: update
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
This commit is contained in:
parent
8a7a77ca1a
commit
0bedb7b20f
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) = ¤t.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) = ¤t.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();
|
||||
|
|
Loading…
Reference in New Issue