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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! `additionalProperties` — validates properties not covered by `properties` or `patternProperties`.
use alloc::boxed::Box;
use alloc::collections::BTreeSet;
use alloc::string::String;
use alloc::vec::Vec;
use regex::Regex;
use serde_json::Value;
use crate::error::{ErrorIterator, ValidationError, ValidationErrorBuilder, ValidationErrorKind};
use crate::node::SchemaNode;
use crate::paths::{LazyLocation, Location};
use super::{Validate, ValidationContext};
/// How additional properties should be validated.
pub enum AdditionalSchema {
/// `additionalProperties: false` — reject all additional properties.
False,
/// `additionalProperties: { ... }` — validate against a sub-schema.
Schema(SchemaNode),
}
/// Validates additional properties against a schema or rejects them.
pub struct AdditionalPropertiesValidator {
schema: Option<AdditionalSchema>,
/// Property names defined by `properties` keyword.
defined_properties: BTreeSet<String>,
/// Regex patterns from `patternProperties` keyword.
pattern_regexes: Vec<(Regex, String)>,
}
impl AdditionalPropertiesValidator {
/// Create a new validator.
///
/// `schema`: None means additionalProperties is absent (allow all).
/// Some(False) means reject all additional props.
/// Some(Schema) means validate against the schema.
#[must_use]
pub fn new(
schema: Option<AdditionalSchema>,
defined_properties: BTreeSet<String>,
pattern_regexes: Vec<(Regex, String)>,
) -> Self {
Self {
schema,
defined_properties,
pattern_regexes,
}
}
fn is_additional_property(&self, name: &str) -> bool {
if self.defined_properties.contains(name) {
return false;
}
for (regex, _) in &self.pattern_regexes {
if regex.is_match(name) {
return false;
}
}
true
}
}
impl Validate for AdditionalPropertiesValidator {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
let Some(ref schema) = self.schema else {
return true;
};
if let Value::Object(obj) = instance {
for name in obj.keys() {
// Skip properties already evaluated by allOf/$ref/other keywords.
if !ctx.is_property_evaluated(name) && self.is_additional_property(name) {
let value = &obj[name];
match schema {
AdditionalSchema::False => {
ctx.mark_property_evaluated(name);
return false;
}
AdditionalSchema::Schema(s) => {
if !s.is_valid(value, ctx) {
return false;
}
ctx.mark_property_evaluated(name);
}
}
}
}
}
true
}
fn validate(
&self,
instance: &Value,
instance_path: &LazyLocation<'_>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError> {
let Some(ref schema) = self.schema else {
return Ok(());
};
if let Value::Object(obj) = instance {
for name in obj.keys() {
if !ctx.is_property_evaluated(name) && self.is_additional_property(name) {
let value = &obj[name];
match schema {
AdditionalSchema::False => {
ctx.mark_property_evaluated(name);
return Err(ValidationErrorBuilder::new(
instance_path.push_property(name).materialize(),
Location::new(),
)
.build(
ValidationErrorKind::AdditionalProperties {
unexpected: alloc::vec![name.clone()],
},
));
}
AdditionalSchema::Schema(s) => {
let child_path = instance_path.push_property(name);
s.validate(value, &child_path, ctx)?;
ctx.mark_property_evaluated(name);
}
}
}
}
}
Ok(())
}
fn iter_errors(
&self,
instance: &Value,
instance_path: &LazyLocation<'_>,
ctx: &mut ValidationContext,
) -> ErrorIterator {
let mut errors: Vec<ValidationError> = Vec::new();
let Some(ref schema) = self.schema else {
return Box::new(errors.into_iter());
};
if let Value::Object(obj) = instance {
for name in obj.keys() {
if !ctx.is_property_evaluated(name) && self.is_additional_property(name) {
let value = &obj[name];
match schema {
AdditionalSchema::False => {
ctx.mark_property_evaluated(name);
errors.push(
ValidationErrorBuilder::new(
instance_path.push_property(name).materialize(),
Location::new(),
)
.build(
ValidationErrorKind::AdditionalProperties {
unexpected: alloc::vec![name.clone()],
},
),
);
}
AdditionalSchema::Schema(s) => {
let child_path = instance_path.push_property(name);
for e in s.iter_errors(value, &child_path, ctx) {
errors.push(e);
}
ctx.mark_property_evaluated(name);
}
}
}
}
}
Box::new(errors.into_iter())
}
}