Skip to content

Commit

Permalink
chore: update
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
  • Loading branch information
Stranger6667 committed Apr 20, 2024
1 parent 194a0b0 commit a240bd5
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 47 deletions.
46 changes: 20 additions & 26 deletions jsonschema/src/compilation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,11 @@ pub(crate) fn compile_validators<'a>(
}
// first check if this keyword was added as a custom keyword
// it may override existing keyword behavior
if let Some(keyword_definition) =
context.config.get_custom_keyword_definition(keyword)
{
if let Some(f) = context.config.get_custom_keyword_constructor(keyword) {
let validator = compile_custom_keyword_validator(
&context,
keyword.clone(),
keyword_definition.clone(),
f(),
subschema.clone(),
schema.clone(),
)?;
Expand Down Expand Up @@ -334,7 +332,7 @@ mod tests {
struct CustomObjectValidator;
impl CustomKeywordValidator for CustomObjectValidator {
fn validate<'instance>(
&mut self,
&self,
instance: &'instance Value,
instance_path: JSONPointer,
subschema: Arc<Value>,
Expand Down Expand Up @@ -365,7 +363,7 @@ mod tests {
Box::new(errors.into_iter())
}

fn is_valid(&mut self, instance: &Value, subschema: &Value, _schema: &Value) -> bool {
fn is_valid(&self, instance: &Value, subschema: &Value, _schema: &Value) -> bool {
if subschema.as_str().map_or(true, |str| str != "ascii-keys") {
return false; // Invalid schema
}
Expand All @@ -382,10 +380,7 @@ mod tests {
let schema =
json!({ "custom-object-type": "ascii-keys", "type": "object", "minProperties": 1 });
let json_schema = JSONSchema::options()
.with_custom_keyword(
"custom-object-type",
Arc::new(Mutex::new(Box::new(CustomObjectValidator))),
)
.with_custom_keyword("custom-object-type", || Box::new(CustomObjectValidator))
.compile(&schema)
.unwrap();

Expand Down Expand Up @@ -420,7 +415,7 @@ mod tests {
struct CustomMinimumValidator;
impl CustomKeywordValidator for CustomMinimumValidator {
fn validate<'instance>(
&mut self,
&self,
instance: &'instance Value,
instance_path: JSONPointer,
subschema: Arc<Value>,
Expand Down Expand Up @@ -488,7 +483,7 @@ mod tests {
Box::new(errors.into_iter())
}

fn is_valid(&mut self, instance: &Value, subschema: &Value, schema: &Value) -> bool {
fn is_valid(&self, instance: &Value, subschema: &Value, schema: &Value) -> bool {
let limit = match subschema {
Value::Number(limit) => limit,
_ => return false,
Expand Down Expand Up @@ -534,10 +529,7 @@ mod tests {
let mut options = JSONSchema::options();
let options = options
.with_format("currency", currency_format_checker)
.with_custom_keyword(
"minimum",
Arc::new(Mutex::new(Box::new(CustomMinimumValidator))),
);
.with_custom_keyword("minimum", || Box::new(CustomMinimumValidator));

// Define a schema that includes both the custom format and the overridden keyword
let schema = json!({ "minimum": 2, "type": "string", "format": "currency" });
Expand Down Expand Up @@ -583,19 +575,19 @@ mod tests {
// Define a custom keyword validator that wraps "minimum"
// but maintains a counter of how many times the validator was applied.
struct CountingValidator {
count: Arc<Mutex<i64>>,
count: Mutex<i64>,
}

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

impl CustomKeywordValidator for CountingValidator {
fn validate<'instance>(
&mut self,
&self,
_: &'instance Value,
_: JSONPointer,
subschema: Arc<Value>,
Expand All @@ -610,7 +602,7 @@ mod tests {
Box::new(None.into_iter())
}

fn is_valid(&mut self, _: &Value, subschema: &Value, _: &Value) -> bool {
fn is_valid(&self, _: &Value, subschema: &Value, _: &Value) -> bool {
let amount = match subschema {
Value::Number(x) => x.as_i64().expect("countme value must be integer"),
_ => return false,
Expand All @@ -621,18 +613,20 @@ mod tests {
}

// define compilation options that include the custom format and the overridden keyword
let count = Arc::new(Mutex::new(0));
let boxed: Box<dyn CustomKeywordValidator> = Box::new(CountingValidator {
count: count.clone(),
});
let validator = Arc::new(Mutex::new(boxed));
let count = Mutex::new(0);
let mut options = JSONSchema::options();
let options = options.with_custom_keyword("countme", validator);
let options = options.with_custom_keyword("countme", || {
Box::new(CountingValidator {
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();

// 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);
Expand Down
29 changes: 15 additions & 14 deletions jsonschema/src/compilation/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ use crate::{
};
use ahash::AHashMap;
use once_cell::sync::Lazy;
use std::{
fmt,
sync::{Arc, Mutex},
};
use std::{fmt, sync::Arc};

macro_rules! schema {
($name:ident, $path:expr) => {
Expand Down Expand Up @@ -281,7 +278,7 @@ pub struct CompilationOptions {
ignore_unknown_formats: bool,
custom_keywords: AHashMap<
String, // TODO<samgqroberts> 2024-04-13 should this also be a &'static str
Arc<Mutex<Box<dyn CustomKeywordValidator>>>,
CustomKeywordConstructor,
>,
}

Expand Down Expand Up @@ -659,7 +656,7 @@ impl CompilationOptions {
/// struct MyCustomValidator;
/// impl<'instance> CustomKeywordValidator<'instance, '_> for MyCustomValidator {
/// fn validate(
/// &mut self,
/// &self,
/// instance: &'instance Value,
/// instance_path: JSONPointer,
/// subschema: Arc<Value>,
Expand All @@ -670,13 +667,13 @@ impl CompilationOptions {
/// Box::new(None.into_iter())
/// }
/// fn is_valid(
/// &mut self,
/// &self,
/// instance: &Value,
/// subschema: &Value,
/// schema: &Value
/// ) -> bool {
/// // ... determine if instance is valid ...
/// return true;
/// true
/// }
/// }
///
Expand All @@ -689,19 +686,20 @@ impl CompilationOptions {
pub fn with_custom_keyword<T>(
&mut self,
keyword: T,
definition: Arc<Mutex<Box<dyn CustomKeywordValidator + 'static>>>,
definition: impl Fn() -> Box<dyn CustomKeywordValidator> + Send + Sync + 'static,
) -> &mut Self
where
T: Into<String>,
{
self.custom_keywords.insert(keyword.into(), definition);
self.custom_keywords
.insert(keyword.into(), Arc::new(definition));
self
}

pub(crate) fn get_custom_keyword_definition(
pub(crate) fn get_custom_keyword_constructor(
&self,
keyword: &str,
) -> Option<&Arc<Mutex<Box<dyn CustomKeywordValidator>>>> {
) -> Option<&CustomKeywordConstructor> {
self.custom_keywords.get(keyword)
}
}
Expand All @@ -721,6 +719,9 @@ impl fmt::Debug for CompilationOptions {
}
}

pub(crate) type CustomKeywordConstructor =
Arc<dyn Fn() -> Box<dyn CustomKeywordValidator> + Send + Sync>;

/// Trait that allows implementing custom validation for keywords.
pub trait CustomKeywordValidator: Send + Sync {
/// Validate [instance](serde_json::Value) according to a custom specification
Expand All @@ -731,7 +732,7 @@ pub trait CustomKeywordValidator: Send + Sync {
/// The custom validation is applied in addition to the JSON schema validation.
/// Validate an instance returning any and all detected validation errors
fn validate<'instance>(
&mut self,
&self,
instance: &'instance serde_json::Value,
instance_path: JSONPointer,
subschema: Arc<serde_json::Value>,
Expand All @@ -740,7 +741,7 @@ pub trait CustomKeywordValidator: Send + Sync {
) -> ErrorIterator<'instance>;
/// Determine if an instance is valid
fn is_valid<'schema>(
&mut self,
&self,
instance: &serde_json::Value,
subschema: &'schema serde_json::Value,
schema: &'schema serde_json::Value,
Expand Down
13 changes: 6 additions & 7 deletions jsonschema/src/keywords/custom_keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use crate::validator::Validate;
use crate::ErrorIterator;
use serde_json::Value;
use std::fmt::{Display, Formatter};
use std::sync::{Arc, Mutex};
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: Arc<Mutex<Box<dyn CustomKeywordValidator>>>,
validator: Box<dyn CustomKeywordValidator>,
}

impl Display for CompiledCustomKeywordValidator {
Expand All @@ -28,8 +28,7 @@ impl Validate for CompiledCustomKeywordValidator {
instance: &'instance Value,
instance_path: &InstancePath,
) -> ErrorIterator<'instance> {
let mut validator = self.validator.lock().expect("Access to validator");
validator.validate(
self.validator.validate(
instance,
instance_path.into(),
self.subschema.clone(),
Expand All @@ -39,15 +38,15 @@ impl Validate for CompiledCustomKeywordValidator {
}

fn is_valid(&self, instance: &Value) -> bool {
let mut validator = self.validator.lock().expect("Access to validator");
validator.is_valid(instance, &self.subschema, &self.schema)
self.validator
.is_valid(instance, &self.subschema, &self.schema)
}
}

pub(crate) fn compile_custom_keyword_validator<'a>(
context: &CompilationContext,
keyword: impl Into<PathChunk>,
validator: Arc<Mutex<Box<dyn CustomKeywordValidator>>>,
validator: Box<dyn CustomKeywordValidator>,
subschema: Value,
schema: Value,
) -> CompilationResult<'a> {
Expand Down

0 comments on commit a240bd5

Please sign in to comment.