Skip to content

Commit

Permalink
chore: update
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <[email protected]>
  • Loading branch information
Stranger6667 committed Apr 30, 2024
1 parent 8d790d8 commit fc4faca
Show file tree
Hide file tree
Showing 2 changed files with 6 additions and 143 deletions.
67 changes: 1 addition & 66 deletions jsonschema/src/compilation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ mod tests {
use once_cell::sync::Lazy;
use regex::Regex;
use serde_json::{from_str, json, Map, Value};
use std::{fs::File, io::Read, path::Path, sync::Mutex};
use std::{fs::File, io::Read, path::Path};

fn load(path: &str, idx: usize) -> Value {
let path = Path::new(path);
Expand Down Expand Up @@ -540,69 +540,4 @@ mod tests {
assert!(compiled.validate(&instance).is_err());
assert!(!compiled.is_valid(&instance));
}

#[test]
fn custom_keyword_with_inner_state() {
// Define a custom keyword validator that wraps "minimum"
// but maintains a counter of how many times the validator was applied.
struct CountingValidator {
amount: i64,
count: Mutex<i64>,
}

impl CountingValidator {
fn increment(&self) {
let mut count = self.count.lock().expect("Lock is poisoned");
*count += self.amount;
}
}

impl Keyword for CountingValidator {
fn validate<'instance>(
&self,
_: &'instance Value,
_: &InstancePath,
) -> ErrorIterator<'instance> {
self.increment();
Box::new(None.into_iter())
}

fn is_valid(&self, _: &Value) -> bool {
self.increment();
true
}
}

fn countme_factory<'a>(
_: &'a Map<String, Value>,
schema: &'a Value,
_: JSONPointer,
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
let amount = schema.as_i64().expect("countme value must be integer");
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 = 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.
// Because the schema has "countme" in it, whenever we run validation we should expect the validator's count to increase
let instance_ok = json!("i am a string");
assert_eq!(*count.lock().expect("Lock is poinsoned"), 0);
assert!(compiled.validate(&instance_ok).is_err());
assert_eq!(*count.lock().expect("Lock is poinsoned"), 3);
assert!(!compiled.is_valid(&instance_ok));
assert_eq!(*count.lock().expect("Lock is poinsoned"), 6);

// TODO compile a schema that doesn't have "countme" and ensure count does not increase
}
}
82 changes: 5 additions & 77 deletions jsonschema/src/keywords/custom.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,8 @@
use crate::compilation::context::CompilationContext;
use crate::keywords::CompilationResult;
use crate::paths::{InstancePath, JSONPointer, PathChunk};
use crate::paths::{InstancePath, JSONPointer};
use crate::validator::Validate;
use crate::{ErrorIterator, ValidationError};
use serde_json::{Map, Value};
use std::fmt::{Display, Formatter};
use std::sync::Arc;

/// Custom keyword validation implemented by user provided validation functions.
pub(crate) struct CompiledCustomKeywordValidator {
schema: Arc<Value>,
subschema: Arc<Value>,
subschema_path: JSONPointer,
validator: Box<dyn CustomKeywordValidator>,
}

impl Display for CompiledCustomKeywordValidator {
fn fmt(&self, _: &mut Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}

impl Validate for CompiledCustomKeywordValidator {
fn validate<'instance>(
&self,
instance: &'instance Value,
instance_path: &InstancePath,
) -> ErrorIterator<'instance> {
self.validator.validate(
instance,
instance_path.into(),
self.subschema.clone(),
self.subschema_path.clone(),
self.schema.clone(),
)
}

fn is_valid(&self, instance: &Value) -> bool {
self.validator
.is_valid(instance, &self.subschema, &self.schema)
}
}

pub(crate) fn compile<'a>(
context: &CompilationContext,
keyword: impl Into<PathChunk>,
validator: Box<dyn CustomKeywordValidator>,
subschema: Value,
schema: Value,
) -> CompilationResult<'a> {
let subschema_path = context.as_pointer_with(keyword);
Ok(Box::new(CompiledCustomKeywordValidator {
schema: Arc::new(schema),
subschema: Arc::new(subschema),
subschema_path,
validator,
}))
}

mod sealed {
pub trait Sealed {}
Expand Down Expand Up @@ -95,36 +41,18 @@ impl Validate for CustomKeyword {
/// Trait that allows implementing custom validation for keywords.
pub trait Keyword: Send + Sync {
fn is_valid(&self, instance: &Value) -> bool;
fn validate<'instance>(
&self,
instance: &'instance Value,
instance_path: &InstancePath,
) -> ErrorIterator<'instance>;
}

pub trait CustomKeywordValidator: Send + Sync {
/// Validate [instance](Value) according to a custom specification
///
/// A custom keyword validator may be used when a validation that cannot, or
/// cannot be be easily or efficiently expressed in JSON schema.
/// A custom keyword validator may be used when a validation that cannot be
/// easily or efficiently expressed in JSON schema.
///
/// The custom validation is applied in addition to the JSON schema validation.
/// Validate an instance returning any and all detected validation errors
/// Validate an instance returning any and all detected validation errors.
fn validate<'instance>(
&self,
instance: &'instance Value,
instance_path: JSONPointer,
subschema: Arc<Value>,
subschema_path: JSONPointer,
schema: Arc<Value>,
instance_path: &InstancePath,
) -> ErrorIterator<'instance>;
/// Determine if an instance is valid
fn is_valid<'schema>(
&self,
instance: &Value,
subschema: &'schema Value,
schema: &'schema Value,
) -> bool;
}

pub trait KeywordFactory: Send + Sync + sealed::Sealed {
Expand Down

0 comments on commit fc4faca

Please sign in to comment.