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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::{
error::ErrorIterator,
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)
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
instance_path: &LazyLocation,
_tracker: Option<&RefTracker>,
_ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
let errors: Vec<_> = self
.inner
.iter_errors(instance)
.map(|err| err.with_context(instance, instance_path, &self.location, &self.keyword))
.collect();
ErrorIterator::from_iterator(errors.into_iter())
}
}
/// 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;
/// Validate an instance, yielding every error at once.
///
/// Override this to report multiple problems from a single keyword. The
/// default yields at most one error, from [`validate`](Keyword::validate).
fn iter_errors<'i>(
&self,
instance: &'i Value,
) -> Box<dyn Iterator<Item = ValidationError<'i>> + 'i> {
Box::new(self.validate(instance).err().into_iter())
}
}
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))
}
}