schemurai 3.0.0 → 3.0.2
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/README.md +6 -4
- data/ext/schemurai_native/Cargo.lock +55 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +1 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +74 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1087 -307
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +126 -14
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/integer_scan.rs +132 -0
- data/ext/schemurai_native/src/lib.rs +917 -238
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +147 -109
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +7 -22
- metadata +25 -2
|
@@ -2,113 +2,569 @@
|
|
|
2
2
|
//! returning to C are the output records materialized as Ruby objects.
|
|
3
3
|
use super::*;
|
|
4
4
|
use tracking::Tracked;
|
|
5
|
+
mod array;
|
|
6
|
+
mod object;
|
|
5
7
|
|
|
8
|
+
fn reference_keyword(opcode: i32) -> &'static str {
|
|
9
|
+
match opcode {
|
|
10
|
+
OP_RECURSIVE_REF => "$recursiveRef",
|
|
11
|
+
OP_DYNAMIC_REF => "$dynamicRef",
|
|
12
|
+
_ => "$ref",
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[derive(Clone)]
|
|
6
17
|
struct Error {
|
|
7
18
|
keyword: &'static str,
|
|
8
|
-
instance_path:
|
|
9
|
-
schema_path:
|
|
19
|
+
instance_path: pointer::Rendered,
|
|
20
|
+
schema_path: pointer::Rendered,
|
|
10
21
|
opcode: i32,
|
|
11
22
|
operand: Value,
|
|
12
23
|
instance: Value,
|
|
13
24
|
detail: usize,
|
|
25
|
+
message: Value,
|
|
14
26
|
}
|
|
15
|
-
#[derive(
|
|
27
|
+
#[derive(Clone)]
|
|
28
|
+
struct ErrorEntry {
|
|
29
|
+
source: (i32, Value),
|
|
30
|
+
instance: Value,
|
|
31
|
+
keyword: &'static str,
|
|
32
|
+
frame: Value,
|
|
33
|
+
}
|
|
34
|
+
#[derive(Clone, Default)]
|
|
16
35
|
pub struct Report {
|
|
17
36
|
errors: Vec<Error>,
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
annotations: bool,
|
|
38
|
+
evaluation: Option<Tracked>,
|
|
39
|
+
container_prior: Option<Tracked>,
|
|
40
|
+
entry: Option<ErrorEntry>,
|
|
41
|
+
attempts: usize,
|
|
42
|
+
collection_reset: usize,
|
|
43
|
+
reset_path_pops: bool,
|
|
44
|
+
instance_path: pointer::Path,
|
|
45
|
+
schema_path: pointer::Path,
|
|
20
46
|
// Explicit failure signal for the current evaluation, independent of the
|
|
21
47
|
// number or rendering of diagnostic records. Child evaluations isolate it.
|
|
22
48
|
failed: bool,
|
|
49
|
+
silent: bool,
|
|
50
|
+
render_messages: bool,
|
|
23
51
|
limits: budget::Limits,
|
|
24
52
|
limit_hit: (u32, usize),
|
|
25
53
|
}
|
|
26
54
|
impl Report {
|
|
55
|
+
pub(super) fn same_collection_policy(&self, other: &Self) -> bool {
|
|
56
|
+
self.silent == other.silent
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn finish_runtime<T>(
|
|
60
|
+
&mut self,
|
|
61
|
+
caches: &runtime::Caches,
|
|
62
|
+
result: Result<T, Verdict>,
|
|
63
|
+
) -> Result<T, Verdict> {
|
|
64
|
+
caches.apply_report_effects(self);
|
|
65
|
+
if matches!(result, Err(Unsupported)) {
|
|
66
|
+
caches.capture_report(self);
|
|
67
|
+
}
|
|
68
|
+
result
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub(super) fn complete_callback(&mut self, valid: bool) {
|
|
72
|
+
self.evaluation = Some(Tracked::new(if valid { Valid } else { Invalid }));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub(super) fn clear_collection(&mut self, paths_nil: bool) {
|
|
76
|
+
self.collection_reset = self.collection_reset.saturating_add(1);
|
|
77
|
+
self.reset_path_pops = paths_nil;
|
|
78
|
+
self.errors.clear();
|
|
79
|
+
self.attempts = 0;
|
|
80
|
+
self.failed = false;
|
|
81
|
+
self.silent = true;
|
|
82
|
+
self.instance_path = pointer::Path::default();
|
|
83
|
+
self.schema_path = pointer::Path::default();
|
|
84
|
+
}
|
|
85
|
+
pub(super) fn effect_since(&self, before: &Self) -> Option<Self> {
|
|
86
|
+
if self.collection_reset != before.collection_reset {
|
|
87
|
+
return Some(Self {
|
|
88
|
+
errors: self.errors.clone(),
|
|
89
|
+
attempts: self.attempts,
|
|
90
|
+
collection_reset: self.collection_reset,
|
|
91
|
+
reset_path_pops: self.reset_path_pops,
|
|
92
|
+
failed: self.failed,
|
|
93
|
+
silent: self.silent,
|
|
94
|
+
instance_path: self.instance_path.clone(),
|
|
95
|
+
schema_path: self.schema_path.clone(),
|
|
96
|
+
..Self::default()
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
let errors = self.errors.get(before.errors.len()..)?.to_vec();
|
|
100
|
+
let attempts = self.attempts.saturating_sub(before.attempts);
|
|
101
|
+
let failed = self.failed && !before.failed;
|
|
102
|
+
(!errors.is_empty() || attempts != 0 || failed).then_some(Self {
|
|
103
|
+
errors,
|
|
104
|
+
attempts,
|
|
105
|
+
failed,
|
|
106
|
+
..Self::default()
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
pub(super) fn apply_effect(&mut self, effect: Self) {
|
|
110
|
+
if effect.collection_reset != 0 {
|
|
111
|
+
self.collection_reset = effect.collection_reset;
|
|
112
|
+
self.reset_path_pops = effect.reset_path_pops;
|
|
113
|
+
self.errors = effect.errors;
|
|
114
|
+
self.attempts = effect.attempts;
|
|
115
|
+
self.failed = effect.failed;
|
|
116
|
+
self.silent = effect.silent;
|
|
117
|
+
self.instance_path = effect.instance_path;
|
|
118
|
+
self.schema_path = effect.schema_path;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
self.errors.extend(effect.errors);
|
|
122
|
+
self.attempts = self.attempts.saturating_add(effect.attempts);
|
|
123
|
+
self.failed |= effect.failed;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pub(super) fn continuation(&self, annotations: bool) -> Self {
|
|
127
|
+
let mut report = self.clone();
|
|
128
|
+
report.annotations = annotations;
|
|
129
|
+
report.evaluation = None;
|
|
130
|
+
report.container_prior = None;
|
|
131
|
+
report.entry = None;
|
|
132
|
+
report
|
|
133
|
+
}
|
|
134
|
+
pub(super) fn adopt(&mut self, mut completed: Self) -> Tracked {
|
|
135
|
+
let evaluation = completed
|
|
136
|
+
.evaluation
|
|
137
|
+
.take()
|
|
138
|
+
.expect("completed native report");
|
|
139
|
+
completed.annotations = self.annotations;
|
|
140
|
+
completed.evaluation = self.evaluation.take();
|
|
141
|
+
completed.container_prior = self.container_prior.take();
|
|
142
|
+
let instance_path = std::mem::take(&mut self.instance_path);
|
|
143
|
+
let schema_path = std::mem::take(&mut self.schema_path);
|
|
144
|
+
*self = completed;
|
|
145
|
+
self.instance_path = instance_path;
|
|
146
|
+
self.schema_path = schema_path;
|
|
147
|
+
evaluation
|
|
148
|
+
}
|
|
149
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
150
|
+
self.errors.capacity() * std::mem::size_of::<Error>()
|
|
151
|
+
+ self.instance_path.memory_usage()
|
|
152
|
+
+ self.schema_path.memory_usage()
|
|
153
|
+
+ self
|
|
154
|
+
.errors
|
|
155
|
+
.iter()
|
|
156
|
+
.map(|e| e.instance_path.memory_usage() + e.schema_path.memory_usage())
|
|
157
|
+
.sum::<usize>()
|
|
158
|
+
+ self.evaluation.as_ref().map_or(0, Tracked::memory_usage)
|
|
159
|
+
+ self
|
|
160
|
+
.container_prior
|
|
161
|
+
.as_ref()
|
|
162
|
+
.map_or(0, Tracked::memory_usage)
|
|
163
|
+
}
|
|
164
|
+
pub(super) fn mark(&self) {
|
|
165
|
+
for path in [&self.instance_path, &self.schema_path] {
|
|
166
|
+
for &(_, value) in &path.names {
|
|
167
|
+
unsafe {
|
|
168
|
+
sn_pin(value);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for error in &self.errors {
|
|
173
|
+
unsafe {
|
|
174
|
+
sn_pin(error.instance_path.value());
|
|
175
|
+
sn_pin(error.schema_path.value());
|
|
176
|
+
sn_pin(error.operand);
|
|
177
|
+
sn_pin(error.instance);
|
|
178
|
+
sn_pin(error.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if let Some(evaluation) = &self.evaluation {
|
|
182
|
+
evaluation.mark();
|
|
183
|
+
}
|
|
184
|
+
if let Some(prior) = &self.container_prior {
|
|
185
|
+
prior.mark();
|
|
186
|
+
}
|
|
187
|
+
if let Some(entry) = &self.entry {
|
|
188
|
+
unsafe {
|
|
189
|
+
sn_pin(entry.source.1);
|
|
190
|
+
sn_pin(entry.instance);
|
|
191
|
+
sn_pin(entry.frame);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
fn container_error(
|
|
196
|
+
&self,
|
|
197
|
+
engine: &Engine,
|
|
198
|
+
node: usize,
|
|
199
|
+
instance: Value,
|
|
200
|
+
keyword: &'static str,
|
|
201
|
+
frame: Value,
|
|
202
|
+
object: bool,
|
|
203
|
+
) -> Self {
|
|
204
|
+
let source = engine.nodes[node]
|
|
205
|
+
.iter()
|
|
206
|
+
.zip(&engine.error_operands[node])
|
|
207
|
+
.find_map(|(op, &source)| {
|
|
208
|
+
(if object {
|
|
209
|
+
matches!(op, Op::Object(..))
|
|
210
|
+
} else {
|
|
211
|
+
matches!(op, Op::Array(..))
|
|
212
|
+
})
|
|
213
|
+
.then_some(source)
|
|
214
|
+
})
|
|
215
|
+
.expect("container source");
|
|
216
|
+
let mut seed = self.continuation(false);
|
|
217
|
+
seed.entry = Some(ErrorEntry {
|
|
218
|
+
source,
|
|
219
|
+
instance,
|
|
220
|
+
keyword,
|
|
221
|
+
frame,
|
|
222
|
+
});
|
|
223
|
+
seed
|
|
224
|
+
}
|
|
225
|
+
pub(super) fn container_continuation(&self, annotations: bool, prior: &Tracked) -> Self {
|
|
226
|
+
let mut report = self.continuation(annotations);
|
|
227
|
+
report.container_prior = Some(prior.clone());
|
|
228
|
+
report.evaluation = Some(Tracked::new(Valid));
|
|
229
|
+
report
|
|
230
|
+
}
|
|
27
231
|
fn add(
|
|
28
232
|
&mut self,
|
|
233
|
+
caches: &runtime::Caches,
|
|
29
234
|
keyword: &'static str,
|
|
30
235
|
source: (i32, Value),
|
|
31
236
|
instance: Value,
|
|
32
237
|
detail: usize,
|
|
33
238
|
) -> Result<(), Verdict> {
|
|
34
|
-
|
|
35
|
-
|
|
239
|
+
self.add_with_frame(caches, keyword, source, instance, detail, None)
|
|
240
|
+
}
|
|
241
|
+
fn collect_header(
|
|
242
|
+
&mut self,
|
|
243
|
+
caches: &runtime::Caches,
|
|
244
|
+
header: &container::Header,
|
|
245
|
+
required: i32,
|
|
246
|
+
value: Value,
|
|
247
|
+
source: (i32, Value),
|
|
248
|
+
) -> Result<bool, Verdict> {
|
|
249
|
+
let silent = self.silent;
|
|
250
|
+
let applied = header.failures(caches, required, value, silent, |keyword, frame| {
|
|
251
|
+
self.add_frame(caches, keyword, source, value, frame)
|
|
252
|
+
})?;
|
|
253
|
+
Ok(applied && silent && self.failed)
|
|
254
|
+
}
|
|
255
|
+
fn add_frame(
|
|
256
|
+
&mut self,
|
|
257
|
+
caches: &runtime::Caches,
|
|
258
|
+
keyword: &'static str,
|
|
259
|
+
source: (i32, Value),
|
|
260
|
+
instance: Value,
|
|
261
|
+
frame: Value,
|
|
262
|
+
) -> Result<(), Verdict> {
|
|
263
|
+
self.add_with_frame(caches, keyword, source, instance, 0, Some(frame))
|
|
264
|
+
}
|
|
265
|
+
fn add_string(
|
|
266
|
+
&mut self,
|
|
267
|
+
caches: &runtime::Caches,
|
|
268
|
+
keyword: &'static str,
|
|
269
|
+
source: (i32, Value),
|
|
270
|
+
instance: Value,
|
|
271
|
+
frame: Value,
|
|
272
|
+
rescued: bool,
|
|
273
|
+
) -> Result<(), Verdict> {
|
|
274
|
+
if !self.silent && !rescued && keyword != "type" {
|
|
275
|
+
if !caches.begin_string_error(frame, self.errors.len(), keyword) {
|
|
276
|
+
self.reserve_error()?;
|
|
277
|
+
return Err(Invalid);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
caches.end_string_error();
|
|
281
|
+
}
|
|
282
|
+
let result = self.add_with_frame(
|
|
283
|
+
caches,
|
|
284
|
+
keyword,
|
|
285
|
+
source,
|
|
286
|
+
instance,
|
|
287
|
+
usize::from(rescued),
|
|
288
|
+
Some(frame),
|
|
289
|
+
);
|
|
290
|
+
if result != Err(Unsupported) {
|
|
291
|
+
caches.end_string_error();
|
|
292
|
+
}
|
|
293
|
+
result
|
|
294
|
+
}
|
|
295
|
+
fn reserve_error(&mut self) -> Result<(), Verdict> {
|
|
296
|
+
if self.limits.enabled & 1 != 0 && self.attempts >= self.limits.max_errors {
|
|
297
|
+
self.limit_hit = (1, self.attempts.saturating_add(1));
|
|
36
298
|
return Err(Unsupported);
|
|
37
299
|
}
|
|
300
|
+
self.attempts = self.attempts.saturating_add(1);
|
|
38
301
|
self.failed = true;
|
|
39
|
-
|
|
40
|
-
|
|
302
|
+
Ok(())
|
|
303
|
+
}
|
|
304
|
+
fn add_with_frame(
|
|
305
|
+
&mut self,
|
|
306
|
+
caches: &runtime::Caches,
|
|
307
|
+
keyword: &'static str,
|
|
308
|
+
source: (i32, Value),
|
|
309
|
+
instance: Value,
|
|
310
|
+
detail: usize,
|
|
311
|
+
frame: Option<Value>,
|
|
312
|
+
) -> Result<(), Verdict> {
|
|
313
|
+
caches.apply_report_effects(self);
|
|
314
|
+
if self.silent {
|
|
315
|
+
self.failed = true;
|
|
316
|
+
return Ok(());
|
|
317
|
+
}
|
|
318
|
+
self.reserve_error()?;
|
|
319
|
+
caches.frame_error(frame.is_some());
|
|
320
|
+
let instance_path = self.instance_path.render(caches, None)?;
|
|
321
|
+
caches.apply_report_effects(self);
|
|
322
|
+
let keyword_segment = (!matches!(
|
|
323
|
+
keyword,
|
|
324
|
+
"falseSchema" | "$ref" | "$recursiveRef" | "$dynamicRef"
|
|
325
|
+
))
|
|
326
|
+
.then_some(keyword);
|
|
327
|
+
let schema_path = self.schema_path.render(caches, keyword_segment)?;
|
|
328
|
+
caches.apply_report_effects(self);
|
|
329
|
+
let message = if self.render_messages || caches.dynamic_calls() {
|
|
330
|
+
if let Some(frame) = frame {
|
|
331
|
+
caches.frame_message(
|
|
332
|
+
keyword,
|
|
333
|
+
source,
|
|
334
|
+
instance,
|
|
335
|
+
frame,
|
|
336
|
+
self.errors.len(),
|
|
337
|
+
detail != 0,
|
|
338
|
+
)?
|
|
339
|
+
} else {
|
|
340
|
+
caches.error_message(keyword, source, instance, detail, self.errors.len())?
|
|
341
|
+
}
|
|
41
342
|
} else {
|
|
42
|
-
|
|
343
|
+
unsafe { sn_nil() }
|
|
43
344
|
};
|
|
345
|
+
caches.apply_report_effects(self);
|
|
44
346
|
self.errors.push(Error {
|
|
45
347
|
keyword,
|
|
46
|
-
instance_path
|
|
348
|
+
instance_path,
|
|
47
349
|
schema_path,
|
|
48
350
|
opcode: source.0,
|
|
49
351
|
operand: source.1,
|
|
50
352
|
instance,
|
|
51
353
|
detail,
|
|
354
|
+
message,
|
|
52
355
|
});
|
|
356
|
+
caches.frame_error(false);
|
|
53
357
|
Ok(())
|
|
54
358
|
}
|
|
359
|
+
fn cleanup_reset_paths<T>(
|
|
360
|
+
&mut self,
|
|
361
|
+
caches: &runtime::Caches,
|
|
362
|
+
reset: usize,
|
|
363
|
+
result: Result<T, Verdict>,
|
|
364
|
+
count: usize,
|
|
365
|
+
) -> Result<T, Verdict> {
|
|
366
|
+
if self.collection_reset != reset
|
|
367
|
+
&& self.reset_path_pops
|
|
368
|
+
&& !matches!(result, Err(Unsupported))
|
|
369
|
+
{
|
|
370
|
+
for _ in 0..count {
|
|
371
|
+
caches.path_pop()?;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
result
|
|
375
|
+
}
|
|
55
376
|
fn scoped<T>(
|
|
56
377
|
&mut self,
|
|
378
|
+
caches: &runtime::Caches,
|
|
57
379
|
segment: &str,
|
|
58
380
|
instance: Option<usize>,
|
|
59
381
|
visit: impl FnOnce(&mut Self) -> Result<T, Verdict>,
|
|
60
382
|
) -> Result<T, Verdict> {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
383
|
+
if self.silent {
|
|
384
|
+
return visit(self);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let reset = self.collection_reset;
|
|
388
|
+
let schema_len = self.schema_path.checkpoint();
|
|
389
|
+
let instance_len = self.instance_path.checkpoint();
|
|
390
|
+
self.schema_path.push_str("/");
|
|
65
391
|
self.schema_path.push_str(segment);
|
|
66
392
|
if let Some(index) = instance {
|
|
67
|
-
|
|
393
|
+
self.instance_path.index(index);
|
|
68
394
|
}
|
|
69
395
|
let result = visit(self);
|
|
396
|
+
let result = self.cleanup_reset_paths(
|
|
397
|
+
caches,
|
|
398
|
+
reset,
|
|
399
|
+
result,
|
|
400
|
+
segment.split('/').count() + usize::from(instance.is_some()),
|
|
401
|
+
);
|
|
70
402
|
self.schema_path.truncate(schema_len);
|
|
71
403
|
self.instance_path.truncate(instance_len);
|
|
72
404
|
result
|
|
73
405
|
}
|
|
74
406
|
fn property<T>(
|
|
75
407
|
&mut self,
|
|
408
|
+
caches: &runtime::Caches,
|
|
76
409
|
segment: &str,
|
|
77
410
|
key: Value,
|
|
78
411
|
schema_key: Option<Value>,
|
|
79
412
|
visit: impl FnOnce(&mut Self) -> Result<T, Verdict>,
|
|
80
413
|
) -> Result<T, Verdict> {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
414
|
+
if self.silent {
|
|
415
|
+
return visit(self);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let reset = self.collection_reset;
|
|
419
|
+
let instance_len = self.instance_path.checkpoint();
|
|
420
|
+
self.instance_path.name(key)?;
|
|
421
|
+
let result = if let Some(key) = schema_key {
|
|
422
|
+
self.schema_name(caches, segment, key, visit)
|
|
84
423
|
} else {
|
|
85
|
-
|
|
424
|
+
self.scoped(caches, segment, None, visit)
|
|
86
425
|
};
|
|
87
|
-
let
|
|
88
|
-
self.instance_path.push('/');
|
|
89
|
-
self.instance_path.push_str(&name);
|
|
90
|
-
let result = self.scoped(&segment, None, visit);
|
|
426
|
+
let result = self.cleanup_reset_paths(caches, reset, result, 1);
|
|
91
427
|
self.instance_path.truncate(instance_len);
|
|
92
428
|
result
|
|
93
429
|
}
|
|
94
|
-
}
|
|
95
430
|
|
|
96
|
-
fn
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
431
|
+
fn schema_name<T>(
|
|
432
|
+
&mut self,
|
|
433
|
+
caches: &runtime::Caches,
|
|
434
|
+
segment: &str,
|
|
435
|
+
key: Value,
|
|
436
|
+
visit: impl FnOnce(&mut Self) -> Result<T, Verdict>,
|
|
437
|
+
) -> Result<T, Verdict> {
|
|
438
|
+
if self.silent {
|
|
439
|
+
return visit(self);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
self.scoped(caches, segment, None, |report| {
|
|
443
|
+
report.schema_path.name(key)?;
|
|
444
|
+
let reset = report.collection_reset;
|
|
445
|
+
let result = visit(report);
|
|
446
|
+
report.cleanup_reset_paths(caches, reset, result, 1)
|
|
447
|
+
})
|
|
106
448
|
}
|
|
107
|
-
let name = std::str::from_utf8(bytes).map_err(|_| Unsupported)?;
|
|
108
|
-
Ok(name.replace('~', "~0").replace('/', "~1"))
|
|
109
449
|
}
|
|
110
450
|
|
|
111
451
|
impl Execution<'_> {
|
|
452
|
+
pub(super) fn should_continue_report(&self, depth: usize) -> bool {
|
|
453
|
+
depth >= MAX_EVALUATION_DEPTH && self.caches.report_continuations_allowed()
|
|
454
|
+
}
|
|
455
|
+
fn collect_report(
|
|
456
|
+
&self,
|
|
457
|
+
report: &mut Report,
|
|
458
|
+
root: usize,
|
|
459
|
+
instance: Value,
|
|
460
|
+
) -> Result<Tracked, Verdict> {
|
|
461
|
+
let result = self.collect_report_inner(report, root, instance);
|
|
462
|
+
report.finish_runtime(self.caches, result)
|
|
463
|
+
}
|
|
464
|
+
fn collect_report_inner(
|
|
465
|
+
&self,
|
|
466
|
+
report: &mut Report,
|
|
467
|
+
root: usize,
|
|
468
|
+
instance: Value,
|
|
469
|
+
) -> Result<Tracked, Verdict> {
|
|
470
|
+
if let Some(entry) = report.entry.take() {
|
|
471
|
+
report.add_with_frame(
|
|
472
|
+
self.caches,
|
|
473
|
+
entry.keyword,
|
|
474
|
+
entry.source,
|
|
475
|
+
entry.instance,
|
|
476
|
+
0,
|
|
477
|
+
Some(entry.frame),
|
|
478
|
+
)?;
|
|
479
|
+
return Ok(Tracked::new(Invalid));
|
|
480
|
+
}
|
|
481
|
+
if report.annotations {
|
|
482
|
+
self.collect::<true>(report, root, instance, 0, scope::Context::default())
|
|
483
|
+
} else {
|
|
484
|
+
self.collect::<false>(report, root, instance, 0, scope::Context::default())
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Scalar trials export no annotations and need no evaluation memo entry.
|
|
488
|
+
// Keep this before memo admission, especially for contains over scalar arrays.
|
|
489
|
+
pub(super) fn trial_scalar(
|
|
490
|
+
&self,
|
|
491
|
+
node: usize,
|
|
492
|
+
value: Value,
|
|
493
|
+
depth: usize,
|
|
494
|
+
kind: i32,
|
|
495
|
+
) -> Option<Verdict> {
|
|
496
|
+
if self.caches.scalar_callback() {
|
|
497
|
+
return None;
|
|
498
|
+
}
|
|
499
|
+
match self.nodes[node].as_slice() {
|
|
500
|
+
[Op::Number(required, rules)]
|
|
501
|
+
if kind != TYPE_UNSUPPORTED
|
|
502
|
+
&& !self.caches.dynamic_calls()
|
|
503
|
+
&& !rules.runtime_operands() =>
|
|
504
|
+
{
|
|
505
|
+
// A typed-integer failure still performs numeric conversion in
|
|
506
|
+
// a trial, so preserve nonfinite decimal exceptions as well.
|
|
507
|
+
let status = if kind & TYPE_NUMBER != 0 {
|
|
508
|
+
let status = rules.eval(self.caches, value, &self.budget);
|
|
509
|
+
if status != Unsupported && *required != 0 && kind & required == 0 {
|
|
510
|
+
Invalid
|
|
511
|
+
} else {
|
|
512
|
+
status
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
verdict(*required == 0)
|
|
516
|
+
};
|
|
517
|
+
Some(status)
|
|
518
|
+
}
|
|
519
|
+
[op @ (Op::Boolean(_) | Op::Type(..))] => Some(self.leaf(op, kind, value, depth)),
|
|
520
|
+
[op @ Op::String(_, rules)]
|
|
521
|
+
if kind != TYPE_UNSUPPORTED
|
|
522
|
+
&& !self.caches.dynamic_calls()
|
|
523
|
+
&& (kind != TYPE_STRING
|
|
524
|
+
|| rules.length_only()
|
|
525
|
+
|| rules.pattern_only().is_some()
|
|
526
|
+
|| rules.format_only().is_some()
|
|
527
|
+
|| rules.content_only().is_some()) =>
|
|
528
|
+
{
|
|
529
|
+
Some(self.leaf(op, kind, value, depth))
|
|
530
|
+
}
|
|
531
|
+
_ => None,
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
pub(super) fn collect_trial(
|
|
535
|
+
&self,
|
|
536
|
+
node: usize,
|
|
537
|
+
value: Value,
|
|
538
|
+
depth: usize,
|
|
539
|
+
active: scope::Context<'_>,
|
|
540
|
+
kind_state: &InstanceKind,
|
|
541
|
+
) -> Tracked {
|
|
542
|
+
if self.should_continue_report(depth) {
|
|
543
|
+
let mut report = Report {
|
|
544
|
+
silent: true,
|
|
545
|
+
..Report::default()
|
|
546
|
+
};
|
|
547
|
+
return self
|
|
548
|
+
.caches
|
|
549
|
+
.report_child(node, value, &mut report, true)
|
|
550
|
+
.unwrap_or_else(Tracked::new);
|
|
551
|
+
}
|
|
552
|
+
let kind = kind_state.get();
|
|
553
|
+
if depth > MAX_EVALUATION_DEPTH || self.unsupported_kind(node, kind) {
|
|
554
|
+
return Tracked::new(Unsupported);
|
|
555
|
+
}
|
|
556
|
+
if !self.caches.scalar_callback()
|
|
557
|
+
&& let [op @ (Op::Const(_) | Op::Enum(_))] = self.nodes[node].as_slice()
|
|
558
|
+
{
|
|
559
|
+
return Tracked::new(self.leaf(op, kind, value, depth));
|
|
560
|
+
}
|
|
561
|
+
let mut report = Report {
|
|
562
|
+
silent: true,
|
|
563
|
+
..Report::default()
|
|
564
|
+
};
|
|
565
|
+
self.collect_uncached::<true>(&mut report, node, value, depth, active, kind_state)
|
|
566
|
+
.unwrap_or_else(Tracked::new)
|
|
567
|
+
}
|
|
112
568
|
fn collect<const ANNOTATIONS: bool>(
|
|
113
569
|
&self,
|
|
114
570
|
report: &mut Report,
|
|
@@ -117,17 +573,21 @@ impl Execution<'_> {
|
|
|
117
573
|
depth: usize,
|
|
118
574
|
active: scope::Context<'_>,
|
|
119
575
|
) -> Result<Tracked, Verdict> {
|
|
576
|
+
if self.should_continue_report(depth) {
|
|
577
|
+
return self.caches.report_child(node, value, report, ANNOTATIONS);
|
|
578
|
+
}
|
|
120
579
|
if depth > MAX_EVALUATION_DEPTH {
|
|
121
580
|
return Err(Unsupported);
|
|
122
581
|
}
|
|
123
582
|
if !self.budget.checkpoint() {
|
|
124
583
|
return Err(Unsupported);
|
|
125
584
|
}
|
|
126
|
-
let
|
|
127
|
-
|
|
585
|
+
let kind_state = InstanceKind::new(TYPE_UNCHECKED);
|
|
586
|
+
let kind = self.diagnostic_kind(node, value, &kind_state);
|
|
587
|
+
if self.unsupported_kind(node, kind) {
|
|
128
588
|
return Err(Unsupported);
|
|
129
589
|
}
|
|
130
|
-
self.collect_kind::<ANNOTATIONS>(report, node, value, depth, active,
|
|
590
|
+
self.collect_kind::<ANNOTATIONS>(report, node, value, depth, active, &kind_state)
|
|
131
591
|
}
|
|
132
592
|
fn collect_kind<const ANNOTATIONS: bool>(
|
|
133
593
|
&self,
|
|
@@ -136,8 +596,31 @@ impl Execution<'_> {
|
|
|
136
596
|
value: Value,
|
|
137
597
|
depth: usize,
|
|
138
598
|
active: scope::Context<'_>,
|
|
139
|
-
|
|
599
|
+
kind_state: &InstanceKind,
|
|
140
600
|
) -> Result<Tracked, Verdict> {
|
|
601
|
+
let result =
|
|
602
|
+
self.collect_kind_inner::<ANNOTATIONS>(report, node, value, depth, active, kind_state);
|
|
603
|
+
report.finish_runtime(self.caches, result)
|
|
604
|
+
}
|
|
605
|
+
fn collect_kind_inner<const ANNOTATIONS: bool>(
|
|
606
|
+
&self,
|
|
607
|
+
report: &mut Report,
|
|
608
|
+
node: usize,
|
|
609
|
+
value: Value,
|
|
610
|
+
depth: usize,
|
|
611
|
+
active: scope::Context<'_>,
|
|
612
|
+
kind_state: &InstanceKind,
|
|
613
|
+
) -> Result<Tracked, Verdict> {
|
|
614
|
+
if self.should_continue_report(depth) {
|
|
615
|
+
return self.caches.report_child(node, value, report, ANNOTATIONS);
|
|
616
|
+
}
|
|
617
|
+
let kind = self.diagnostic_kind(node, value, kind_state);
|
|
618
|
+
if self.unsupported_kind(node, kind) {
|
|
619
|
+
return Err(Unsupported);
|
|
620
|
+
}
|
|
621
|
+
// Silent children share this collection's failure state. Only actual
|
|
622
|
+
// trials create a fresh report in collect_trial; another isolated trial
|
|
623
|
+
// here would discard earlier failures when a Ruby caller rescues a raise.
|
|
141
624
|
// A diagnostic success is established by complete traversal, never by
|
|
142
625
|
// a short-circuit validity result. Keep exported annotations and the
|
|
143
626
|
// collection policy separate; failures retain their path-specific errors.
|
|
@@ -155,7 +638,8 @@ impl Execution<'_> {
|
|
|
155
638
|
{
|
|
156
639
|
return Ok(result);
|
|
157
640
|
}
|
|
158
|
-
let result =
|
|
641
|
+
let result =
|
|
642
|
+
self.collect_uncached::<ANNOTATIONS>(report, node, value, depth, active, kind_state);
|
|
159
643
|
if let Some(key) = key
|
|
160
644
|
&& let Ok(coverage) = &result
|
|
161
645
|
&& coverage.status == Valid
|
|
@@ -172,11 +656,41 @@ impl Execution<'_> {
|
|
|
172
656
|
value: Value,
|
|
173
657
|
depth: usize,
|
|
174
658
|
active: scope::Context<'_>,
|
|
175
|
-
|
|
659
|
+
kind_state: &InstanceKind,
|
|
660
|
+
) -> Result<Tracked, Verdict> {
|
|
661
|
+
let result = self
|
|
662
|
+
.collect_uncached_inner::<ANNOTATIONS>(report, node, value, depth, active, kind_state);
|
|
663
|
+
report.finish_runtime(self.caches, result)
|
|
664
|
+
}
|
|
665
|
+
fn collect_uncached_inner<const ANNOTATIONS: bool>(
|
|
666
|
+
&self,
|
|
667
|
+
report: &mut Report,
|
|
668
|
+
node: usize,
|
|
669
|
+
value: Value,
|
|
670
|
+
depth: usize,
|
|
671
|
+
active: scope::Context<'_>,
|
|
672
|
+
kind_state: &InstanceKind,
|
|
176
673
|
) -> Result<Tracked, Verdict> {
|
|
674
|
+
let kind = kind_state.get();
|
|
675
|
+
// Callback results currently carry validity only. Preserve the existing
|
|
676
|
+
// diagnostic guard before any other assertion invokes Ruby methods.
|
|
677
|
+
if kind == TYPE_UNSUPPORTED
|
|
678
|
+
&& (self.callback_container_node(node)
|
|
679
|
+
|| self.nodes[node].iter().any(|op| match op {
|
|
680
|
+
Op::Array(_, rules) => !rules.header_only(),
|
|
681
|
+
Op::Object(_, rules) => !rules.header_only(),
|
|
682
|
+
_ => false,
|
|
683
|
+
}))
|
|
684
|
+
&& !self.caches.report_continuations_allowed()
|
|
685
|
+
{
|
|
686
|
+
return Err(Unsupported);
|
|
687
|
+
}
|
|
177
688
|
if depth > MAX_EVALUATION_DEPTH {
|
|
178
689
|
return Err(Unsupported);
|
|
179
690
|
}
|
|
691
|
+
if self.caches.callback_checkpoint(value) != Valid {
|
|
692
|
+
return Err(Unsupported);
|
|
693
|
+
}
|
|
180
694
|
let frame = scope::ScopeFrame {
|
|
181
695
|
resource: self.metadata[node].resource,
|
|
182
696
|
parent: active.scope,
|
|
@@ -186,28 +700,42 @@ impl Execution<'_> {
|
|
|
186
700
|
.or(self.metadata[node].primary_binding),
|
|
187
701
|
};
|
|
188
702
|
let active = self.enter_scope(node, active, &frame);
|
|
703
|
+
let _scope = self
|
|
704
|
+
.caches
|
|
705
|
+
.scope_guard(frame.resource, self.metadata[node].enters_scope);
|
|
189
706
|
let tracked = ANNOTATIONS || self.tracks[node];
|
|
190
707
|
let parent_failed = std::mem::replace(&mut report.failed, false);
|
|
191
708
|
let mut prior = Tracked::new(Valid);
|
|
192
709
|
for (op, &source) in self.nodes[node].iter().zip(&self.error_operands[node]) {
|
|
710
|
+
if self.caches.callback_checkpoint(value) != Valid {
|
|
711
|
+
return Err(Unsupported);
|
|
712
|
+
}
|
|
193
713
|
if !self.budget.checkpoint() {
|
|
194
714
|
return Err(Unsupported);
|
|
195
715
|
}
|
|
196
716
|
match op {
|
|
197
717
|
Op::Unsupported => return Err(Unsupported),
|
|
718
|
+
Op::UnresolvedRef(message) => {
|
|
719
|
+
let keyword = reference_keyword(source.0);
|
|
720
|
+
report.add(self.caches, keyword, (source.0, *message), value, 0)?;
|
|
721
|
+
prior = Tracked::new(Invalid);
|
|
722
|
+
}
|
|
198
723
|
Op::Boolean(false) => {
|
|
199
|
-
report.add("falseSchema", source, value, 0)?;
|
|
724
|
+
report.add(self.caches, "falseSchema", source, value, 0)?;
|
|
200
725
|
prior = Tracked::new(Invalid);
|
|
201
726
|
}
|
|
202
727
|
Op::Boolean(true) => {}
|
|
203
|
-
Op::Type(mask) => {
|
|
204
|
-
|
|
205
|
-
|
|
728
|
+
Op::Type(mask, union) => match self.type_verdict(*mask, *union, kind, value) {
|
|
729
|
+
Unsupported => return Err(Unsupported),
|
|
730
|
+
Valid => {}
|
|
731
|
+
Invalid => {
|
|
732
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
206
733
|
}
|
|
207
|
-
}
|
|
734
|
+
},
|
|
208
735
|
Op::Const(..) | Op::Enum(..) => match self.leaf(op, kind, value, depth) {
|
|
209
736
|
Unsupported => return Err(Unsupported),
|
|
210
737
|
Invalid => report.add(
|
|
738
|
+
self.caches,
|
|
211
739
|
if matches!(op, Op::Const(_)) {
|
|
212
740
|
"const"
|
|
213
741
|
} else {
|
|
@@ -220,30 +748,42 @@ impl Execution<'_> {
|
|
|
220
748
|
Valid => {}
|
|
221
749
|
},
|
|
222
750
|
Op::Number(required, rules) => {
|
|
751
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
752
|
+
rules.runtime_failures(self.caches, *required, value, |keyword| {
|
|
753
|
+
report.add(self.caches, keyword, source, value, 0)
|
|
754
|
+
})?;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
223
757
|
if *required != 0 && kind & required == 0 {
|
|
224
|
-
report.add("type", source, value, 0)?;
|
|
758
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
225
759
|
}
|
|
226
760
|
if kind & TYPE_NUMBER != 0 {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
(MINIMUM, "minimum"),
|
|
231
|
-
(EXCLUSIVE_MAXIMUM, "exclusiveMaximum"),
|
|
232
|
-
(EXCLUSIVE_MINIMUM, "exclusiveMinimum"),
|
|
233
|
-
(MULTIPLE_OF, "multipleOf"),
|
|
234
|
-
] {
|
|
235
|
-
if !self.budget.checkpoint() {
|
|
236
|
-
return Err(Unsupported);
|
|
237
|
-
}
|
|
238
|
-
if bits & bit != 0 {
|
|
239
|
-
report.add(keyword, source, value, 0)?;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
761
|
+
rules.visit_failures(self.caches, value, &self.budget, |keyword| {
|
|
762
|
+
report.add(self.caches, keyword, source, value, 0)
|
|
763
|
+
})?;
|
|
242
764
|
}
|
|
243
765
|
}
|
|
244
766
|
Op::String(required, rules) => {
|
|
767
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
768
|
+
rules.runtime_failures(
|
|
769
|
+
self.caches,
|
|
770
|
+
*required,
|
|
771
|
+
value,
|
|
772
|
+
|keyword, frame, rescued| {
|
|
773
|
+
report.add_string(
|
|
774
|
+
self.caches,
|
|
775
|
+
keyword,
|
|
776
|
+
source,
|
|
777
|
+
value,
|
|
778
|
+
frame,
|
|
779
|
+
rescued,
|
|
780
|
+
)
|
|
781
|
+
},
|
|
782
|
+
)?;
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
245
785
|
if *required != 0 && kind != TYPE_STRING {
|
|
246
|
-
report.add("type", source, value, 0)?;
|
|
786
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
247
787
|
}
|
|
248
788
|
if kind == TYPE_STRING {
|
|
249
789
|
let len = if rules.min.is_some() || rules.max.is_some() {
|
|
@@ -252,29 +792,42 @@ impl Execution<'_> {
|
|
|
252
792
|
0
|
|
253
793
|
};
|
|
254
794
|
if rules.max.is_some_and(|max| len > max) {
|
|
255
|
-
report.add("maxLength", source, value, len)?;
|
|
795
|
+
report.add(self.caches, "maxLength", source, value, len)?;
|
|
256
796
|
}
|
|
257
797
|
if rules.min.is_some_and(|min| len < min) {
|
|
258
|
-
report.add("minLength", source, value, len)?;
|
|
798
|
+
report.add(self.caches, "minLength", source, value, len)?;
|
|
259
799
|
}
|
|
260
800
|
if let Some(pattern) = &rules.pattern {
|
|
261
|
-
match pattern.
|
|
801
|
+
match pattern.assertion(self.caches, value) {
|
|
262
802
|
Unsupported => return Err(Unsupported),
|
|
263
|
-
Invalid =>
|
|
803
|
+
Invalid => {
|
|
804
|
+
let invalid = pattern.invalid(self.caches, value);
|
|
805
|
+
report.add(
|
|
806
|
+
self.caches,
|
|
807
|
+
"pattern",
|
|
808
|
+
source,
|
|
809
|
+
value,
|
|
810
|
+
usize::from(invalid),
|
|
811
|
+
)?;
|
|
812
|
+
if invalid {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
264
816
|
Valid => {}
|
|
265
817
|
}
|
|
266
818
|
}
|
|
267
819
|
if let Some(format) = &rules.format {
|
|
268
820
|
match format.eval(value) {
|
|
269
821
|
Unsupported => return Err(Unsupported),
|
|
270
|
-
Invalid => report.add("format", source, value, 0)?,
|
|
822
|
+
Invalid => report.add(self.caches, "format", source, value, 0)?,
|
|
271
823
|
Valid => {}
|
|
272
824
|
}
|
|
273
825
|
}
|
|
274
826
|
if let Some(content) = &rules.content {
|
|
275
|
-
match content.eval(value) {
|
|
827
|
+
match content.eval(self.caches, value) {
|
|
276
828
|
Unsupported => return Err(Unsupported),
|
|
277
829
|
Invalid => report.add(
|
|
830
|
+
self.caches,
|
|
278
831
|
if content.base64 {
|
|
279
832
|
"contentEncoding"
|
|
280
833
|
} else {
|
|
@@ -290,27 +843,73 @@ impl Execution<'_> {
|
|
|
290
843
|
}
|
|
291
844
|
}
|
|
292
845
|
Op::Array(required, rules) => {
|
|
846
|
+
if self.caches.report_continuations_allowed()
|
|
847
|
+
&& !rules.header_only()
|
|
848
|
+
&& (kind == TYPE_UNSUPPORTED
|
|
849
|
+
|| self.caches.dynamic_calls()
|
|
850
|
+
|| prior.has_runtime_properties())
|
|
851
|
+
{
|
|
852
|
+
let result = self.caches.container_report(
|
|
853
|
+
node,
|
|
854
|
+
value,
|
|
855
|
+
(source.1, *required, false),
|
|
856
|
+
report,
|
|
857
|
+
&prior,
|
|
858
|
+
tracked,
|
|
859
|
+
)?;
|
|
860
|
+
if tracked {
|
|
861
|
+
prior.merge_diagnostics(self, result)?;
|
|
862
|
+
}
|
|
863
|
+
if report.silent && report.failed {
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if rules.header_only() && rules.header.runtime_required(self.caches, kind) {
|
|
869
|
+
if report.collect_header(
|
|
870
|
+
self.caches,
|
|
871
|
+
&rules.header,
|
|
872
|
+
*required,
|
|
873
|
+
value,
|
|
874
|
+
source,
|
|
875
|
+
)? {
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
293
880
|
if kind == TYPE_ARRAY && !self.budget.checkpoint() {
|
|
294
881
|
return Err(Unsupported);
|
|
295
882
|
}
|
|
296
883
|
if *required != 0 && kind != TYPE_ARRAY {
|
|
297
|
-
report.add("type", source, value, 0)?;
|
|
884
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
298
885
|
}
|
|
299
886
|
if kind != TYPE_ARRAY {
|
|
300
887
|
continue;
|
|
301
888
|
}
|
|
889
|
+
if report.silent {
|
|
890
|
+
let status =
|
|
891
|
+
self.array_tracked(rules, value, depth + 1, active, &mut prior);
|
|
892
|
+
if status == Unsupported {
|
|
893
|
+
return Err(Unsupported);
|
|
894
|
+
}
|
|
895
|
+
report.failed |= status == Invalid;
|
|
896
|
+
if report.failed {
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
302
901
|
let mut coverage = Tracked::new(Valid);
|
|
303
902
|
let len = unsafe { sn_len(value) };
|
|
304
903
|
if rules.max.is_some_and(|max| len > max) {
|
|
305
|
-
report.add("maxItems", source, value, len)?;
|
|
904
|
+
report.add(self.caches, "maxItems", source, value, len)?;
|
|
306
905
|
}
|
|
307
906
|
if rules.min.is_some_and(|min| len < min) {
|
|
308
|
-
report.add("minItems", source, value, len)?;
|
|
907
|
+
report.add(self.caches, "minItems", source, value, len)?;
|
|
309
908
|
}
|
|
310
909
|
if rules.unique {
|
|
311
|
-
match
|
|
910
|
+
match self.unique_values(value, depth) {
|
|
312
911
|
Unsupported => return Err(Unsupported),
|
|
313
|
-
Invalid => report.add("uniqueItems", source, value, 0)?,
|
|
912
|
+
Invalid => report.add(self.caches, "uniqueItems", source, value, 0)?,
|
|
314
913
|
Valid => {}
|
|
315
914
|
}
|
|
316
915
|
}
|
|
@@ -321,15 +920,20 @@ impl Execution<'_> {
|
|
|
321
920
|
if tracked {
|
|
322
921
|
coverage.item(index);
|
|
323
922
|
}
|
|
324
|
-
report.scoped(
|
|
325
|
-
self.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
923
|
+
report.scoped(
|
|
924
|
+
self.caches,
|
|
925
|
+
&format!("prefixItems/{index}"),
|
|
926
|
+
Some(index),
|
|
927
|
+
|r| {
|
|
928
|
+
self.collect::<false>(
|
|
929
|
+
r,
|
|
930
|
+
child,
|
|
931
|
+
unsafe { sn_entry(value, index) },
|
|
932
|
+
depth + 1,
|
|
933
|
+
active,
|
|
934
|
+
)
|
|
935
|
+
},
|
|
936
|
+
)?;
|
|
333
937
|
}
|
|
334
938
|
if rules.list {
|
|
335
939
|
for (index, &child) in rules.items.iter().take(len).enumerate() {
|
|
@@ -339,15 +943,20 @@ impl Execution<'_> {
|
|
|
339
943
|
if tracked {
|
|
340
944
|
coverage.item(index);
|
|
341
945
|
}
|
|
342
|
-
report.scoped(
|
|
343
|
-
self.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
946
|
+
report.scoped(
|
|
947
|
+
self.caches,
|
|
948
|
+
&format!("items/{index}"),
|
|
949
|
+
Some(index),
|
|
950
|
+
|r| {
|
|
951
|
+
self.collect::<false>(
|
|
952
|
+
r,
|
|
953
|
+
child,
|
|
954
|
+
unsafe { sn_entry(value, index) },
|
|
955
|
+
depth + 1,
|
|
956
|
+
active,
|
|
957
|
+
)
|
|
958
|
+
},
|
|
959
|
+
)?;
|
|
351
960
|
}
|
|
352
961
|
if let Some(child) = rules.additional {
|
|
353
962
|
for index in rules.items.len()..len {
|
|
@@ -357,20 +966,25 @@ impl Execution<'_> {
|
|
|
357
966
|
if tracked {
|
|
358
967
|
coverage.item(index);
|
|
359
968
|
}
|
|
360
|
-
report.scoped(
|
|
361
|
-
self.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
969
|
+
report.scoped(
|
|
970
|
+
self.caches,
|
|
971
|
+
"additionalItems",
|
|
972
|
+
Some(index),
|
|
973
|
+
|r| {
|
|
974
|
+
self.collect::<false>(
|
|
975
|
+
r,
|
|
976
|
+
child,
|
|
977
|
+
unsafe { sn_entry(value, index) },
|
|
978
|
+
depth + 1,
|
|
979
|
+
active,
|
|
980
|
+
)
|
|
981
|
+
},
|
|
982
|
+
)?;
|
|
369
983
|
}
|
|
370
984
|
}
|
|
371
985
|
} else if let Some(&child) = rules.items.first() {
|
|
372
986
|
let primitive = match self.nodes[child].as_slice() {
|
|
373
|
-
[Op::Type(mask)]
|
|
987
|
+
[Op::Type(mask, _)]
|
|
374
988
|
if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 =>
|
|
375
989
|
{
|
|
376
990
|
Some(&[][..])
|
|
@@ -378,6 +992,7 @@ impl Execution<'_> {
|
|
|
378
992
|
[Op::Number(_, rules)] => rules.fast_limits.as_deref(),
|
|
379
993
|
_ => None,
|
|
380
994
|
};
|
|
995
|
+
let primitive = primitive.filter(|_| !self.caches.dynamic_calls());
|
|
381
996
|
let values = if len == 0 {
|
|
382
997
|
&[]
|
|
383
998
|
} else {
|
|
@@ -389,9 +1004,12 @@ impl Execution<'_> {
|
|
|
389
1004
|
}
|
|
390
1005
|
_ => None,
|
|
391
1006
|
};
|
|
392
|
-
let pattern = pattern
|
|
1007
|
+
let pattern = pattern
|
|
1008
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls());
|
|
393
1009
|
let number = match self.nodes[child].as_slice() {
|
|
394
|
-
[Op::Number(required, number)]
|
|
1010
|
+
[Op::Number(required, number)]
|
|
1011
|
+
if depth < MAX_EVALUATION_DEPTH && !self.caches.dynamic_calls() =>
|
|
1012
|
+
{
|
|
395
1013
|
Some((*required, number))
|
|
396
1014
|
}
|
|
397
1015
|
_ => None,
|
|
@@ -402,14 +1020,16 @@ impl Execution<'_> {
|
|
|
402
1020
|
}
|
|
403
1021
|
_ => None,
|
|
404
1022
|
};
|
|
405
|
-
let format =
|
|
1023
|
+
let format =
|
|
1024
|
+
format.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls());
|
|
406
1025
|
let content = match self.nodes[child].as_slice() {
|
|
407
1026
|
[Op::String(required, string)] if depth < MAX_EVALUATION_DEPTH => {
|
|
408
1027
|
string.content_only().map(|content| (*required, content))
|
|
409
1028
|
}
|
|
410
1029
|
_ => None,
|
|
411
1030
|
};
|
|
412
|
-
let content = content
|
|
1031
|
+
let content = content
|
|
1032
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls());
|
|
413
1033
|
if pattern.is_some() || format.is_some() || content.is_some() {
|
|
414
1034
|
let start = rules.prefix.len().min(len);
|
|
415
1035
|
if tracked {
|
|
@@ -423,6 +1043,7 @@ impl Execution<'_> {
|
|
|
423
1043
|
// The invalid callback is shared, but each scanner retains
|
|
424
1044
|
// its statically selected parser and guarded byte views.
|
|
425
1045
|
let invalid = |index, item| match report.scoped(
|
|
1046
|
+
self.caches,
|
|
426
1047
|
"items",
|
|
427
1048
|
Some(start + index),
|
|
428
1049
|
|r| self.collect::<false>(r, child, item, depth + 1, active),
|
|
@@ -440,9 +1061,12 @@ impl Execution<'_> {
|
|
|
440
1061
|
(_, Some((required, format)), _) => {
|
|
441
1062
|
format.visit_invalid_items(required, &values[start..], invalid)
|
|
442
1063
|
}
|
|
443
|
-
(_, _, Some((required, content))) =>
|
|
444
|
-
|
|
445
|
-
|
|
1064
|
+
(_, _, Some((required, content))) => content.visit_invalid_items(
|
|
1065
|
+
self.caches,
|
|
1066
|
+
required,
|
|
1067
|
+
&values[start..],
|
|
1068
|
+
invalid,
|
|
1069
|
+
),
|
|
446
1070
|
_ => unreachable!("a scalar item scanner was selected"),
|
|
447
1071
|
};
|
|
448
1072
|
if status != Valid {
|
|
@@ -472,18 +1096,25 @@ impl Execution<'_> {
|
|
|
472
1096
|
_ => n > limit,
|
|
473
1097
|
})
|
|
474
1098
|
} else if let Some((required, number)) = number {
|
|
475
|
-
match number.eval_guarded(
|
|
1099
|
+
match number.eval_guarded(
|
|
1100
|
+
self.caches,
|
|
1101
|
+
required,
|
|
1102
|
+
item,
|
|
1103
|
+
&self.budget,
|
|
1104
|
+
) {
|
|
476
1105
|
Valid => false,
|
|
477
1106
|
Invalid => true,
|
|
478
|
-
Unsupported =>
|
|
1107
|
+
Unsupported => {
|
|
1108
|
+
self.needs_diagnostics(child, item, depth + 1)?
|
|
1109
|
+
}
|
|
479
1110
|
}
|
|
480
1111
|
} else {
|
|
481
|
-
self.needs_diagnostics(child, item, depth + 1
|
|
1112
|
+
self.needs_diagnostics(child, item, depth + 1)?
|
|
482
1113
|
};
|
|
483
1114
|
if !needs {
|
|
484
1115
|
continue;
|
|
485
1116
|
}
|
|
486
|
-
report.scoped("items", Some(index), |r| {
|
|
1117
|
+
report.scoped(self.caches, "items", Some(index), |r| {
|
|
487
1118
|
self.collect::<false>(r, child, item, depth + 1, active)
|
|
488
1119
|
})?;
|
|
489
1120
|
}
|
|
@@ -497,15 +1128,8 @@ impl Execution<'_> {
|
|
|
497
1128
|
};
|
|
498
1129
|
let matches = evaluation::scan_contains(
|
|
499
1130
|
len,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
values[index],
|
|
503
|
-
depth + 1,
|
|
504
|
-
active,
|
|
505
|
-
) {
|
|
506
|
-
Ok(needs) => verdict(!needs),
|
|
507
|
-
Err(status) => status,
|
|
508
|
-
},
|
|
1131
|
+
evaluation::ContainsPolicy::AllMatches,
|
|
1132
|
+
|index| self.trial_status(child, values[index], depth + 1, active),
|
|
509
1133
|
|index| {
|
|
510
1134
|
if tracked {
|
|
511
1135
|
coverage.item(index);
|
|
@@ -514,7 +1138,7 @@ impl Execution<'_> {
|
|
|
514
1138
|
)?;
|
|
515
1139
|
if bounds(matches, Some(rules.min_contains), rules.max_contains) == Invalid
|
|
516
1140
|
{
|
|
517
|
-
report.add("contains", source, value, matches)?;
|
|
1141
|
+
report.add(self.caches, "contains", source, value, matches)?;
|
|
518
1142
|
}
|
|
519
1143
|
}
|
|
520
1144
|
if let Some(child) = rules
|
|
@@ -528,7 +1152,7 @@ impl Execution<'_> {
|
|
|
528
1152
|
if prior.contains_item(index) || coverage.contains_item(index) {
|
|
529
1153
|
continue;
|
|
530
1154
|
}
|
|
531
|
-
report.scoped("unevaluatedItems", Some(index), |r| {
|
|
1155
|
+
report.scoped(self.caches, "unevaluatedItems", Some(index), |r| {
|
|
532
1156
|
self.collect::<false>(
|
|
533
1157
|
r,
|
|
534
1158
|
child,
|
|
@@ -541,33 +1165,79 @@ impl Execution<'_> {
|
|
|
541
1165
|
}
|
|
542
1166
|
}
|
|
543
1167
|
if tracked {
|
|
544
|
-
prior.merge_diagnostics(coverage)?;
|
|
1168
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
545
1169
|
}
|
|
546
1170
|
}
|
|
547
1171
|
Op::Object(required, rules) => {
|
|
1172
|
+
if self.caches.report_continuations_allowed()
|
|
1173
|
+
&& !rules.header_only()
|
|
1174
|
+
&& (kind == TYPE_UNSUPPORTED
|
|
1175
|
+
|| self.caches.dynamic_calls()
|
|
1176
|
+
|| prior.has_runtime_properties())
|
|
1177
|
+
{
|
|
1178
|
+
let result = self.caches.container_report(
|
|
1179
|
+
node,
|
|
1180
|
+
value,
|
|
1181
|
+
(source.1, *required, true),
|
|
1182
|
+
report,
|
|
1183
|
+
&prior,
|
|
1184
|
+
tracked,
|
|
1185
|
+
)?;
|
|
1186
|
+
if tracked {
|
|
1187
|
+
prior.merge_diagnostics(self, result)?;
|
|
1188
|
+
}
|
|
1189
|
+
if report.silent && report.failed {
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if rules.header_only() && rules.header.runtime_required(self.caches, kind) {
|
|
1195
|
+
if report.collect_header(
|
|
1196
|
+
self.caches,
|
|
1197
|
+
&rules.header,
|
|
1198
|
+
*required,
|
|
1199
|
+
value,
|
|
1200
|
+
source,
|
|
1201
|
+
)? {
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
548
1206
|
if kind == TYPE_OBJECT && !self.budget.checkpoint() {
|
|
549
1207
|
return Err(Unsupported);
|
|
550
1208
|
}
|
|
551
1209
|
if *required != 0 && kind != TYPE_OBJECT {
|
|
552
|
-
report.add("type", source, value, 0)?;
|
|
1210
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
553
1211
|
}
|
|
554
1212
|
if kind != TYPE_OBJECT {
|
|
555
1213
|
continue;
|
|
556
1214
|
}
|
|
1215
|
+
if report.silent {
|
|
1216
|
+
let status =
|
|
1217
|
+
self.object_tracked(rules, value, depth + 1, active, &mut prior);
|
|
1218
|
+
if status == Unsupported {
|
|
1219
|
+
return Err(Unsupported);
|
|
1220
|
+
}
|
|
1221
|
+
report.failed |= status == Invalid;
|
|
1222
|
+
if report.failed {
|
|
1223
|
+
break;
|
|
1224
|
+
}
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
557
1227
|
let mut coverage = Tracked::new(Valid);
|
|
558
1228
|
let len = unsafe { sn_len(value) };
|
|
559
1229
|
if rules.max.is_some_and(|max| len > max) {
|
|
560
|
-
report.add("maxProperties", source, value, len)?;
|
|
1230
|
+
report.add(self.caches, "maxProperties", source, value, len)?;
|
|
561
1231
|
}
|
|
562
1232
|
if rules.min.is_some_and(|min| len < min) {
|
|
563
|
-
report.add("minProperties", source, value, len)?;
|
|
1233
|
+
report.add(self.caches, "minProperties", source, value, len)?;
|
|
564
1234
|
}
|
|
565
1235
|
for &name in &rules.required_order {
|
|
566
1236
|
if !self.budget.checkpoint() {
|
|
567
1237
|
return Err(Unsupported);
|
|
568
1238
|
}
|
|
569
|
-
if missing(
|
|
570
|
-
report.add("required", source, name, 0)?;
|
|
1239
|
+
if name.missing(self.caches, value, self.undefined)? {
|
|
1240
|
+
report.add(self.caches, "required", source, name.name, 0)?;
|
|
571
1241
|
}
|
|
572
1242
|
}
|
|
573
1243
|
let mut context = ObjectDiagnostics {
|
|
@@ -621,8 +1291,14 @@ impl Execution<'_> {
|
|
|
621
1291
|
if !self.budget.checkpoint() {
|
|
622
1292
|
return Err(Unsupported);
|
|
623
1293
|
}
|
|
624
|
-
if missing(
|
|
625
|
-
report.add(
|
|
1294
|
+
if required.missing(self.caches, value, self.undefined)? {
|
|
1295
|
+
report.add(
|
|
1296
|
+
self.caches,
|
|
1297
|
+
keyword,
|
|
1298
|
+
(source.0, *name),
|
|
1299
|
+
required.name,
|
|
1300
|
+
0,
|
|
1301
|
+
)?;
|
|
626
1302
|
}
|
|
627
1303
|
}
|
|
628
1304
|
}
|
|
@@ -631,10 +1307,8 @@ impl Execution<'_> {
|
|
|
631
1307
|
continue;
|
|
632
1308
|
}
|
|
633
1309
|
if tracked {
|
|
634
|
-
let result =
|
|
635
|
-
|
|
636
|
-
None,
|
|
637
|
-
|r| {
|
|
1310
|
+
let result =
|
|
1311
|
+
report.schema_name(self.caches, keyword, *name, |r| {
|
|
638
1312
|
self.collect::<true>(
|
|
639
1313
|
r,
|
|
640
1314
|
*child,
|
|
@@ -642,30 +1316,16 @@ impl Execution<'_> {
|
|
|
642
1316
|
depth + 1,
|
|
643
1317
|
active,
|
|
644
1318
|
)
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1319
|
+
})?;
|
|
1320
|
+
if result.status == Valid
|
|
1321
|
+
&& coverage.merge_runtime(self, result) == Unsupported
|
|
1322
|
+
{
|
|
1323
|
+
return Err(Unsupported);
|
|
649
1324
|
}
|
|
650
|
-
} else if self.needs_diagnostics(
|
|
651
|
-
*
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
active,
|
|
655
|
-
)? {
|
|
656
|
-
report.scoped(
|
|
657
|
-
&format!("{keyword}/{}", pointer_name(*name)?),
|
|
658
|
-
None,
|
|
659
|
-
|r| {
|
|
660
|
-
self.collect::<false>(
|
|
661
|
-
r,
|
|
662
|
-
*child,
|
|
663
|
-
value,
|
|
664
|
-
depth + 1,
|
|
665
|
-
active,
|
|
666
|
-
)
|
|
667
|
-
},
|
|
668
|
-
)?;
|
|
1325
|
+
} else if self.needs_diagnostics(*child, value, depth + 1)? {
|
|
1326
|
+
report.schema_name(self.caches, keyword, *name, |r| {
|
|
1327
|
+
self.collect::<false>(r, *child, value, depth + 1, active)
|
|
1328
|
+
})?;
|
|
669
1329
|
}
|
|
670
1330
|
}
|
|
671
1331
|
}
|
|
@@ -698,7 +1358,7 @@ impl Execution<'_> {
|
|
|
698
1358
|
}
|
|
699
1359
|
}
|
|
700
1360
|
if tracked {
|
|
701
|
-
prior.merge_diagnostics(coverage)?;
|
|
1361
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
702
1362
|
}
|
|
703
1363
|
}
|
|
704
1364
|
Op::All(nodes) => {
|
|
@@ -707,19 +1367,34 @@ impl Execution<'_> {
|
|
|
707
1367
|
if !self.budget.checkpoint() {
|
|
708
1368
|
return Err(Unsupported);
|
|
709
1369
|
}
|
|
710
|
-
let result =
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1370
|
+
let result =
|
|
1371
|
+
report.scoped(self.caches, &format!("allOf/{index}"), None, |r| {
|
|
1372
|
+
if tracked {
|
|
1373
|
+
self.collect_kind::<true>(
|
|
1374
|
+
r,
|
|
1375
|
+
child,
|
|
1376
|
+
value,
|
|
1377
|
+
depth + 1,
|
|
1378
|
+
active,
|
|
1379
|
+
kind_state,
|
|
1380
|
+
)
|
|
1381
|
+
} else {
|
|
1382
|
+
self.collect_kind::<false>(
|
|
1383
|
+
r,
|
|
1384
|
+
child,
|
|
1385
|
+
value,
|
|
1386
|
+
depth + 1,
|
|
1387
|
+
active,
|
|
1388
|
+
kind_state,
|
|
1389
|
+
)
|
|
1390
|
+
}
|
|
1391
|
+
})?;
|
|
717
1392
|
if tracked {
|
|
718
|
-
coverage.merge_diagnostics(result)?;
|
|
1393
|
+
coverage.merge_diagnostics(self, result)?;
|
|
719
1394
|
}
|
|
720
1395
|
}
|
|
721
1396
|
if tracked {
|
|
722
|
-
prior.merge_diagnostics(coverage)?;
|
|
1397
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
723
1398
|
}
|
|
724
1399
|
}
|
|
725
1400
|
Op::Any(nodes) | Op::One(nodes) => {
|
|
@@ -728,67 +1403,68 @@ impl Execution<'_> {
|
|
|
728
1403
|
} else {
|
|
729
1404
|
evaluation::BranchKind::One
|
|
730
1405
|
};
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
let result =
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
)?;
|
|
747
|
-
evaluation::BranchMatches {
|
|
748
|
-
matches: result.matches,
|
|
749
|
-
coverage: Tracked::new(Valid),
|
|
750
|
-
}
|
|
751
|
-
};
|
|
1406
|
+
// Trials execute complete scalar checks without retaining
|
|
1407
|
+
// errors or paths; ordinary validity may stop earlier.
|
|
1408
|
+
let result = evaluation::scan_branches(
|
|
1409
|
+
nodes,
|
|
1410
|
+
evaluation::BranchPolicy::Complete,
|
|
1411
|
+
self.caches.dynamic_calls(),
|
|
1412
|
+
|child| self.eval_tracked_kind(child, value, depth + 1, active, kind_state),
|
|
1413
|
+
|coverage, branch| {
|
|
1414
|
+
if coverage.merge_runtime(self, branch) == Unsupported {
|
|
1415
|
+
Err(Unsupported)
|
|
1416
|
+
} else {
|
|
1417
|
+
Ok(())
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
)?;
|
|
752
1421
|
if !branch_kind.accepts(result.matches) {
|
|
753
|
-
report.add(
|
|
1422
|
+
report.add(
|
|
1423
|
+
self.caches,
|
|
1424
|
+
branch_kind.keyword(),
|
|
1425
|
+
source,
|
|
1426
|
+
value,
|
|
1427
|
+
result.matches,
|
|
1428
|
+
)?;
|
|
754
1429
|
} else if tracked {
|
|
755
|
-
prior.merge_diagnostics(result.coverage)?;
|
|
1430
|
+
prior.merge_diagnostics(self, result.coverage)?;
|
|
756
1431
|
}
|
|
757
1432
|
}
|
|
758
1433
|
Op::Not(child) => {
|
|
759
|
-
match self
|
|
1434
|
+
match self
|
|
1435
|
+
.eval_tracked_kind(*child, value, depth + 1, active, kind_state)
|
|
1436
|
+
.status
|
|
1437
|
+
{
|
|
760
1438
|
Unsupported => return Err(Unsupported),
|
|
761
|
-
Valid => report.add("not", source, value, 0)?,
|
|
1439
|
+
Valid => report.add(self.caches, "not", source, value, 0)?,
|
|
762
1440
|
Invalid => {}
|
|
763
1441
|
}
|
|
764
1442
|
}
|
|
765
1443
|
Op::Conditional(condition, yes, no) => {
|
|
766
|
-
let condition =
|
|
767
|
-
self.eval_tracked_kind(*condition, value, depth + 1, active,
|
|
768
|
-
} else {
|
|
769
|
-
Tracked::new(self.eval_with_kind(
|
|
770
|
-
*condition,
|
|
771
|
-
value,
|
|
772
|
-
depth + 1,
|
|
773
|
-
active,
|
|
774
|
-
Some(kind),
|
|
775
|
-
))
|
|
776
|
-
};
|
|
1444
|
+
let condition =
|
|
1445
|
+
self.eval_tracked_kind(*condition, value, depth + 1, active, kind_state);
|
|
777
1446
|
let mut coverage = Tracked::new(Valid);
|
|
778
1447
|
let (child, segment) = match condition.status {
|
|
779
1448
|
Unsupported => return Err(Unsupported),
|
|
780
1449
|
Valid => {
|
|
781
|
-
if tracked {
|
|
782
|
-
|
|
1450
|
+
if tracked && coverage.merge_runtime(self, condition) == Unsupported {
|
|
1451
|
+
return Err(Unsupported);
|
|
783
1452
|
}
|
|
784
1453
|
(yes, "then")
|
|
785
1454
|
}
|
|
786
1455
|
Invalid => (no, "else"),
|
|
787
1456
|
};
|
|
788
1457
|
if let Some(child) = child {
|
|
789
|
-
let result = report.scoped(segment, None, |r| {
|
|
1458
|
+
let result = report.scoped(self.caches, segment, None, |r| {
|
|
790
1459
|
if tracked {
|
|
791
|
-
self.collect_kind::<true>(
|
|
1460
|
+
self.collect_kind::<true>(
|
|
1461
|
+
r,
|
|
1462
|
+
*child,
|
|
1463
|
+
value,
|
|
1464
|
+
depth + 1,
|
|
1465
|
+
active,
|
|
1466
|
+
kind_state,
|
|
1467
|
+
)
|
|
792
1468
|
} else {
|
|
793
1469
|
self.collect_kind::<false>(
|
|
794
1470
|
r,
|
|
@@ -796,20 +1472,24 @@ impl Execution<'_> {
|
|
|
796
1472
|
value,
|
|
797
1473
|
depth + 1,
|
|
798
1474
|
active,
|
|
799
|
-
|
|
1475
|
+
kind_state,
|
|
800
1476
|
)
|
|
801
1477
|
}
|
|
802
1478
|
})?;
|
|
803
1479
|
if tracked {
|
|
804
|
-
coverage.merge_diagnostics(result)?;
|
|
1480
|
+
coverage.merge_diagnostics(self, result)?;
|
|
805
1481
|
}
|
|
806
1482
|
}
|
|
807
1483
|
if tracked {
|
|
808
|
-
prior.merge_diagnostics(coverage)?;
|
|
1484
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
809
1485
|
}
|
|
810
1486
|
}
|
|
811
1487
|
Op::Ref(target) => {
|
|
812
|
-
|
|
1488
|
+
let target = self.resolve_reference(*target, value, active)?;
|
|
1489
|
+
let program = self.programs[node];
|
|
1490
|
+
if RefFrame::contains(active.active, node, value)
|
|
1491
|
+
|| self.caches.reference_active(program, value)
|
|
1492
|
+
{
|
|
813
1493
|
continue;
|
|
814
1494
|
}
|
|
815
1495
|
let frame = RefFrame {
|
|
@@ -817,29 +1497,51 @@ impl Execution<'_> {
|
|
|
817
1497
|
value,
|
|
818
1498
|
parent: active.active,
|
|
819
1499
|
};
|
|
820
|
-
let keyword =
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
_ => "$ref",
|
|
824
|
-
};
|
|
825
|
-
let target = self.reference_target(*target, active);
|
|
1500
|
+
let keyword = reference_keyword(source.0);
|
|
1501
|
+
let reference = self.caches.reference_frame(program, value);
|
|
1502
|
+
let _guard = self.caches.reference_guard(&reference);
|
|
826
1503
|
let active = scope::Context {
|
|
827
1504
|
active: Some(&frame),
|
|
828
1505
|
..active
|
|
829
1506
|
};
|
|
830
|
-
let result = report.scoped(keyword, None, |r| {
|
|
1507
|
+
let result = report.scoped(self.caches, keyword, None, |r| {
|
|
831
1508
|
if tracked {
|
|
832
|
-
self.collect_kind::<true>(
|
|
1509
|
+
self.collect_kind::<true>(
|
|
1510
|
+
r,
|
|
1511
|
+
target,
|
|
1512
|
+
value,
|
|
1513
|
+
depth + 1,
|
|
1514
|
+
active,
|
|
1515
|
+
kind_state,
|
|
1516
|
+
)
|
|
833
1517
|
} else {
|
|
834
|
-
self.collect_kind::<false>(
|
|
1518
|
+
self.collect_kind::<false>(
|
|
1519
|
+
r,
|
|
1520
|
+
target,
|
|
1521
|
+
value,
|
|
1522
|
+
depth + 1,
|
|
1523
|
+
active,
|
|
1524
|
+
kind_state,
|
|
1525
|
+
)
|
|
835
1526
|
}
|
|
836
1527
|
})?;
|
|
837
1528
|
if tracked {
|
|
838
|
-
prior.merge_diagnostics(result)?;
|
|
1529
|
+
prior.merge_diagnostics(self, result)?;
|
|
839
1530
|
}
|
|
840
1531
|
}
|
|
841
1532
|
}
|
|
1533
|
+
self.caches.apply_report_effects(report);
|
|
1534
|
+
if report.silent
|
|
1535
|
+
&& report.failed
|
|
1536
|
+
&& matches!(
|
|
1537
|
+
op,
|
|
1538
|
+
Op::All(_) | Op::Any(_) | Op::One(_) | Op::Not(_) | Op::Conditional(..)
|
|
1539
|
+
)
|
|
1540
|
+
{
|
|
1541
|
+
break;
|
|
1542
|
+
}
|
|
842
1543
|
}
|
|
1544
|
+
self.caches.apply_report_effects(report);
|
|
843
1545
|
let failed = report.failed;
|
|
844
1546
|
report.failed |= parent_failed;
|
|
845
1547
|
// Diagnostic coverage keeps local attempted locations for subsequent
|
|
@@ -849,33 +1551,61 @@ impl Execution<'_> {
|
|
|
849
1551
|
|
|
850
1552
|
fn needs_diagnostics(
|
|
851
1553
|
&self,
|
|
852
|
-
node: usize,
|
|
1554
|
+
mut node: usize,
|
|
853
1555
|
value: Value,
|
|
854
|
-
depth: usize,
|
|
855
|
-
active: scope::Context<'_>,
|
|
1556
|
+
mut depth: usize,
|
|
856
1557
|
) -> Result<bool, Verdict> {
|
|
857
|
-
|
|
858
|
-
|
|
1558
|
+
loop {
|
|
1559
|
+
if !self.budget.checkpoint() {
|
|
1560
|
+
return Err(Unsupported);
|
|
1561
|
+
}
|
|
1562
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
1563
|
+
return Ok(true);
|
|
1564
|
+
}
|
|
1565
|
+
let [Op::Ref(reference)] = self.nodes[node].as_slice() else {
|
|
1566
|
+
break;
|
|
1567
|
+
};
|
|
1568
|
+
let Some(target) = reference.static_target() else {
|
|
1569
|
+
break;
|
|
1570
|
+
};
|
|
1571
|
+
node = target;
|
|
1572
|
+
depth += 1;
|
|
859
1573
|
}
|
|
860
1574
|
let status = match self.nodes[node].as_slice() {
|
|
861
1575
|
[Op::Number(required, rules)] if depth <= MAX_EVALUATION_DEPTH => {
|
|
862
|
-
|
|
1576
|
+
if self.caches.dynamic_calls()
|
|
1577
|
+
|| self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED))
|
|
1578
|
+
== TYPE_UNSUPPORTED
|
|
1579
|
+
{
|
|
1580
|
+
return Ok(true);
|
|
1581
|
+
}
|
|
1582
|
+
rules.eval_guarded(self.caches, *required, value, &self.budget)
|
|
863
1583
|
}
|
|
864
|
-
[
|
|
865
|
-
|
|
866
|
-
{
|
|
867
|
-
let kind =
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1584
|
+
[
|
|
1585
|
+
op @ (Op::Type(..) | Op::Boolean(_) | Op::String(..) | Op::Const(_) | Op::Enum(_)),
|
|
1586
|
+
] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1587
|
+
let kind = self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED));
|
|
1588
|
+
if (kind == TYPE_UNSUPPORTED
|
|
1589
|
+
&& matches!(
|
|
1590
|
+
op,
|
|
1591
|
+
Op::Type(..) | Op::String(..) | Op::Const(_) | Op::Enum(_)
|
|
1592
|
+
))
|
|
1593
|
+
|| (self.caches.dynamic_calls()
|
|
1594
|
+
&& matches!(op, Op::String(..) | Op::Const(_) | Op::Enum(_)))
|
|
1595
|
+
{
|
|
1596
|
+
// Collection must execute a dynamic predicate once, rather
|
|
1597
|
+
// than once here and again while recording its failure.
|
|
1598
|
+
return Ok(true);
|
|
1599
|
+
}
|
|
1600
|
+
if self.unsupported_kind(node, kind) {
|
|
873
1601
|
Unsupported
|
|
874
1602
|
} else {
|
|
875
1603
|
self.leaf(op, kind, value, depth)
|
|
876
1604
|
}
|
|
877
1605
|
}
|
|
878
|
-
|
|
1606
|
+
// A successful validity/trial scan can omit diagnostic-only work
|
|
1607
|
+
// in descendants. Only complete collection proves their success.
|
|
1608
|
+
_ => return Ok(true),
|
|
879
1609
|
};
|
|
880
1610
|
match status {
|
|
881
1611
|
Valid => Ok(false),
|
|
@@ -904,8 +1634,8 @@ unsafe extern "C" fn object_diagnostic(context: *mut c_void, key: Value, value:
|
|
|
904
1634
|
return Unsupported as i32;
|
|
905
1635
|
}
|
|
906
1636
|
let visit = |report: &mut Report, child: usize, item: Value, segment: &str, schema_key| {
|
|
907
|
-
if c.engine.needs_diagnostics(child, item, c.depth
|
|
908
|
-
report.property(segment, key, schema_key, |r| {
|
|
1637
|
+
if c.engine.needs_diagnostics(child, item, c.depth)? {
|
|
1638
|
+
report.property(c.engine.caches, segment, key, schema_key, |r| {
|
|
909
1639
|
c.engine.collect::<false>(r, child, item, c.depth, c.active)
|
|
910
1640
|
})?;
|
|
911
1641
|
}
|
|
@@ -934,9 +1664,10 @@ unsafe extern "C" fn object_diagnostic(context: *mut c_void, key: Value, value:
|
|
|
934
1664
|
c.coverage.property(c.engine, key);
|
|
935
1665
|
return Ok(());
|
|
936
1666
|
}
|
|
937
|
-
let evaluated = c.rules.visit_value_rules
|
|
1667
|
+
let evaluated = c.rules.visit_value_rules(
|
|
938
1668
|
c.engine.caches,
|
|
939
1669
|
&c.engine.budget,
|
|
1670
|
+
true,
|
|
940
1671
|
key,
|
|
941
1672
|
None,
|
|
942
1673
|
|segment, name, child| visit(c.report, child, value, segment, name),
|
|
@@ -962,10 +1693,20 @@ pub struct Record {
|
|
|
962
1693
|
instance_len: usize,
|
|
963
1694
|
schema_path: *const u8,
|
|
964
1695
|
schema_len: usize,
|
|
1696
|
+
instance_pointer: Value,
|
|
1697
|
+
schema_pointer: Value,
|
|
965
1698
|
opcode: i32,
|
|
966
1699
|
operand: Value,
|
|
967
1700
|
instance: Value,
|
|
968
1701
|
detail: usize,
|
|
1702
|
+
message: Value,
|
|
1703
|
+
}
|
|
1704
|
+
fn report_outcome(result: std::thread::Result<Result<*mut Report, i32>>) -> (*mut Report, i32) {
|
|
1705
|
+
match result {
|
|
1706
|
+
Ok(Ok(report)) => (report, VERDICT_VALID),
|
|
1707
|
+
Ok(Err(code)) => (std::ptr::null_mut(), code),
|
|
1708
|
+
Err(_) => (std::ptr::null_mut(), STATUS_PANIC),
|
|
1709
|
+
}
|
|
969
1710
|
}
|
|
970
1711
|
/// # Safety
|
|
971
1712
|
/// Engine/status must be live under the GVL; root must be in bounds and
|
|
@@ -973,14 +1714,14 @@ pub struct Record {
|
|
|
973
1714
|
#[unsafe(no_mangle)]
|
|
974
1715
|
pub unsafe extern "C" fn sn_diagnose(
|
|
975
1716
|
engine: *const Engine,
|
|
976
|
-
caches: *const
|
|
1717
|
+
caches: *const runtime::Caches,
|
|
977
1718
|
root: usize,
|
|
978
1719
|
instance: Value,
|
|
979
1720
|
status: *mut i32,
|
|
980
1721
|
) -> *mut Report {
|
|
981
1722
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
982
1723
|
let engine = unsafe { &*engine };
|
|
983
|
-
let local =
|
|
1724
|
+
let local = runtime::Caches::default();
|
|
984
1725
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
985
1726
|
let execution = Execution {
|
|
986
1727
|
engine,
|
|
@@ -988,33 +1729,29 @@ pub unsafe extern "C" fn sn_diagnose(
|
|
|
988
1729
|
memo: memo::Memo::default(),
|
|
989
1730
|
budget: budget::Budget::default(),
|
|
990
1731
|
};
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1732
|
+
caches.admit(engine, root);
|
|
1733
|
+
let inherited = caches.initial_report();
|
|
1734
|
+
let resumed = inherited.is_some();
|
|
1735
|
+
let mut report = inherited.unwrap_or_else(|| Report {
|
|
1736
|
+
render_messages: engine.runtime_messages,
|
|
1737
|
+
..Report::default()
|
|
1738
|
+
});
|
|
1739
|
+
let result = execution
|
|
1740
|
+
.collect_report(&mut report, root, instance)
|
|
1741
|
+
.map_err(|verdict| execution.budget.status(verdict))?;
|
|
1742
|
+
report.evaluation = if resumed { Some(result) } else { None };
|
|
1743
|
+
Ok::<_, i32>(Box::into_raw(Box::new(report)))
|
|
994
1744
|
}));
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
report
|
|
999
|
-
}
|
|
1000
|
-
other => {
|
|
1001
|
-
unsafe {
|
|
1002
|
-
*status = if other.is_err() {
|
|
1003
|
-
STATUS_PANIC
|
|
1004
|
-
} else {
|
|
1005
|
-
VERDICT_UNSUPPORTED
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
std::ptr::null_mut()
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1745
|
+
let (report, code) = report_outcome(result);
|
|
1746
|
+
unsafe { *status = code };
|
|
1747
|
+
report
|
|
1011
1748
|
}
|
|
1012
1749
|
/// # Safety
|
|
1013
1750
|
/// Same requirements as sn_diagnose; all limit/output pointers must be live.
|
|
1014
1751
|
#[unsafe(no_mangle)]
|
|
1015
1752
|
pub unsafe extern "C" fn sn_diagnose_with_limits(
|
|
1016
1753
|
engine: *const Engine,
|
|
1017
|
-
caches: *const
|
|
1754
|
+
caches: *const runtime::Caches,
|
|
1018
1755
|
root: usize,
|
|
1019
1756
|
instance: Value,
|
|
1020
1757
|
status: *mut i32,
|
|
@@ -1022,21 +1759,25 @@ pub unsafe extern "C" fn sn_diagnose_with_limits(
|
|
|
1022
1759
|
outcome: *mut budget::Outcome,
|
|
1023
1760
|
) -> *mut Report {
|
|
1024
1761
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
1025
|
-
let local =
|
|
1762
|
+
let local = runtime::Caches::default();
|
|
1026
1763
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
1027
1764
|
let limits = unsafe { *limits };
|
|
1028
1765
|
let execution = Execution {
|
|
1029
1766
|
engine: unsafe { &*engine },
|
|
1030
1767
|
caches,
|
|
1031
1768
|
memo: memo::Memo::default(),
|
|
1032
|
-
budget: budget
|
|
1769
|
+
budget: caches.budget(limits),
|
|
1033
1770
|
};
|
|
1034
|
-
|
|
1771
|
+
caches.admit(execution.engine, root);
|
|
1772
|
+
let inherited = caches.initial_report();
|
|
1773
|
+
let resumed = inherited.is_some();
|
|
1774
|
+
let mut report = inherited.unwrap_or_else(|| Report {
|
|
1035
1775
|
limits,
|
|
1776
|
+
render_messages: execution.runtime_messages,
|
|
1036
1777
|
..Report::default()
|
|
1037
|
-
};
|
|
1778
|
+
});
|
|
1038
1779
|
let result = if execution.budget.check_deadline() {
|
|
1039
|
-
execution.
|
|
1780
|
+
execution.collect_report(&mut report, root, instance)
|
|
1040
1781
|
} else {
|
|
1041
1782
|
Err(Unsupported)
|
|
1042
1783
|
};
|
|
@@ -1053,24 +1794,16 @@ pub unsafe extern "C" fn sn_diagnose_with_limits(
|
|
|
1053
1794
|
unsafe {
|
|
1054
1795
|
((*outcome).kind, (*outcome).attempted) = hit;
|
|
1055
1796
|
}
|
|
1056
|
-
result
|
|
1797
|
+
result
|
|
1798
|
+
.map(|evaluation| {
|
|
1799
|
+
report.evaluation = if resumed { Some(evaluation) } else { None };
|
|
1800
|
+
Box::into_raw(Box::new(report))
|
|
1801
|
+
})
|
|
1802
|
+
.map_err(|verdict| execution.budget.status(verdict))
|
|
1057
1803
|
}));
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
report
|
|
1062
|
-
}
|
|
1063
|
-
other => {
|
|
1064
|
-
unsafe {
|
|
1065
|
-
*status = if other.is_err() {
|
|
1066
|
-
STATUS_PANIC
|
|
1067
|
-
} else {
|
|
1068
|
-
VERDICT_UNSUPPORTED
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
std::ptr::null_mut()
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1804
|
+
let (report, code) = report_outcome(result);
|
|
1805
|
+
unsafe { *status = code };
|
|
1806
|
+
report
|
|
1074
1807
|
}
|
|
1075
1808
|
/// # Safety
|
|
1076
1809
|
/// Report must be live under the GVL.
|
|
@@ -1082,15 +1815,7 @@ pub unsafe extern "C" fn sn_report_len(report: *const Report) -> usize {
|
|
|
1082
1815
|
/// Report must be live under the GVL.
|
|
1083
1816
|
#[unsafe(no_mangle)]
|
|
1084
1817
|
pub unsafe extern "C" fn sn_report_size(report: *const Report) -> usize {
|
|
1085
|
-
|
|
1086
|
-
std::mem::size_of::<Report>()
|
|
1087
|
-
+ r.errors.capacity() * std::mem::size_of::<Error>()
|
|
1088
|
-
+ r.instance_path.capacity()
|
|
1089
|
-
+ r.schema_path.capacity()
|
|
1090
|
-
+ r.errors
|
|
1091
|
-
.iter()
|
|
1092
|
-
.map(|e| e.instance_path.capacity() + e.schema_path.capacity())
|
|
1093
|
-
.sum::<usize>()
|
|
1818
|
+
std::mem::size_of::<Report>() + unsafe { &*report }.memory_usage()
|
|
1094
1819
|
}
|
|
1095
1820
|
/// # Safety
|
|
1096
1821
|
/// Report must be live, index in bounds, and record a writable pointer.
|
|
@@ -1105,14 +1830,17 @@ pub unsafe extern "C" fn sn_report_record(
|
|
|
1105
1830
|
*record = Record {
|
|
1106
1831
|
keyword: e.keyword.as_ptr(),
|
|
1107
1832
|
keyword_len: e.keyword.len(),
|
|
1108
|
-
instance_path: e.instance_path.as_ptr(),
|
|
1109
|
-
instance_len: e.instance_path.len(),
|
|
1110
|
-
schema_path: e.schema_path.as_ptr(),
|
|
1111
|
-
schema_len: e.schema_path.len(),
|
|
1833
|
+
instance_path: e.instance_path.bytes().as_ptr(),
|
|
1834
|
+
instance_len: e.instance_path.bytes().len(),
|
|
1835
|
+
schema_path: e.schema_path.bytes().as_ptr(),
|
|
1836
|
+
schema_len: e.schema_path.bytes().len(),
|
|
1837
|
+
instance_pointer: e.instance_path.value(),
|
|
1838
|
+
schema_pointer: e.schema_path.value(),
|
|
1112
1839
|
opcode: e.opcode,
|
|
1113
1840
|
operand: e.operand,
|
|
1114
1841
|
instance: e.instance,
|
|
1115
1842
|
detail: e.detail,
|
|
1843
|
+
message: e.message,
|
|
1116
1844
|
}
|
|
1117
1845
|
};
|
|
1118
1846
|
}
|
|
@@ -1120,12 +1848,7 @@ pub unsafe extern "C" fn sn_report_record(
|
|
|
1120
1848
|
/// Report must be live; call only from its synchronous Ruby GC mark callback.
|
|
1121
1849
|
#[unsafe(no_mangle)]
|
|
1122
1850
|
pub unsafe extern "C" fn sn_report_mark(report: *const Report) {
|
|
1123
|
-
|
|
1124
|
-
unsafe {
|
|
1125
|
-
sn_pin(error.operand);
|
|
1126
|
-
sn_pin(error.instance);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1851
|
+
unsafe { &*report }.mark();
|
|
1129
1852
|
}
|
|
1130
1853
|
/// # Safety
|
|
1131
1854
|
/// Report must be owned, returned by sn_diagnose, and freed exactly once.
|
|
@@ -1133,3 +1856,60 @@ pub unsafe extern "C" fn sn_report_mark(report: *const Report) {
|
|
|
1133
1856
|
pub unsafe extern "C" fn sn_report_free(report: *mut Report) {
|
|
1134
1857
|
drop(unsafe { Box::from_raw(report) });
|
|
1135
1858
|
}
|
|
1859
|
+
|
|
1860
|
+
#[cfg(test)]
|
|
1861
|
+
mod tests {
|
|
1862
|
+
use super::*;
|
|
1863
|
+
|
|
1864
|
+
#[test]
|
|
1865
|
+
fn report_seeds_and_results_own_error_paths_limits_and_locations() {
|
|
1866
|
+
let mut report = Report {
|
|
1867
|
+
attempts: 2,
|
|
1868
|
+
failed: true,
|
|
1869
|
+
..Report::default()
|
|
1870
|
+
};
|
|
1871
|
+
report.instance_path.text.push_str("/item");
|
|
1872
|
+
report.schema_path.text.push_str("/allOf/1");
|
|
1873
|
+
report.limits = budget::Limits {
|
|
1874
|
+
enabled: 1,
|
|
1875
|
+
max_errors: 3,
|
|
1876
|
+
..budget::Limits::default()
|
|
1877
|
+
};
|
|
1878
|
+
report.errors.push(Error {
|
|
1879
|
+
keyword: "type",
|
|
1880
|
+
instance_path: pointer::Rendered::Native("/item".into()),
|
|
1881
|
+
schema_path: pointer::Rendered::Native("/allOf/0/type".into()),
|
|
1882
|
+
opcode: OP_TYPE_INTEGER,
|
|
1883
|
+
operand: 0,
|
|
1884
|
+
instance: 0,
|
|
1885
|
+
detail: 0,
|
|
1886
|
+
message: 0,
|
|
1887
|
+
});
|
|
1888
|
+
let mut locations = Tracked::new(Valid);
|
|
1889
|
+
locations.item(4);
|
|
1890
|
+
locations.item(65);
|
|
1891
|
+
report.evaluation = Some(locations);
|
|
1892
|
+
let mut seed = report.continuation(true);
|
|
1893
|
+
assert!(seed.annotations);
|
|
1894
|
+
assert!(seed.evaluation.is_none());
|
|
1895
|
+
assert_eq!(seed.attempts, 2);
|
|
1896
|
+
assert!(seed.failed);
|
|
1897
|
+
assert_eq!(seed.limits.max_errors, 3);
|
|
1898
|
+
seed.instance_path.text.push_str("/child");
|
|
1899
|
+
seed.errors.clear();
|
|
1900
|
+
assert_eq!(report.instance_path.text, "/item");
|
|
1901
|
+
assert_eq!(report.errors.len(), 1);
|
|
1902
|
+
assert!(report.memory_usage() > 0);
|
|
1903
|
+
let mut adopted = Report::default();
|
|
1904
|
+
let mut completed = report.clone();
|
|
1905
|
+
completed.annotations = true;
|
|
1906
|
+
let mut result = adopted.adopt(completed);
|
|
1907
|
+
result.item(8);
|
|
1908
|
+
assert_eq!(result.item_count(), 3);
|
|
1909
|
+
assert_eq!(report.evaluation.as_ref().unwrap().item_count(), 2);
|
|
1910
|
+
assert!(report.evaluation.as_ref().unwrap().contains_item(65));
|
|
1911
|
+
assert!(adopted.evaluation.is_none());
|
|
1912
|
+
assert!(!adopted.annotations);
|
|
1913
|
+
assert_eq!(adopted.attempts, 2);
|
|
1914
|
+
}
|
|
1915
|
+
}
|