1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use crate::{
paths::{LazyLocation, Location, RefTracker},
validator::{Validate, ValidationContext},
ValidationError,
};
use serde_json::{Map, Value};
pub(crate) struct CustomKeyword {
inner: Box<dyn Keyword>,
location: Location,
keyword: String,
}
impl CustomKeyword {
pub(crate) fn new(inner: Box<dyn Keyword>, location: Location, keyword: String) -> Self {
Self {
inner,
location,
keyword,
}
}
}
impl Validate for CustomKeyword {
fn validate<'i>(
&self,
instance: &'i Value,
instance_path: &LazyLocation,
_tracker: Option<&RefTracker>,
_ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
self.inner
.validate(instance)
.map_err(|err| err.with_context(instance, instance_path, &self.location, &self.keyword))
}
fn is_valid(&self, instance: &Value, _ctx: &mut ValidationContext) -> bool {
self.inner.is_valid(instance)
}
}
/// Trait for implementing custom keyword validators.
///
/// Custom keywords extend JSON Schema validation with domain-specific rules.
///
/// # Example
///
/// ```rust
/// use jsonschema::{Keyword, ValidationError};
/// use serde_json::Value;
///
/// struct EvenNumberValidator;
///
/// impl Keyword for EvenNumberValidator {
/// fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
/// if let Some(n) = instance.as_u64() {
/// if n % 2 != 0 {
/// return Err(ValidationError::custom("number must be even"));
/// }
/// }
/// Ok(())
/// }
///
/// fn is_valid(&self, instance: &Value) -> bool {
/// instance.as_u64().map_or(true, |n| n % 2 == 0)
/// }
/// }
/// ```
pub trait Keyword: Send + Sync {
/// Validate an instance against this custom keyword.
///
/// Use [`ValidationError::custom`] for error messages. Path information
/// (`instance_path` and `schema_path`) is filled in automatically.
///
/// # Errors
///
/// Returns a [`ValidationError`] if the instance is invalid.
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>>;
/// Check validity without collecting error details.
fn is_valid(&self, instance: &Value) -> bool;
}
pub(crate) trait KeywordFactory: Send + Sync {
fn init<'a>(
&self,
parent: &'a Map<String, Value>,
schema: &'a Value,
schema_path: Location,
keyword: &str,
) -> Result<Box<dyn Keyword>, ValidationError<'a>>;
}
impl<F> KeywordFactory for F
where
F: for<'a> Fn(
&'a Map<String, Value>,
&'a Value,
Location,
) -> Result<Box<dyn Keyword>, ValidationError<'a>>
+ Send
+ Sync,
{
fn init<'a>(
&self,
parent: &'a Map<String, Value>,
schema: &'a Value,
schema_path: Location,
keyword: &str,
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
self(parent, schema, schema_path.clone())
.map_err(|err| err.with_schema_context(schema, schema_path, keyword))
}
}