corvus_json_schema 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +2 -12
- data/README.md +10 -1
- data/THIRD-PARTY-NOTICES.txt +95 -0
- data/VERSIONHISTORY.md +24 -0
- data/ext/corvus_json_schema/Cargo.toml +2 -2
- data/ext/corvus_json_schema/clippy.toml +3 -4
- data/ext/corvus_json_schema/src/lib.rs +42 -19
- data/ext/corvus_json_schema/vendor/corvus-json-schema/Cargo.toml +9 -4
- data/ext/corvus_json_schema/vendor/corvus-json-schema/README.md +87 -4
- data/ext/corvus_json_schema/vendor/corvus-json-schema/THIRD-PARTY-NOTICES.txt +93 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/compiler.rs +88 -96
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/document.rs +2 -2
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/charset.rs +271 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/compile.rs +941 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/dfa.rs +366 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/fold.rs +138 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/mod.rs +329 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/parse.rs +1128 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/re2.rs +129 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tables_gen.rs +6685 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tests.rs +1672 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/ucd.rs +309 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/units.rs +165 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/vm.rs +981 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan/fused.rs +13 -9
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan.rs +12 -11
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval.rs +105 -73
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/formats.rs +95 -1
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/lib.rs +165 -30
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/loader.rs +19 -21
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/numbers.rs +1 -1
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/options.rs +199 -4
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/pattern.rs +758 -79
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/uri.rs +4 -4
- data/lib/corvus_json_schema/version.rb +1 -1
- data/lib/corvus_json_schema.rb +13 -3
- metadata +16 -2
|
@@ -18,10 +18,52 @@
|
|
|
18
18
|
//! assert!(validator.is_valid(&json!({ "id": 3 })));
|
|
19
19
|
//! assert!(!validator.is_valid(&json!({ "id": 0 })));
|
|
20
20
|
//! ```
|
|
21
|
+
//!
|
|
22
|
+
//! # Regular expressions and their limits
|
|
23
|
+
//!
|
|
24
|
+
//! `pattern`, `patternProperties` and the `regex` format follow ECMA-262. Simple patterns are matched by matchers of
|
|
25
|
+
//! the crate's own and by the `regex` crate, and everything else (lookarounds, backreferences, modifier groups) by
|
|
26
|
+
//! the crate's own ECMA-262 engine, as each pattern is written.
|
|
27
|
+
//!
|
|
28
|
+
//! A schema and an instance may both come from outside, so a pattern is bounded in what it costs. One match against
|
|
29
|
+
//! one string may take [`CompileOptions::pattern_match_limit`], one second unless the options say otherwise. A match
|
|
30
|
+
//! that reaches it is abandoned and the validation has no answer: [`Validator::try_validate`] and the other `try_`
|
|
31
|
+
//! functions return [`SchemaEvaluationError::PatternMatchLimit`]. A pattern longer than 1,048,576 bytes, nested more
|
|
32
|
+
//! than 1,000 groups deep or of more than about 16,000 terms does not compile, and
|
|
33
|
+
//! [`SchemaCompilationError::kind`] is then [`SchemaCompilationErrorKind::PatternTooLarge`], which is not the kind
|
|
34
|
+
//! of a pattern that is not valid.
|
|
35
|
+
//!
|
|
36
|
+
//! ```
|
|
37
|
+
//! use std::time::Duration;
|
|
38
|
+
//!
|
|
39
|
+
//! use corvus_json_schema::{CompileOptions, SchemaCompilationErrorKind, SchemaEvaluationError, compile, compile_with};
|
|
40
|
+
//! use serde_json::json;
|
|
41
|
+
//!
|
|
42
|
+
//! // A group read back inside a repetition: on this string the match would run for longer than anyone waits.
|
|
43
|
+
//! let options = CompileOptions { pattern_match_limit: Some(Duration::from_millis(50)), ..CompileOptions::default() };
|
|
44
|
+
//! let validator = compile_with(&json!({ "pattern": r"^(?:(a+)\1*)+$" }), &options).unwrap();
|
|
45
|
+
//! let instance = json!("a".repeat(60) + "b");
|
|
46
|
+
//! match validator.try_validate(&instance) {
|
|
47
|
+
//! Err(SchemaEvaluationError::PatternMatchLimit(e)) => {
|
|
48
|
+
//! assert_eq!(e.pattern(), r"^(?:(a+)\1*)+$");
|
|
49
|
+
//! assert_eq!(e.limit(), Duration::from_millis(50));
|
|
50
|
+
//! }
|
|
51
|
+
//! other => panic!("{other:?}"),
|
|
52
|
+
//! }
|
|
53
|
+
//! // is_valid has no way to say so, and answers false.
|
|
54
|
+
//! assert!(!validator.is_valid(&instance));
|
|
55
|
+
//!
|
|
56
|
+
//! // A pattern over a limit of size is not a pattern that is not valid.
|
|
57
|
+
//! let error = compile(&json!({ "pattern": "a".repeat(20000) })).unwrap_err();
|
|
58
|
+
//! assert!(matches!(error.kind(), SchemaCompilationErrorKind::PatternTooLarge(large) if large.limit() == "size"));
|
|
59
|
+
//! let error = compile(&json!({ "pattern": "a**" })).unwrap_err();
|
|
60
|
+
//! assert_eq!(error.kind(), &SchemaCompilationErrorKind::InvalidPattern);
|
|
61
|
+
//! ```
|
|
21
62
|
|
|
22
63
|
mod compiler;
|
|
23
64
|
mod dialect;
|
|
24
65
|
mod document;
|
|
66
|
+
mod ecmaregex;
|
|
25
67
|
mod eval;
|
|
26
68
|
mod formats;
|
|
27
69
|
mod instance;
|
|
@@ -43,7 +85,9 @@ pub use dialect::Dialect;
|
|
|
43
85
|
pub use document::{JsonDocument, JsonDocumentArray, JsonDocumentObject, JsonDocumentValue, JsonParseError};
|
|
44
86
|
pub use instance::{ArrayView, Instance, Kind, ObjectView, View};
|
|
45
87
|
pub use options::{
|
|
46
|
-
CompileOptions, DocumentResolver, FormatValidator,
|
|
88
|
+
CompileOptions, DEFAULT_PATTERN_MATCH_LIMIT, DocumentResolver, FormatValidator, PatternMatchLimitError,
|
|
89
|
+
PatternTooLargeError, SchemaCompilationError, SchemaCompilationErrorKind, SchemaEvaluationDepthError,
|
|
90
|
+
SchemaEvaluationError,
|
|
47
91
|
};
|
|
48
92
|
pub use results::{
|
|
49
93
|
Annotation, JsonSchemaResultsCollector, ResultsLevel, SchemaResult, collect_annotations, encode_pointer_segment,
|
|
@@ -53,12 +97,16 @@ pub use results::{
|
|
|
53
97
|
/// The version of this crate.
|
|
54
98
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
55
99
|
|
|
56
|
-
/// Why JSON text could not be validated.
|
|
100
|
+
/// Why JSON text could not be validated, as [`Validator::validate_json`] and [`Validator::evaluate_json`] report it.
|
|
101
|
+
/// [`Validator::try_validate_json`] and [`Validator::try_evaluate_json`] report a [`SchemaEvaluationError`], which
|
|
102
|
+
/// also tells a match of a regular expression that reached its limit from the maximum depth.
|
|
57
103
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
58
104
|
pub enum JsonValidationError {
|
|
59
105
|
/// The text is not valid JSON.
|
|
60
106
|
InvalidJson(JsonParseError),
|
|
61
|
-
///
|
|
107
|
+
/// The validation was abandoned at a limit: it recursed in place beyond the maximum depth, or a match of a
|
|
108
|
+
/// regular expression reached its limit. The name is from when the depth was the one limit, and the message
|
|
109
|
+
/// names both (see [`without_the_limit`] for why the two are one here).
|
|
62
110
|
DepthExceeded(SchemaEvaluationDepthError),
|
|
63
111
|
}
|
|
64
112
|
|
|
@@ -80,6 +128,24 @@ impl std::error::Error for JsonValidationError {
|
|
|
80
128
|
}
|
|
81
129
|
}
|
|
82
130
|
|
|
131
|
+
/// What the functions whose error type was fixed before the limit of a match existed report for an evaluation that
|
|
132
|
+
/// was abandoned. Their type, [`SchemaEvaluationDepthError`], has one value, so a match that reached its limit is
|
|
133
|
+
/// reported as that value too: an error, and so not an answer, though not one that says which limit it was. The
|
|
134
|
+
/// `try_` functions say which. This is the one place that decides it.
|
|
135
|
+
fn without_the_limit(e: SchemaEvaluationError) -> SchemaEvaluationDepthError {
|
|
136
|
+
match e {
|
|
137
|
+
SchemaEvaluationError::DepthExceeded(e) => e,
|
|
138
|
+
_ => SchemaEvaluationDepthError,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn json_error(e: SchemaEvaluationError) -> JsonValidationError {
|
|
143
|
+
match e {
|
|
144
|
+
SchemaEvaluationError::InvalidJson(e) => JsonValidationError::InvalidJson(e),
|
|
145
|
+
other => JsonValidationError::DepthExceeded(without_the_limit(other)),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
83
149
|
/// A compiled schema. Cheap to clone and safe to share between threads.
|
|
84
150
|
#[derive(Clone)]
|
|
85
151
|
pub struct Validator {
|
|
@@ -101,18 +167,21 @@ pub fn compile(schema: &Value) -> Result<Validator, SchemaCompilationError> {
|
|
|
101
167
|
/// Compiles a schema.
|
|
102
168
|
pub fn compile_with(schema: &Value, options: &CompileOptions) -> Result<Validator, SchemaCompilationError> {
|
|
103
169
|
let compiled = compiler::SchemaCompiler::compile(schema.clone(), options)?;
|
|
104
|
-
|
|
170
|
+
Validator::from_compiled(compiled, options)
|
|
105
171
|
}
|
|
106
172
|
|
|
107
173
|
/// Compiles the schema document at `uri`, fetched through `options.resolve_document` (or one of the standard
|
|
108
174
|
/// metaschemas).
|
|
109
175
|
pub fn compile_from_uri(uri: &str, options: &CompileOptions) -> Result<Validator, SchemaCompilationError> {
|
|
110
176
|
let compiled = compiler::SchemaCompiler::compile_from_uri(uri, options)?;
|
|
111
|
-
|
|
177
|
+
Validator::from_compiled(compiled, options)
|
|
112
178
|
}
|
|
113
179
|
|
|
114
180
|
impl Validator {
|
|
115
|
-
fn from_compiled(
|
|
181
|
+
fn from_compiled(
|
|
182
|
+
c: compiler::CompiledSchema,
|
|
183
|
+
options: &CompileOptions,
|
|
184
|
+
) -> Result<Validator, SchemaCompilationError> {
|
|
116
185
|
let program = eval::Program::new(
|
|
117
186
|
c.nodes,
|
|
118
187
|
c.root,
|
|
@@ -122,68 +191,134 @@ impl Validator {
|
|
|
122
191
|
c.documents,
|
|
123
192
|
c.annotation_sources,
|
|
124
193
|
options.assert_format.is_some(),
|
|
194
|
+
pattern::PatternLimit::new(options.pattern_match_limit),
|
|
125
195
|
);
|
|
126
|
-
|
|
196
|
+
// Compiling matches each pattern of patternProperties against the property names the schema declares.
|
|
197
|
+
let failure = program.compile_budget.lock().unwrap_or_else(|e| e.into_inner()).take_failure();
|
|
198
|
+
if let Some(e) = failure {
|
|
199
|
+
return Err(SchemaCompilationError::of_kind(
|
|
200
|
+
e.to_string(),
|
|
201
|
+
SchemaCompilationErrorKind::PatternMatchLimit(e),
|
|
202
|
+
));
|
|
203
|
+
}
|
|
204
|
+
Ok(Validator { program: Arc::new(program) })
|
|
127
205
|
}
|
|
128
206
|
|
|
129
|
-
/// Whether the instance is valid.
|
|
130
|
-
///
|
|
207
|
+
/// Whether the instance is valid. It has no way to report an error, so it fails closed: it reports false both
|
|
208
|
+
/// for a schema that recursed in place beyond the maximum depth and for a match of a regular expression that
|
|
209
|
+
/// reached its limit (see [`CompileOptions::pattern_match_limit`]). A limit is never reported as valid. In
|
|
210
|
+
/// neither case is the instance known to be not valid. Use [`Validator::try_validate`] to tell them from an
|
|
211
|
+
/// instance that is not valid.
|
|
131
212
|
#[inline]
|
|
132
213
|
pub fn is_valid(&self, instance: &Value) -> bool {
|
|
133
214
|
eval::Evaluator::new(&self.program, None).validate(instance)
|
|
134
215
|
}
|
|
135
216
|
|
|
136
|
-
/// Whether the instance is valid, or
|
|
137
|
-
|
|
217
|
+
/// Whether the instance is valid, or why the evaluation gave no answer: it recursed in place beyond the maximum
|
|
218
|
+
/// depth, or a match of a regular expression reached its limit.
|
|
219
|
+
pub fn try_validate(&self, instance: &Value) -> Result<bool, SchemaEvaluationError> {
|
|
220
|
+
self.try_validate_instance(instance)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// [`Validator::try_validate`] for any [`Instance`]: a value read in place, such as a host language's own
|
|
224
|
+
/// objects, with no conversion to `serde_json::Value`.
|
|
225
|
+
pub fn try_validate_instance<'a, I: Instance<'a>>(&self, instance: I) -> Result<bool, SchemaEvaluationError> {
|
|
138
226
|
let mut e = eval::Evaluator::new(&self.program, None);
|
|
139
227
|
let ok = e.validate(instance);
|
|
140
|
-
|
|
228
|
+
e.outcome(ok)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// Whether the JSON text is valid, or why there is no answer. The text is parsed into the thread's reused
|
|
232
|
+
/// buffers and evaluated in place: in the steady state nothing is allocated. Use [`JsonDocument`] to validate
|
|
233
|
+
/// the same text more than once.
|
|
234
|
+
pub fn try_validate_json(&self, json: &str) -> Result<bool, SchemaEvaluationError> {
|
|
235
|
+
JsonDocument::with_document(json, |d| self.try_validate_instance(d.root()))
|
|
236
|
+
.map_err(SchemaEvaluationError::InvalidJson)?
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Evaluates the instance exhaustively, reporting to the collector. Returns whether it is valid, or why the
|
|
240
|
+
/// evaluation gave no answer. With an error the collector's results are not complete.
|
|
241
|
+
pub fn try_evaluate(
|
|
242
|
+
&self,
|
|
243
|
+
instance: &Value,
|
|
244
|
+
collector: &mut JsonSchemaResultsCollector,
|
|
245
|
+
) -> Result<bool, SchemaEvaluationError> {
|
|
246
|
+
self.try_evaluate_instance(instance, collector)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// [`Validator::try_evaluate`] for any [`Instance`].
|
|
250
|
+
pub fn try_evaluate_instance<'a, I: Instance<'a>>(
|
|
251
|
+
&self,
|
|
252
|
+
instance: I,
|
|
253
|
+
collector: &mut JsonSchemaResultsCollector,
|
|
254
|
+
) -> Result<bool, SchemaEvaluationError> {
|
|
255
|
+
let mut e = eval::Evaluator::new(&self.program, Some(collector));
|
|
256
|
+
let ok = e.evaluate(instance);
|
|
257
|
+
e.outcome(ok)
|
|
141
258
|
}
|
|
142
259
|
|
|
143
|
-
///
|
|
260
|
+
/// [`Validator::try_evaluate`] for JSON text, parsed as [`Validator::try_validate_json`] parses it.
|
|
261
|
+
pub fn try_evaluate_json(
|
|
262
|
+
&self,
|
|
263
|
+
json: &str,
|
|
264
|
+
collector: &mut JsonSchemaResultsCollector,
|
|
265
|
+
) -> Result<bool, SchemaEvaluationError> {
|
|
266
|
+
JsonDocument::with_document(json, |d| self.try_evaluate_instance(d.root(), collector))
|
|
267
|
+
.map_err(SchemaEvaluationError::InvalidJson)?
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Whether the instance is valid, or an error if the validation was abandoned at a limit. The error type has
|
|
271
|
+
/// one value, whose message names both limits, so it does not say whether the evaluation recursed in place
|
|
272
|
+
/// beyond the maximum depth or a match of a regular expression reached its limit: [`Validator::try_validate`]
|
|
273
|
+
/// does.
|
|
274
|
+
pub fn validate(&self, instance: &Value) -> Result<bool, SchemaEvaluationDepthError> {
|
|
275
|
+
self.try_validate(instance).map_err(without_the_limit)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// Evaluates the instance exhaustively, reporting to the collector. Returns whether it is valid. The error is
|
|
279
|
+
/// that of [`Validator::validate`], for a validation abandoned at either limit: [`Validator::try_evaluate`]
|
|
280
|
+
/// says which.
|
|
144
281
|
pub fn evaluate(
|
|
145
282
|
&self,
|
|
146
283
|
instance: &Value,
|
|
147
284
|
collector: &mut JsonSchemaResultsCollector,
|
|
148
285
|
) -> Result<bool, SchemaEvaluationDepthError> {
|
|
149
|
-
self.
|
|
286
|
+
self.try_evaluate(instance, collector).map_err(without_the_limit)
|
|
150
287
|
}
|
|
151
288
|
|
|
152
289
|
/// [`Validator::validate`] for any [`Instance`]: a value read in place, such as a host language's own objects,
|
|
153
|
-
/// with no conversion to `serde_json::Value`.
|
|
290
|
+
/// with no conversion to `serde_json::Value`. The error is that of [`Validator::validate`], for a validation
|
|
291
|
+
/// abandoned at either limit: [`Validator::try_validate_instance`] says which.
|
|
154
292
|
pub fn validate_instance<'a, I: Instance<'a>>(&self, instance: I) -> Result<bool, SchemaEvaluationDepthError> {
|
|
155
|
-
|
|
156
|
-
let ok = e.validate(instance);
|
|
157
|
-
if e.depth_exceeded { Err(SchemaEvaluationDepthError) } else { Ok(ok) }
|
|
293
|
+
self.try_validate_instance(instance).map_err(without_the_limit)
|
|
158
294
|
}
|
|
159
295
|
|
|
160
296
|
/// Whether the JSON text is valid. The text is parsed into the thread's reused buffers and evaluated in place: in
|
|
161
|
-
/// the steady state nothing is allocated. Use [`JsonDocument`] to validate the same text more than once.
|
|
297
|
+
/// the steady state nothing is allocated. Use [`JsonDocument`] to validate the same text more than once. An
|
|
298
|
+
/// evaluation that was abandoned is reported as [`JsonValidationError::DepthExceeded`], whichever limit it
|
|
299
|
+
/// reached: [`Validator::try_validate_json`] says which.
|
|
162
300
|
pub fn validate_json(&self, json: &str) -> Result<bool, JsonValidationError> {
|
|
163
|
-
|
|
164
|
-
.map_err(JsonValidationError::InvalidJson)?
|
|
165
|
-
.map_err(JsonValidationError::DepthExceeded)
|
|
301
|
+
self.try_validate_json(json).map_err(json_error)
|
|
166
302
|
}
|
|
167
303
|
|
|
168
|
-
/// [`Validator::evaluate`] for JSON text, parsed as [`Validator::validate_json`] parses it.
|
|
304
|
+
/// [`Validator::evaluate`] for JSON text, parsed as [`Validator::validate_json`] parses it. An evaluation that
|
|
305
|
+
/// was abandoned is [`JsonValidationError::DepthExceeded`], whichever limit it reached:
|
|
306
|
+
/// [`Validator::try_evaluate_json`] says which.
|
|
169
307
|
pub fn evaluate_json(
|
|
170
308
|
&self,
|
|
171
309
|
json: &str,
|
|
172
310
|
collector: &mut JsonSchemaResultsCollector,
|
|
173
311
|
) -> Result<bool, JsonValidationError> {
|
|
174
|
-
|
|
175
|
-
.map_err(JsonValidationError::InvalidJson)?
|
|
176
|
-
.map_err(JsonValidationError::DepthExceeded)
|
|
312
|
+
self.try_evaluate_json(json, collector).map_err(json_error)
|
|
177
313
|
}
|
|
178
314
|
|
|
179
|
-
/// [`Validator::evaluate`] for any [`Instance`].
|
|
315
|
+
/// [`Validator::evaluate`] for any [`Instance`]. The error is that of [`Validator::validate`], for a validation
|
|
316
|
+
/// abandoned at either limit: [`Validator::try_evaluate_instance`] says which.
|
|
180
317
|
pub fn evaluate_instance<'a, I: Instance<'a>>(
|
|
181
318
|
&self,
|
|
182
319
|
instance: I,
|
|
183
320
|
collector: &mut JsonSchemaResultsCollector,
|
|
184
321
|
) -> Result<bool, SchemaEvaluationDepthError> {
|
|
185
|
-
|
|
186
|
-
let ok = e.evaluate(instance);
|
|
187
|
-
if e.depth_exceeded { Err(SchemaEvaluationDepthError) } else { Ok(ok) }
|
|
322
|
+
self.try_evaluate_instance(instance, collector).map_err(without_the_limit)
|
|
188
323
|
}
|
|
189
324
|
}
|
|
@@ -231,11 +231,11 @@ impl<'o> SchemaLoader<'o> {
|
|
|
231
231
|
if self.documents_by_uri.contains_key(absolute_uri) {
|
|
232
232
|
return Ok(true);
|
|
233
233
|
}
|
|
234
|
-
if let Some(resolver) = &self.options.resolve_document
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
if let Some(resolver) = &self.options.resolve_document
|
|
235
|
+
&& let Some(doc) = resolver(absolute_uri)
|
|
236
|
+
{
|
|
237
|
+
self.add_document(absolute_uri.to_string(), doc)?;
|
|
238
|
+
return Ok(true);
|
|
239
239
|
}
|
|
240
240
|
if let Some(text) = metaschema(absolute_uri) {
|
|
241
241
|
let doc: Value = serde_json::from_str(text).expect("embedded metaschemas are valid JSON");
|
|
@@ -304,10 +304,8 @@ impl<'o> SchemaLoader<'o> {
|
|
|
304
304
|
let r = &self.resources[resource as usize];
|
|
305
305
|
(r.dialect, r.vocabularies)
|
|
306
306
|
};
|
|
307
|
-
if !is_resource_root {
|
|
308
|
-
|
|
309
|
-
(dialect, vocabularies) = self.get_dialect_info(s)?;
|
|
310
|
-
}
|
|
307
|
+
if !is_resource_root && let Some(Value::String(s)) = map.get("$schema") {
|
|
308
|
+
(dialect, vocabularies) = self.get_dialect_info(s)?;
|
|
311
309
|
}
|
|
312
310
|
|
|
313
311
|
let legacy_ref_overrides_siblings = dialect.is_legacy() && matches!(map.get("$ref"), Some(Value::String(_)));
|
|
@@ -335,19 +333,19 @@ impl<'o> SchemaLoader<'o> {
|
|
|
335
333
|
}
|
|
336
334
|
}
|
|
337
335
|
}
|
|
338
|
-
if dialect >= Dialect::Draft201909
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
if dialect >= Dialect::Draft201909
|
|
337
|
+
&& let Some(Value::String(a)) = map.get("$anchor")
|
|
338
|
+
{
|
|
339
|
+
self.add_anchor(resource, a, &pointer);
|
|
342
340
|
}
|
|
343
|
-
if dialect >= Dialect::Draft202012
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
341
|
+
if dialect >= Dialect::Draft202012
|
|
342
|
+
&& let Some(Value::String(name)) = map.get("$dynamicAnchor")
|
|
343
|
+
{
|
|
344
|
+
self.resources[resource as usize]
|
|
345
|
+
.dynamic_anchors
|
|
346
|
+
.entry(name.clone())
|
|
347
|
+
.or_insert_with(|| pointer.clone());
|
|
348
|
+
self.add_anchor(resource, name, &pointer);
|
|
351
349
|
}
|
|
352
350
|
if dialect == Dialect::Draft201909
|
|
353
351
|
&& is_resource_root
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
use std::collections::HashMap;
|
|
4
4
|
use std::fmt;
|
|
5
5
|
use std::sync::Arc;
|
|
6
|
+
use std::time::Duration;
|
|
6
7
|
|
|
7
8
|
use serde_json::Value;
|
|
8
9
|
|
|
@@ -36,8 +37,23 @@ pub struct CompileOptions {
|
|
|
36
37
|
pub entry_point: Option<String>,
|
|
37
38
|
/// Maximum depth of in-place recursion on a cycle before evaluation is abandoned. Defaults to 128.
|
|
38
39
|
pub max_depth: u32,
|
|
40
|
+
/// The time one match of a `pattern` or `patternProperties` regular expression against one string may take.
|
|
41
|
+
/// Defaults to [`DEFAULT_PATTERN_MATCH_LIMIT`], one second. A match that takes longer is abandoned, and the
|
|
42
|
+
/// validation it belongs to reports a [`PatternMatchLimitError`]. `None`, or a limit of zero, turns the limit
|
|
43
|
+
/// off, for a caller who trusts both the schema and the instances. Without the limit a match also has no limit
|
|
44
|
+
/// on its memory.
|
|
45
|
+
///
|
|
46
|
+
/// The limit is on each match, not on the validation. Only the backtracking matcher can take long, and it
|
|
47
|
+
/// reads the clock every 4,096 steps, so a match is abandoned within tens of microseconds of the limit. The
|
|
48
|
+
/// matchers that take time proportional to the string are not stopped: a string is given to one only when it
|
|
49
|
+
/// can answer within the limit.
|
|
50
|
+
pub pattern_match_limit: Option<Duration>,
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
/// The time one match of a `pattern` or `patternProperties` regular expression against one string may take, unless
|
|
54
|
+
/// [`CompileOptions::pattern_match_limit`] says otherwise.
|
|
55
|
+
pub const DEFAULT_PATTERN_MATCH_LIMIT: Duration = Duration::from_secs(1);
|
|
56
|
+
|
|
41
57
|
impl Default for CompileOptions {
|
|
42
58
|
fn default() -> Self {
|
|
43
59
|
CompileOptions {
|
|
@@ -50,6 +66,7 @@ impl Default for CompileOptions {
|
|
|
50
66
|
base_uri: None,
|
|
51
67
|
entry_point: None,
|
|
52
68
|
max_depth: 128,
|
|
69
|
+
pattern_match_limit: Some(DEFAULT_PATTERN_MATCH_LIMIT),
|
|
53
70
|
}
|
|
54
71
|
}
|
|
55
72
|
}
|
|
@@ -66,25 +83,53 @@ impl fmt::Debug for CompileOptions {
|
|
|
66
83
|
.field("base_uri", &self.base_uri)
|
|
67
84
|
.field("entry_point", &self.entry_point)
|
|
68
85
|
.field("max_depth", &self.max_depth)
|
|
86
|
+
.field("pattern_match_limit", &self.pattern_match_limit)
|
|
69
87
|
.finish()
|
|
70
88
|
}
|
|
71
89
|
}
|
|
72
90
|
|
|
73
|
-
/// A schema could not be compiled (an unresolvable reference, an invalid pattern).
|
|
91
|
+
/// A schema could not be compiled (an unresolvable reference, an invalid pattern, a pattern over a limit of size).
|
|
92
|
+
/// [`SchemaCompilationError::kind`] tells the errors of a regular expression apart.
|
|
74
93
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
75
94
|
pub struct SchemaCompilationError {
|
|
76
95
|
message: String,
|
|
96
|
+
kind: SchemaCompilationErrorKind,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// What kind of error a [`SchemaCompilationError`] is.
|
|
100
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
101
|
+
#[non_exhaustive]
|
|
102
|
+
pub enum SchemaCompilationErrorKind {
|
|
103
|
+
/// Any error that is not about a regular expression: an unresolvable reference, for one.
|
|
104
|
+
Other,
|
|
105
|
+
/// A `pattern`, or a key of `patternProperties`, is not a valid ECMA-262 regular expression.
|
|
106
|
+
InvalidPattern,
|
|
107
|
+
/// A regular expression is over a limit of size. It is not the error of one that is not valid: the pattern was
|
|
108
|
+
/// not read to its end, so nothing is known of whether it is valid.
|
|
109
|
+
PatternTooLarge(PatternTooLargeError),
|
|
110
|
+
/// Matching a regular expression against a property name the schema declares, which is done once when the
|
|
111
|
+
/// schema is compiled, reached the limit of a match (see [`CompileOptions::pattern_match_limit`]).
|
|
112
|
+
PatternMatchLimit(PatternMatchLimitError),
|
|
77
113
|
}
|
|
78
114
|
|
|
79
115
|
impl SchemaCompilationError {
|
|
80
116
|
pub(crate) fn new(message: impl Into<String>) -> Self {
|
|
81
|
-
SchemaCompilationError { message: message.into() }
|
|
117
|
+
SchemaCompilationError { message: message.into(), kind: SchemaCompilationErrorKind::Other }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
pub(crate) fn of_kind(message: impl Into<String>, kind: SchemaCompilationErrorKind) -> Self {
|
|
121
|
+
SchemaCompilationError { message: message.into(), kind }
|
|
82
122
|
}
|
|
83
123
|
|
|
84
124
|
/// The reason compilation failed.
|
|
85
125
|
pub fn message(&self) -> &str {
|
|
86
126
|
&self.message
|
|
87
127
|
}
|
|
128
|
+
|
|
129
|
+
/// The kind of error, which tells a regular expression that is too large from one that is not valid.
|
|
130
|
+
pub fn kind(&self) -> &SchemaCompilationErrorKind {
|
|
131
|
+
&self.kind
|
|
132
|
+
}
|
|
88
133
|
}
|
|
89
134
|
|
|
90
135
|
impl fmt::Display for SchemaCompilationError {
|
|
@@ -95,14 +140,164 @@ impl fmt::Display for SchemaCompilationError {
|
|
|
95
140
|
|
|
96
141
|
impl std::error::Error for SchemaCompilationError {}
|
|
97
142
|
|
|
98
|
-
///
|
|
143
|
+
/// A regular expression of a schema is over a limit of size, so the schema was not compiled.
|
|
144
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
145
|
+
pub struct PatternTooLargeError {
|
|
146
|
+
pattern: String,
|
|
147
|
+
keyword: &'static str,
|
|
148
|
+
limit: &'static str,
|
|
149
|
+
value: usize,
|
|
150
|
+
reason: String,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
impl PatternTooLargeError {
|
|
154
|
+
pub(crate) fn new(pattern: &str, keyword: &'static str, limit: &'static str, value: usize, reason: String) -> Self {
|
|
155
|
+
PatternTooLargeError { pattern: pattern.to_string(), keyword, limit, value, reason }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// The regular expression.
|
|
159
|
+
pub fn pattern(&self) -> &str {
|
|
160
|
+
&self.pattern
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// The keyword that holds it, `pattern` or `patternProperties`.
|
|
164
|
+
pub fn keyword(&self) -> &str {
|
|
165
|
+
self.keyword
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// The name of the limit: `length` (the bytes of the pattern, 1,048,576 at most), `nesting` (the depth of its
|
|
169
|
+
/// groups, 1,000 at most) or `size` (the bytes counted for it as it is read, 1,048,576 at most).
|
|
170
|
+
pub fn limit(&self) -> &str {
|
|
171
|
+
self.limit
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// The value of that limit.
|
|
175
|
+
pub fn value(&self) -> usize {
|
|
176
|
+
self.value
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
impl fmt::Display for PatternTooLargeError {
|
|
181
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
182
|
+
write!(
|
|
183
|
+
f,
|
|
184
|
+
"The regular expression {} in {} is too large to read as an ECMA-262 regular expression: {}.",
|
|
185
|
+
crate::pattern::shown_pattern(&self.pattern),
|
|
186
|
+
self.keyword,
|
|
187
|
+
self.reason
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
impl std::error::Error for PatternTooLargeError {}
|
|
193
|
+
|
|
194
|
+
/// A validation was abandoned because one match of a `pattern` or `patternProperties` regular expression reached
|
|
195
|
+
/// its limit. The instance is then neither valid nor not valid.
|
|
196
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
197
|
+
pub struct PatternMatchLimitError {
|
|
198
|
+
pattern: String,
|
|
199
|
+
limit: Duration,
|
|
200
|
+
memory: bool,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
impl PatternMatchLimitError {
|
|
204
|
+
pub(crate) fn new(pattern: &str, limit: Duration, memory: bool) -> Self {
|
|
205
|
+
PatternMatchLimitError { pattern: pattern.to_string(), limit, memory }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// The regular expression.
|
|
209
|
+
pub fn pattern(&self) -> &str {
|
|
210
|
+
&self.pattern
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// The time the match was allowed (see [`CompileOptions::pattern_match_limit`]).
|
|
214
|
+
pub fn limit(&self) -> Duration {
|
|
215
|
+
self.limit
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Whether the match was abandoned for the memory of its backtracking, not for its time: it held 1,048,576
|
|
219
|
+
/// choices, which is 24 MB.
|
|
220
|
+
pub fn is_memory(&self) -> bool {
|
|
221
|
+
self.memory
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
impl fmt::Display for PatternMatchLimitError {
|
|
226
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
227
|
+
let pattern = crate::pattern::shown_pattern(&self.pattern);
|
|
228
|
+
if self.memory {
|
|
229
|
+
write!(
|
|
230
|
+
f,
|
|
231
|
+
"A match of the regular expression {pattern} was abandoned: it needed more than the limit of \
|
|
232
|
+
1,048,576 backtracking entries."
|
|
233
|
+
)
|
|
234
|
+
} else {
|
|
235
|
+
write!(
|
|
236
|
+
f,
|
|
237
|
+
"A match of the regular expression {pattern} was abandoned: it took longer than the limit of {:?}.",
|
|
238
|
+
self.limit
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
impl std::error::Error for PatternMatchLimitError {}
|
|
245
|
+
|
|
246
|
+
/// The validation was abandoned at a limit, and gave no answer. It is the error of the functions whose error type
|
|
247
|
+
/// was there before the limit of a match was (`validate`, `validate_instance`, `evaluate`, `evaluate_instance`, and
|
|
248
|
+
/// inside [`crate::JsonValidationError::DepthExceeded`] `validate_json` and `evaluate_json`).
|
|
249
|
+
///
|
|
250
|
+
/// The name is from when there was one limit: an evaluation that recursed in place beyond `max_depth` (a schema
|
|
251
|
+
/// that loops without consuming the instance). A match of a regular expression that reached its limit (see
|
|
252
|
+
/// [`CompileOptions::pattern_match_limit`]) is reported as this error too. The type has one value, so it does not
|
|
253
|
+
/// say which limit it was, and its message names both. The `try_` functions (`Validator::try_validate` and the
|
|
254
|
+
/// rest) return a [`SchemaEvaluationError`], which says which.
|
|
99
255
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
100
256
|
pub struct SchemaEvaluationDepthError;
|
|
101
257
|
|
|
102
258
|
impl fmt::Display for SchemaEvaluationDepthError {
|
|
103
259
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
104
|
-
f.write_str(
|
|
260
|
+
f.write_str(
|
|
261
|
+
"The validation was abandoned at a limit: the schema recursed in place beyond the maximum depth, or a \
|
|
262
|
+
match of a regular expression reached its limit. The try_ functions say which.",
|
|
263
|
+
)
|
|
105
264
|
}
|
|
106
265
|
}
|
|
107
266
|
|
|
108
267
|
impl std::error::Error for SchemaEvaluationDepthError {}
|
|
268
|
+
|
|
269
|
+
/// Why an evaluation gave no answer. The instance is then neither valid nor not valid.
|
|
270
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
271
|
+
#[non_exhaustive]
|
|
272
|
+
pub enum SchemaEvaluationError {
|
|
273
|
+
/// The text is not valid JSON (the functions that take JSON text).
|
|
274
|
+
InvalidJson(crate::JsonParseError),
|
|
275
|
+
/// Evaluation recursed in place beyond the maximum depth.
|
|
276
|
+
DepthExceeded(SchemaEvaluationDepthError),
|
|
277
|
+
/// A match of a regular expression reached its limit.
|
|
278
|
+
PatternMatchLimit(PatternMatchLimitError),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
impl fmt::Display for SchemaEvaluationError {
|
|
282
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
283
|
+
match self {
|
|
284
|
+
SchemaEvaluationError::InvalidJson(e) => write!(f, "invalid JSON: {e}"),
|
|
285
|
+
// Not the message of the value inside, which is the one error of the functions that cannot say which
|
|
286
|
+
// limit it was, and so names both.
|
|
287
|
+
SchemaEvaluationError::DepthExceeded(_) => {
|
|
288
|
+
f.write_str("The schema recursed in place beyond the maximum depth.")
|
|
289
|
+
}
|
|
290
|
+
SchemaEvaluationError::PatternMatchLimit(e) => e.fmt(f),
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
impl std::error::Error for SchemaEvaluationError {
|
|
296
|
+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
297
|
+
match self {
|
|
298
|
+
SchemaEvaluationError::InvalidJson(e) => Some(e),
|
|
299
|
+
SchemaEvaluationError::DepthExceeded(e) => Some(e),
|
|
300
|
+
SchemaEvaluationError::PatternMatchLimit(e) => Some(e),
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|