schemurai 3.0.1 → 3.1.0
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 +28 -7
- data/ext/schemurai_native/Cargo.lock +59 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +5 -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 +56 -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 +1064 -325
- 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 +8 -6
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/lib.rs +910 -237
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +210 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +113 -101
- 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_limits.rb +65 -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/limited_evaluator.rb +2 -59
- 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 +8 -78
- 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,111 @@ 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
|
}
|
|
716
|
+
// Container entry policy is shared; child traversal stays specific
|
|
717
|
+
// to each container's paths, assertions and annotation coverage.
|
|
718
|
+
let container = match op {
|
|
719
|
+
Op::Array(required, rules) => {
|
|
720
|
+
Some((*required, &rules.header, rules.header_only(), TYPE_ARRAY))
|
|
721
|
+
}
|
|
722
|
+
Op::Object(required, rules) => {
|
|
723
|
+
Some((*required, &rules.header, rules.header_only(), TYPE_OBJECT))
|
|
724
|
+
}
|
|
725
|
+
_ => None,
|
|
726
|
+
};
|
|
727
|
+
if let Some((required, header, header_only, expected_kind)) = container {
|
|
728
|
+
if self.caches.report_continuations_allowed()
|
|
729
|
+
&& !header_only
|
|
730
|
+
&& (kind == TYPE_UNSUPPORTED
|
|
731
|
+
|| self.caches.dynamic_calls()
|
|
732
|
+
|| prior.has_runtime_properties())
|
|
733
|
+
{
|
|
734
|
+
let result = self.caches.container_report(
|
|
735
|
+
node,
|
|
736
|
+
value,
|
|
737
|
+
(source.1, required, expected_kind == TYPE_OBJECT),
|
|
738
|
+
report,
|
|
739
|
+
&prior,
|
|
740
|
+
tracked,
|
|
741
|
+
)?;
|
|
742
|
+
if tracked {
|
|
743
|
+
prior.merge_diagnostics(self, result)?;
|
|
744
|
+
}
|
|
745
|
+
if report.silent && report.failed {
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if header_only && header.runtime_required(self.caches, kind) {
|
|
751
|
+
if report.collect_header(self.caches, header, required, value, source)? {
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if kind == expected_kind && !self.budget.checkpoint() {
|
|
757
|
+
return Err(Unsupported);
|
|
758
|
+
}
|
|
759
|
+
if required != 0 && kind != expected_kind {
|
|
760
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
761
|
+
}
|
|
762
|
+
if kind != expected_kind {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if report.silent {
|
|
766
|
+
let status = match op {
|
|
767
|
+
Op::Array(_, rules) => {
|
|
768
|
+
self.array_tracked(rules, value, depth + 1, active, &mut prior)
|
|
769
|
+
}
|
|
770
|
+
Op::Object(_, rules) => {
|
|
771
|
+
self.object_tracked(rules, value, depth + 1, active, &mut prior)
|
|
772
|
+
}
|
|
773
|
+
_ => unreachable!("container entry"),
|
|
774
|
+
};
|
|
775
|
+
if status == Unsupported {
|
|
776
|
+
return Err(Unsupported);
|
|
777
|
+
}
|
|
778
|
+
report.failed |= status == Invalid;
|
|
779
|
+
if report.failed {
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
196
785
|
match op {
|
|
197
786
|
Op::Unsupported => return Err(Unsupported),
|
|
787
|
+
Op::UnresolvedRef(message) => {
|
|
788
|
+
let keyword = reference_keyword(source.0);
|
|
789
|
+
report.add(self.caches, keyword, (source.0, *message), value, 0)?;
|
|
790
|
+
prior = Tracked::new(Invalid);
|
|
791
|
+
}
|
|
198
792
|
Op::Boolean(false) => {
|
|
199
|
-
report.add("falseSchema", source, value, 0)?;
|
|
793
|
+
report.add(self.caches, "falseSchema", source, value, 0)?;
|
|
200
794
|
prior = Tracked::new(Invalid);
|
|
201
795
|
}
|
|
202
796
|
Op::Boolean(true) => {}
|
|
203
|
-
Op::Type(mask) => {
|
|
204
|
-
|
|
205
|
-
|
|
797
|
+
Op::Type(mask, union) => match self.type_verdict(*mask, *union, kind, value) {
|
|
798
|
+
Unsupported => return Err(Unsupported),
|
|
799
|
+
Valid => {}
|
|
800
|
+
Invalid => {
|
|
801
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
206
802
|
}
|
|
207
|
-
}
|
|
803
|
+
},
|
|
208
804
|
Op::Const(..) | Op::Enum(..) => match self.leaf(op, kind, value, depth) {
|
|
209
805
|
Unsupported => return Err(Unsupported),
|
|
210
806
|
Invalid => report.add(
|
|
807
|
+
self.caches,
|
|
211
808
|
if matches!(op, Op::Const(_)) {
|
|
212
809
|
"const"
|
|
213
810
|
} else {
|
|
@@ -220,30 +817,42 @@ impl Execution<'_> {
|
|
|
220
817
|
Valid => {}
|
|
221
818
|
},
|
|
222
819
|
Op::Number(required, rules) => {
|
|
820
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
821
|
+
rules.runtime_failures(self.caches, *required, value, |keyword| {
|
|
822
|
+
report.add(self.caches, keyword, source, value, 0)
|
|
823
|
+
})?;
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
223
826
|
if *required != 0 && kind & required == 0 {
|
|
224
|
-
report.add("type", source, value, 0)?;
|
|
827
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
225
828
|
}
|
|
226
829
|
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
|
-
}
|
|
830
|
+
rules.visit_failures(self.caches, value, &self.budget, |keyword| {
|
|
831
|
+
report.add(self.caches, keyword, source, value, 0)
|
|
832
|
+
})?;
|
|
242
833
|
}
|
|
243
834
|
}
|
|
244
835
|
Op::String(required, rules) => {
|
|
836
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
837
|
+
rules.runtime_failures(
|
|
838
|
+
self.caches,
|
|
839
|
+
*required,
|
|
840
|
+
value,
|
|
841
|
+
|keyword, frame, rescued| {
|
|
842
|
+
report.add_string(
|
|
843
|
+
self.caches,
|
|
844
|
+
keyword,
|
|
845
|
+
source,
|
|
846
|
+
value,
|
|
847
|
+
frame,
|
|
848
|
+
rescued,
|
|
849
|
+
)
|
|
850
|
+
},
|
|
851
|
+
)?;
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
245
854
|
if *required != 0 && kind != TYPE_STRING {
|
|
246
|
-
report.add("type", source, value, 0)?;
|
|
855
|
+
report.add(self.caches, "type", source, value, 0)?;
|
|
247
856
|
}
|
|
248
857
|
if kind == TYPE_STRING {
|
|
249
858
|
let len = if rules.min.is_some() || rules.max.is_some() {
|
|
@@ -252,29 +861,42 @@ impl Execution<'_> {
|
|
|
252
861
|
0
|
|
253
862
|
};
|
|
254
863
|
if rules.max.is_some_and(|max| len > max) {
|
|
255
|
-
report.add("maxLength", source, value, len)?;
|
|
864
|
+
report.add(self.caches, "maxLength", source, value, len)?;
|
|
256
865
|
}
|
|
257
866
|
if rules.min.is_some_and(|min| len < min) {
|
|
258
|
-
report.add("minLength", source, value, len)?;
|
|
867
|
+
report.add(self.caches, "minLength", source, value, len)?;
|
|
259
868
|
}
|
|
260
869
|
if let Some(pattern) = &rules.pattern {
|
|
261
|
-
match pattern.
|
|
870
|
+
match pattern.assertion(self.caches, value) {
|
|
262
871
|
Unsupported => return Err(Unsupported),
|
|
263
|
-
Invalid =>
|
|
872
|
+
Invalid => {
|
|
873
|
+
let invalid = pattern.invalid(self.caches, value);
|
|
874
|
+
report.add(
|
|
875
|
+
self.caches,
|
|
876
|
+
"pattern",
|
|
877
|
+
source,
|
|
878
|
+
value,
|
|
879
|
+
usize::from(invalid),
|
|
880
|
+
)?;
|
|
881
|
+
if invalid {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
264
885
|
Valid => {}
|
|
265
886
|
}
|
|
266
887
|
}
|
|
267
888
|
if let Some(format) = &rules.format {
|
|
268
889
|
match format.eval(value) {
|
|
269
890
|
Unsupported => return Err(Unsupported),
|
|
270
|
-
Invalid => report.add("format", source, value, 0)?,
|
|
891
|
+
Invalid => report.add(self.caches, "format", source, value, 0)?,
|
|
271
892
|
Valid => {}
|
|
272
893
|
}
|
|
273
894
|
}
|
|
274
895
|
if let Some(content) = &rules.content {
|
|
275
|
-
match content.eval(value) {
|
|
896
|
+
match content.eval(self.caches, value) {
|
|
276
897
|
Unsupported => return Err(Unsupported),
|
|
277
898
|
Invalid => report.add(
|
|
899
|
+
self.caches,
|
|
278
900
|
if content.base64 {
|
|
279
901
|
"contentEncoding"
|
|
280
902
|
} else {
|
|
@@ -289,28 +911,19 @@ impl Execution<'_> {
|
|
|
289
911
|
}
|
|
290
912
|
}
|
|
291
913
|
}
|
|
292
|
-
Op::Array(
|
|
293
|
-
if kind == TYPE_ARRAY && !self.budget.checkpoint() {
|
|
294
|
-
return Err(Unsupported);
|
|
295
|
-
}
|
|
296
|
-
if *required != 0 && kind != TYPE_ARRAY {
|
|
297
|
-
report.add("type", source, value, 0)?;
|
|
298
|
-
}
|
|
299
|
-
if kind != TYPE_ARRAY {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
914
|
+
Op::Array(_, rules) => {
|
|
302
915
|
let mut coverage = Tracked::new(Valid);
|
|
303
916
|
let len = unsafe { sn_len(value) };
|
|
304
917
|
if rules.max.is_some_and(|max| len > max) {
|
|
305
|
-
report.add("maxItems", source, value, len)?;
|
|
918
|
+
report.add(self.caches, "maxItems", source, value, len)?;
|
|
306
919
|
}
|
|
307
920
|
if rules.min.is_some_and(|min| len < min) {
|
|
308
|
-
report.add("minItems", source, value, len)?;
|
|
921
|
+
report.add(self.caches, "minItems", source, value, len)?;
|
|
309
922
|
}
|
|
310
923
|
if rules.unique {
|
|
311
|
-
match
|
|
924
|
+
match self.unique_values(value, depth) {
|
|
312
925
|
Unsupported => return Err(Unsupported),
|
|
313
|
-
Invalid => report.add("uniqueItems", source, value, 0)?,
|
|
926
|
+
Invalid => report.add(self.caches, "uniqueItems", source, value, 0)?,
|
|
314
927
|
Valid => {}
|
|
315
928
|
}
|
|
316
929
|
}
|
|
@@ -321,15 +934,20 @@ impl Execution<'_> {
|
|
|
321
934
|
if tracked {
|
|
322
935
|
coverage.item(index);
|
|
323
936
|
}
|
|
324
|
-
report.scoped(
|
|
325
|
-
self.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
937
|
+
report.scoped(
|
|
938
|
+
self.caches,
|
|
939
|
+
&format!("prefixItems/{index}"),
|
|
940
|
+
Some(index),
|
|
941
|
+
|r| {
|
|
942
|
+
self.collect::<false>(
|
|
943
|
+
r,
|
|
944
|
+
child,
|
|
945
|
+
unsafe { sn_entry(value, index) },
|
|
946
|
+
depth + 1,
|
|
947
|
+
active,
|
|
948
|
+
)
|
|
949
|
+
},
|
|
950
|
+
)?;
|
|
333
951
|
}
|
|
334
952
|
if rules.list {
|
|
335
953
|
for (index, &child) in rules.items.iter().take(len).enumerate() {
|
|
@@ -339,15 +957,20 @@ impl Execution<'_> {
|
|
|
339
957
|
if tracked {
|
|
340
958
|
coverage.item(index);
|
|
341
959
|
}
|
|
342
|
-
report.scoped(
|
|
343
|
-
self.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
960
|
+
report.scoped(
|
|
961
|
+
self.caches,
|
|
962
|
+
&format!("items/{index}"),
|
|
963
|
+
Some(index),
|
|
964
|
+
|r| {
|
|
965
|
+
self.collect::<false>(
|
|
966
|
+
r,
|
|
967
|
+
child,
|
|
968
|
+
unsafe { sn_entry(value, index) },
|
|
969
|
+
depth + 1,
|
|
970
|
+
active,
|
|
971
|
+
)
|
|
972
|
+
},
|
|
973
|
+
)?;
|
|
351
974
|
}
|
|
352
975
|
if let Some(child) = rules.additional {
|
|
353
976
|
for index in rules.items.len()..len {
|
|
@@ -357,20 +980,25 @@ impl Execution<'_> {
|
|
|
357
980
|
if tracked {
|
|
358
981
|
coverage.item(index);
|
|
359
982
|
}
|
|
360
|
-
report.scoped(
|
|
361
|
-
self.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
983
|
+
report.scoped(
|
|
984
|
+
self.caches,
|
|
985
|
+
"additionalItems",
|
|
986
|
+
Some(index),
|
|
987
|
+
|r| {
|
|
988
|
+
self.collect::<false>(
|
|
989
|
+
r,
|
|
990
|
+
child,
|
|
991
|
+
unsafe { sn_entry(value, index) },
|
|
992
|
+
depth + 1,
|
|
993
|
+
active,
|
|
994
|
+
)
|
|
995
|
+
},
|
|
996
|
+
)?;
|
|
369
997
|
}
|
|
370
998
|
}
|
|
371
999
|
} else if let Some(&child) = rules.items.first() {
|
|
372
1000
|
let primitive = match self.nodes[child].as_slice() {
|
|
373
|
-
[Op::Type(mask)]
|
|
1001
|
+
[Op::Type(mask, _)]
|
|
374
1002
|
if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 =>
|
|
375
1003
|
{
|
|
376
1004
|
Some(&[][..])
|
|
@@ -378,6 +1006,7 @@ impl Execution<'_> {
|
|
|
378
1006
|
[Op::Number(_, rules)] => rules.fast_limits.as_deref(),
|
|
379
1007
|
_ => None,
|
|
380
1008
|
};
|
|
1009
|
+
let primitive = primitive.filter(|_| !self.caches.dynamic_calls());
|
|
381
1010
|
let values = if len == 0 {
|
|
382
1011
|
&[]
|
|
383
1012
|
} else {
|
|
@@ -389,9 +1018,12 @@ impl Execution<'_> {
|
|
|
389
1018
|
}
|
|
390
1019
|
_ => None,
|
|
391
1020
|
};
|
|
392
|
-
let pattern = pattern
|
|
1021
|
+
let pattern = pattern
|
|
1022
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls());
|
|
393
1023
|
let number = match self.nodes[child].as_slice() {
|
|
394
|
-
[Op::Number(required, number)]
|
|
1024
|
+
[Op::Number(required, number)]
|
|
1025
|
+
if depth < MAX_EVALUATION_DEPTH && !self.caches.dynamic_calls() =>
|
|
1026
|
+
{
|
|
395
1027
|
Some((*required, number))
|
|
396
1028
|
}
|
|
397
1029
|
_ => None,
|
|
@@ -402,14 +1034,16 @@ impl Execution<'_> {
|
|
|
402
1034
|
}
|
|
403
1035
|
_ => None,
|
|
404
1036
|
};
|
|
405
|
-
let format =
|
|
1037
|
+
let format =
|
|
1038
|
+
format.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls());
|
|
406
1039
|
let content = match self.nodes[child].as_slice() {
|
|
407
1040
|
[Op::String(required, string)] if depth < MAX_EVALUATION_DEPTH => {
|
|
408
1041
|
string.content_only().map(|content| (*required, content))
|
|
409
1042
|
}
|
|
410
1043
|
_ => None,
|
|
411
1044
|
};
|
|
412
|
-
let content = content
|
|
1045
|
+
let content = content
|
|
1046
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls());
|
|
413
1047
|
if pattern.is_some() || format.is_some() || content.is_some() {
|
|
414
1048
|
let start = rules.prefix.len().min(len);
|
|
415
1049
|
if tracked {
|
|
@@ -423,6 +1057,7 @@ impl Execution<'_> {
|
|
|
423
1057
|
// The invalid callback is shared, but each scanner retains
|
|
424
1058
|
// its statically selected parser and guarded byte views.
|
|
425
1059
|
let invalid = |index, item| match report.scoped(
|
|
1060
|
+
self.caches,
|
|
426
1061
|
"items",
|
|
427
1062
|
Some(start + index),
|
|
428
1063
|
|r| self.collect::<false>(r, child, item, depth + 1, active),
|
|
@@ -440,9 +1075,12 @@ impl Execution<'_> {
|
|
|
440
1075
|
(_, Some((required, format)), _) => {
|
|
441
1076
|
format.visit_invalid_items(required, &values[start..], invalid)
|
|
442
1077
|
}
|
|
443
|
-
(_, _, Some((required, content))) =>
|
|
444
|
-
|
|
445
|
-
|
|
1078
|
+
(_, _, Some((required, content))) => content.visit_invalid_items(
|
|
1079
|
+
self.caches,
|
|
1080
|
+
required,
|
|
1081
|
+
&values[start..],
|
|
1082
|
+
invalid,
|
|
1083
|
+
),
|
|
446
1084
|
_ => unreachable!("a scalar item scanner was selected"),
|
|
447
1085
|
};
|
|
448
1086
|
if status != Valid {
|
|
@@ -472,18 +1110,25 @@ impl Execution<'_> {
|
|
|
472
1110
|
_ => n > limit,
|
|
473
1111
|
})
|
|
474
1112
|
} else if let Some((required, number)) = number {
|
|
475
|
-
match number.eval_guarded(
|
|
1113
|
+
match number.eval_guarded(
|
|
1114
|
+
self.caches,
|
|
1115
|
+
required,
|
|
1116
|
+
item,
|
|
1117
|
+
&self.budget,
|
|
1118
|
+
) {
|
|
476
1119
|
Valid => false,
|
|
477
1120
|
Invalid => true,
|
|
478
|
-
Unsupported =>
|
|
1121
|
+
Unsupported => {
|
|
1122
|
+
self.needs_diagnostics(child, item, depth + 1)?
|
|
1123
|
+
}
|
|
479
1124
|
}
|
|
480
1125
|
} else {
|
|
481
|
-
self.needs_diagnostics(child, item, depth + 1
|
|
1126
|
+
self.needs_diagnostics(child, item, depth + 1)?
|
|
482
1127
|
};
|
|
483
1128
|
if !needs {
|
|
484
1129
|
continue;
|
|
485
1130
|
}
|
|
486
|
-
report.scoped("items", Some(index), |r| {
|
|
1131
|
+
report.scoped(self.caches, "items", Some(index), |r| {
|
|
487
1132
|
self.collect::<false>(r, child, item, depth + 1, active)
|
|
488
1133
|
})?;
|
|
489
1134
|
}
|
|
@@ -497,15 +1142,8 @@ impl Execution<'_> {
|
|
|
497
1142
|
};
|
|
498
1143
|
let matches = evaluation::scan_contains(
|
|
499
1144
|
len,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
values[index],
|
|
503
|
-
depth + 1,
|
|
504
|
-
active,
|
|
505
|
-
) {
|
|
506
|
-
Ok(needs) => verdict(!needs),
|
|
507
|
-
Err(status) => status,
|
|
508
|
-
},
|
|
1145
|
+
evaluation::ContainsPolicy::AllMatches,
|
|
1146
|
+
|index| self.trial_status(child, values[index], depth + 1, active),
|
|
509
1147
|
|index| {
|
|
510
1148
|
if tracked {
|
|
511
1149
|
coverage.item(index);
|
|
@@ -514,7 +1152,7 @@ impl Execution<'_> {
|
|
|
514
1152
|
)?;
|
|
515
1153
|
if bounds(matches, Some(rules.min_contains), rules.max_contains) == Invalid
|
|
516
1154
|
{
|
|
517
|
-
report.add("contains", source, value, matches)?;
|
|
1155
|
+
report.add(self.caches, "contains", source, value, matches)?;
|
|
518
1156
|
}
|
|
519
1157
|
}
|
|
520
1158
|
if let Some(child) = rules
|
|
@@ -528,7 +1166,7 @@ impl Execution<'_> {
|
|
|
528
1166
|
if prior.contains_item(index) || coverage.contains_item(index) {
|
|
529
1167
|
continue;
|
|
530
1168
|
}
|
|
531
|
-
report.scoped("unevaluatedItems", Some(index), |r| {
|
|
1169
|
+
report.scoped(self.caches, "unevaluatedItems", Some(index), |r| {
|
|
532
1170
|
self.collect::<false>(
|
|
533
1171
|
r,
|
|
534
1172
|
child,
|
|
@@ -541,33 +1179,24 @@ impl Execution<'_> {
|
|
|
541
1179
|
}
|
|
542
1180
|
}
|
|
543
1181
|
if tracked {
|
|
544
|
-
prior.merge_diagnostics(coverage)?;
|
|
1182
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
545
1183
|
}
|
|
546
1184
|
}
|
|
547
|
-
Op::Object(
|
|
548
|
-
if kind == TYPE_OBJECT && !self.budget.checkpoint() {
|
|
549
|
-
return Err(Unsupported);
|
|
550
|
-
}
|
|
551
|
-
if *required != 0 && kind != TYPE_OBJECT {
|
|
552
|
-
report.add("type", source, value, 0)?;
|
|
553
|
-
}
|
|
554
|
-
if kind != TYPE_OBJECT {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
1185
|
+
Op::Object(_, rules) => {
|
|
557
1186
|
let mut coverage = Tracked::new(Valid);
|
|
558
1187
|
let len = unsafe { sn_len(value) };
|
|
559
1188
|
if rules.max.is_some_and(|max| len > max) {
|
|
560
|
-
report.add("maxProperties", source, value, len)?;
|
|
1189
|
+
report.add(self.caches, "maxProperties", source, value, len)?;
|
|
561
1190
|
}
|
|
562
1191
|
if rules.min.is_some_and(|min| len < min) {
|
|
563
|
-
report.add("minProperties", source, value, len)?;
|
|
1192
|
+
report.add(self.caches, "minProperties", source, value, len)?;
|
|
564
1193
|
}
|
|
565
1194
|
for &name in &rules.required_order {
|
|
566
1195
|
if !self.budget.checkpoint() {
|
|
567
1196
|
return Err(Unsupported);
|
|
568
1197
|
}
|
|
569
|
-
if missing(
|
|
570
|
-
report.add("required", source, name, 0)?;
|
|
1198
|
+
if name.missing(self.caches, value, self.undefined)? {
|
|
1199
|
+
report.add(self.caches, "required", source, name.name, 0)?;
|
|
571
1200
|
}
|
|
572
1201
|
}
|
|
573
1202
|
let mut context = ObjectDiagnostics {
|
|
@@ -621,8 +1250,14 @@ impl Execution<'_> {
|
|
|
621
1250
|
if !self.budget.checkpoint() {
|
|
622
1251
|
return Err(Unsupported);
|
|
623
1252
|
}
|
|
624
|
-
if missing(
|
|
625
|
-
report.add(
|
|
1253
|
+
if required.missing(self.caches, value, self.undefined)? {
|
|
1254
|
+
report.add(
|
|
1255
|
+
self.caches,
|
|
1256
|
+
keyword,
|
|
1257
|
+
(source.0, *name),
|
|
1258
|
+
required.name,
|
|
1259
|
+
0,
|
|
1260
|
+
)?;
|
|
626
1261
|
}
|
|
627
1262
|
}
|
|
628
1263
|
}
|
|
@@ -631,10 +1266,8 @@ impl Execution<'_> {
|
|
|
631
1266
|
continue;
|
|
632
1267
|
}
|
|
633
1268
|
if tracked {
|
|
634
|
-
let result =
|
|
635
|
-
|
|
636
|
-
None,
|
|
637
|
-
|r| {
|
|
1269
|
+
let result =
|
|
1270
|
+
report.schema_name(self.caches, keyword, *name, |r| {
|
|
638
1271
|
self.collect::<true>(
|
|
639
1272
|
r,
|
|
640
1273
|
*child,
|
|
@@ -642,30 +1275,16 @@ impl Execution<'_> {
|
|
|
642
1275
|
depth + 1,
|
|
643
1276
|
active,
|
|
644
1277
|
)
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1278
|
+
})?;
|
|
1279
|
+
if result.status == Valid
|
|
1280
|
+
&& coverage.merge_runtime(self, result) == Unsupported
|
|
1281
|
+
{
|
|
1282
|
+
return Err(Unsupported);
|
|
649
1283
|
}
|
|
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
|
-
)?;
|
|
1284
|
+
} else if self.needs_diagnostics(*child, value, depth + 1)? {
|
|
1285
|
+
report.schema_name(self.caches, keyword, *name, |r| {
|
|
1286
|
+
self.collect::<false>(r, *child, value, depth + 1, active)
|
|
1287
|
+
})?;
|
|
669
1288
|
}
|
|
670
1289
|
}
|
|
671
1290
|
}
|
|
@@ -698,7 +1317,7 @@ impl Execution<'_> {
|
|
|
698
1317
|
}
|
|
699
1318
|
}
|
|
700
1319
|
if tracked {
|
|
701
|
-
prior.merge_diagnostics(coverage)?;
|
|
1320
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
702
1321
|
}
|
|
703
1322
|
}
|
|
704
1323
|
Op::All(nodes) => {
|
|
@@ -707,19 +1326,34 @@ impl Execution<'_> {
|
|
|
707
1326
|
if !self.budget.checkpoint() {
|
|
708
1327
|
return Err(Unsupported);
|
|
709
1328
|
}
|
|
710
|
-
let result =
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1329
|
+
let result =
|
|
1330
|
+
report.scoped(self.caches, &format!("allOf/{index}"), None, |r| {
|
|
1331
|
+
if tracked {
|
|
1332
|
+
self.collect_kind::<true>(
|
|
1333
|
+
r,
|
|
1334
|
+
child,
|
|
1335
|
+
value,
|
|
1336
|
+
depth + 1,
|
|
1337
|
+
active,
|
|
1338
|
+
kind_state,
|
|
1339
|
+
)
|
|
1340
|
+
} else {
|
|
1341
|
+
self.collect_kind::<false>(
|
|
1342
|
+
r,
|
|
1343
|
+
child,
|
|
1344
|
+
value,
|
|
1345
|
+
depth + 1,
|
|
1346
|
+
active,
|
|
1347
|
+
kind_state,
|
|
1348
|
+
)
|
|
1349
|
+
}
|
|
1350
|
+
})?;
|
|
717
1351
|
if tracked {
|
|
718
|
-
coverage.merge_diagnostics(result)?;
|
|
1352
|
+
coverage.merge_diagnostics(self, result)?;
|
|
719
1353
|
}
|
|
720
1354
|
}
|
|
721
1355
|
if tracked {
|
|
722
|
-
prior.merge_diagnostics(coverage)?;
|
|
1356
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
723
1357
|
}
|
|
724
1358
|
}
|
|
725
1359
|
Op::Any(nodes) | Op::One(nodes) => {
|
|
@@ -728,67 +1362,68 @@ impl Execution<'_> {
|
|
|
728
1362
|
} else {
|
|
729
1363
|
evaluation::BranchKind::One
|
|
730
1364
|
};
|
|
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
|
-
};
|
|
1365
|
+
// Trials execute complete scalar checks without retaining
|
|
1366
|
+
// errors or paths; ordinary validity may stop earlier.
|
|
1367
|
+
let result = evaluation::scan_branches(
|
|
1368
|
+
nodes,
|
|
1369
|
+
evaluation::BranchPolicy::Complete,
|
|
1370
|
+
self.caches.dynamic_calls(),
|
|
1371
|
+
|child| self.eval_tracked_kind(child, value, depth + 1, active, kind_state),
|
|
1372
|
+
|coverage, branch| {
|
|
1373
|
+
if coverage.merge_runtime(self, branch) == Unsupported {
|
|
1374
|
+
Err(Unsupported)
|
|
1375
|
+
} else {
|
|
1376
|
+
Ok(())
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
)?;
|
|
752
1380
|
if !branch_kind.accepts(result.matches) {
|
|
753
|
-
report.add(
|
|
1381
|
+
report.add(
|
|
1382
|
+
self.caches,
|
|
1383
|
+
branch_kind.keyword(),
|
|
1384
|
+
source,
|
|
1385
|
+
value,
|
|
1386
|
+
result.matches,
|
|
1387
|
+
)?;
|
|
754
1388
|
} else if tracked {
|
|
755
|
-
prior.merge_diagnostics(result.coverage)?;
|
|
1389
|
+
prior.merge_diagnostics(self, result.coverage)?;
|
|
756
1390
|
}
|
|
757
1391
|
}
|
|
758
1392
|
Op::Not(child) => {
|
|
759
|
-
match self
|
|
1393
|
+
match self
|
|
1394
|
+
.eval_tracked_kind(*child, value, depth + 1, active, kind_state)
|
|
1395
|
+
.status
|
|
1396
|
+
{
|
|
760
1397
|
Unsupported => return Err(Unsupported),
|
|
761
|
-
Valid => report.add("not", source, value, 0)?,
|
|
1398
|
+
Valid => report.add(self.caches, "not", source, value, 0)?,
|
|
762
1399
|
Invalid => {}
|
|
763
1400
|
}
|
|
764
1401
|
}
|
|
765
1402
|
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
|
-
};
|
|
1403
|
+
let condition =
|
|
1404
|
+
self.eval_tracked_kind(*condition, value, depth + 1, active, kind_state);
|
|
777
1405
|
let mut coverage = Tracked::new(Valid);
|
|
778
1406
|
let (child, segment) = match condition.status {
|
|
779
1407
|
Unsupported => return Err(Unsupported),
|
|
780
1408
|
Valid => {
|
|
781
|
-
if tracked {
|
|
782
|
-
|
|
1409
|
+
if tracked && coverage.merge_runtime(self, condition) == Unsupported {
|
|
1410
|
+
return Err(Unsupported);
|
|
783
1411
|
}
|
|
784
1412
|
(yes, "then")
|
|
785
1413
|
}
|
|
786
1414
|
Invalid => (no, "else"),
|
|
787
1415
|
};
|
|
788
1416
|
if let Some(child) = child {
|
|
789
|
-
let result = report.scoped(segment, None, |r| {
|
|
1417
|
+
let result = report.scoped(self.caches, segment, None, |r| {
|
|
790
1418
|
if tracked {
|
|
791
|
-
self.collect_kind::<true>(
|
|
1419
|
+
self.collect_kind::<true>(
|
|
1420
|
+
r,
|
|
1421
|
+
*child,
|
|
1422
|
+
value,
|
|
1423
|
+
depth + 1,
|
|
1424
|
+
active,
|
|
1425
|
+
kind_state,
|
|
1426
|
+
)
|
|
792
1427
|
} else {
|
|
793
1428
|
self.collect_kind::<false>(
|
|
794
1429
|
r,
|
|
@@ -796,20 +1431,24 @@ impl Execution<'_> {
|
|
|
796
1431
|
value,
|
|
797
1432
|
depth + 1,
|
|
798
1433
|
active,
|
|
799
|
-
|
|
1434
|
+
kind_state,
|
|
800
1435
|
)
|
|
801
1436
|
}
|
|
802
1437
|
})?;
|
|
803
1438
|
if tracked {
|
|
804
|
-
coverage.merge_diagnostics(result)?;
|
|
1439
|
+
coverage.merge_diagnostics(self, result)?;
|
|
805
1440
|
}
|
|
806
1441
|
}
|
|
807
1442
|
if tracked {
|
|
808
|
-
prior.merge_diagnostics(coverage)?;
|
|
1443
|
+
prior.merge_diagnostics(self, coverage)?;
|
|
809
1444
|
}
|
|
810
1445
|
}
|
|
811
1446
|
Op::Ref(target) => {
|
|
812
|
-
|
|
1447
|
+
let target = self.resolve_reference(*target, value, active)?;
|
|
1448
|
+
let program = self.programs[node];
|
|
1449
|
+
if RefFrame::contains(active.active, node, value)
|
|
1450
|
+
|| self.caches.reference_active(program, value)
|
|
1451
|
+
{
|
|
813
1452
|
continue;
|
|
814
1453
|
}
|
|
815
1454
|
let frame = RefFrame {
|
|
@@ -817,29 +1456,51 @@ impl Execution<'_> {
|
|
|
817
1456
|
value,
|
|
818
1457
|
parent: active.active,
|
|
819
1458
|
};
|
|
820
|
-
let keyword =
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
_ => "$ref",
|
|
824
|
-
};
|
|
825
|
-
let target = self.reference_target(*target, active);
|
|
1459
|
+
let keyword = reference_keyword(source.0);
|
|
1460
|
+
let reference = self.caches.reference_frame(program, value);
|
|
1461
|
+
let _guard = self.caches.reference_guard(&reference);
|
|
826
1462
|
let active = scope::Context {
|
|
827
1463
|
active: Some(&frame),
|
|
828
1464
|
..active
|
|
829
1465
|
};
|
|
830
|
-
let result = report.scoped(keyword, None, |r| {
|
|
1466
|
+
let result = report.scoped(self.caches, keyword, None, |r| {
|
|
831
1467
|
if tracked {
|
|
832
|
-
self.collect_kind::<true>(
|
|
1468
|
+
self.collect_kind::<true>(
|
|
1469
|
+
r,
|
|
1470
|
+
target,
|
|
1471
|
+
value,
|
|
1472
|
+
depth + 1,
|
|
1473
|
+
active,
|
|
1474
|
+
kind_state,
|
|
1475
|
+
)
|
|
833
1476
|
} else {
|
|
834
|
-
self.collect_kind::<false>(
|
|
1477
|
+
self.collect_kind::<false>(
|
|
1478
|
+
r,
|
|
1479
|
+
target,
|
|
1480
|
+
value,
|
|
1481
|
+
depth + 1,
|
|
1482
|
+
active,
|
|
1483
|
+
kind_state,
|
|
1484
|
+
)
|
|
835
1485
|
}
|
|
836
1486
|
})?;
|
|
837
1487
|
if tracked {
|
|
838
|
-
prior.merge_diagnostics(result)?;
|
|
1488
|
+
prior.merge_diagnostics(self, result)?;
|
|
839
1489
|
}
|
|
840
1490
|
}
|
|
841
1491
|
}
|
|
1492
|
+
self.caches.apply_report_effects(report);
|
|
1493
|
+
if report.silent
|
|
1494
|
+
&& report.failed
|
|
1495
|
+
&& matches!(
|
|
1496
|
+
op,
|
|
1497
|
+
Op::All(_) | Op::Any(_) | Op::One(_) | Op::Not(_) | Op::Conditional(..)
|
|
1498
|
+
)
|
|
1499
|
+
{
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
842
1502
|
}
|
|
1503
|
+
self.caches.apply_report_effects(report);
|
|
843
1504
|
let failed = report.failed;
|
|
844
1505
|
report.failed |= parent_failed;
|
|
845
1506
|
// Diagnostic coverage keeps local attempted locations for subsequent
|
|
@@ -849,33 +1510,61 @@ impl Execution<'_> {
|
|
|
849
1510
|
|
|
850
1511
|
fn needs_diagnostics(
|
|
851
1512
|
&self,
|
|
852
|
-
node: usize,
|
|
1513
|
+
mut node: usize,
|
|
853
1514
|
value: Value,
|
|
854
|
-
depth: usize,
|
|
855
|
-
active: scope::Context<'_>,
|
|
1515
|
+
mut depth: usize,
|
|
856
1516
|
) -> Result<bool, Verdict> {
|
|
857
|
-
|
|
858
|
-
|
|
1517
|
+
loop {
|
|
1518
|
+
if !self.budget.checkpoint() {
|
|
1519
|
+
return Err(Unsupported);
|
|
1520
|
+
}
|
|
1521
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
1522
|
+
return Ok(true);
|
|
1523
|
+
}
|
|
1524
|
+
let [Op::Ref(reference)] = self.nodes[node].as_slice() else {
|
|
1525
|
+
break;
|
|
1526
|
+
};
|
|
1527
|
+
let Some(target) = reference.static_target() else {
|
|
1528
|
+
break;
|
|
1529
|
+
};
|
|
1530
|
+
node = target;
|
|
1531
|
+
depth += 1;
|
|
859
1532
|
}
|
|
860
1533
|
let status = match self.nodes[node].as_slice() {
|
|
861
1534
|
[Op::Number(required, rules)] if depth <= MAX_EVALUATION_DEPTH => {
|
|
862
|
-
|
|
1535
|
+
if self.caches.dynamic_calls()
|
|
1536
|
+
|| self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED))
|
|
1537
|
+
== TYPE_UNSUPPORTED
|
|
1538
|
+
{
|
|
1539
|
+
return Ok(true);
|
|
1540
|
+
}
|
|
1541
|
+
rules.eval_guarded(self.caches, *required, value, &self.budget)
|
|
863
1542
|
}
|
|
864
|
-
[
|
|
865
|
-
|
|
866
|
-
{
|
|
867
|
-
let kind =
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1543
|
+
[
|
|
1544
|
+
op @ (Op::Type(..) | Op::Boolean(_) | Op::String(..) | Op::Const(_) | Op::Enum(_)),
|
|
1545
|
+
] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1546
|
+
let kind = self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED));
|
|
1547
|
+
if (kind == TYPE_UNSUPPORTED
|
|
1548
|
+
&& matches!(
|
|
1549
|
+
op,
|
|
1550
|
+
Op::Type(..) | Op::String(..) | Op::Const(_) | Op::Enum(_)
|
|
1551
|
+
))
|
|
1552
|
+
|| (self.caches.dynamic_calls()
|
|
1553
|
+
&& matches!(op, Op::String(..) | Op::Const(_) | Op::Enum(_)))
|
|
1554
|
+
{
|
|
1555
|
+
// Collection must execute a dynamic predicate once, rather
|
|
1556
|
+
// than once here and again while recording its failure.
|
|
1557
|
+
return Ok(true);
|
|
1558
|
+
}
|
|
1559
|
+
if self.unsupported_kind(node, kind) {
|
|
873
1560
|
Unsupported
|
|
874
1561
|
} else {
|
|
875
1562
|
self.leaf(op, kind, value, depth)
|
|
876
1563
|
}
|
|
877
1564
|
}
|
|
878
|
-
|
|
1565
|
+
// A successful validity/trial scan can omit diagnostic-only work
|
|
1566
|
+
// in descendants. Only complete collection proves their success.
|
|
1567
|
+
_ => return Ok(true),
|
|
879
1568
|
};
|
|
880
1569
|
match status {
|
|
881
1570
|
Valid => Ok(false),
|
|
@@ -904,8 +1593,8 @@ unsafe extern "C" fn object_diagnostic(context: *mut c_void, key: Value, value:
|
|
|
904
1593
|
return Unsupported as i32;
|
|
905
1594
|
}
|
|
906
1595
|
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| {
|
|
1596
|
+
if c.engine.needs_diagnostics(child, item, c.depth)? {
|
|
1597
|
+
report.property(c.engine.caches, segment, key, schema_key, |r| {
|
|
909
1598
|
c.engine.collect::<false>(r, child, item, c.depth, c.active)
|
|
910
1599
|
})?;
|
|
911
1600
|
}
|
|
@@ -934,9 +1623,10 @@ unsafe extern "C" fn object_diagnostic(context: *mut c_void, key: Value, value:
|
|
|
934
1623
|
c.coverage.property(c.engine, key);
|
|
935
1624
|
return Ok(());
|
|
936
1625
|
}
|
|
937
|
-
let evaluated = c.rules.visit_value_rules
|
|
1626
|
+
let evaluated = c.rules.visit_value_rules(
|
|
938
1627
|
c.engine.caches,
|
|
939
1628
|
&c.engine.budget,
|
|
1629
|
+
true,
|
|
940
1630
|
key,
|
|
941
1631
|
None,
|
|
942
1632
|
|segment, name, child| visit(c.report, child, value, segment, name),
|
|
@@ -962,10 +1652,20 @@ pub struct Record {
|
|
|
962
1652
|
instance_len: usize,
|
|
963
1653
|
schema_path: *const u8,
|
|
964
1654
|
schema_len: usize,
|
|
1655
|
+
instance_pointer: Value,
|
|
1656
|
+
schema_pointer: Value,
|
|
965
1657
|
opcode: i32,
|
|
966
1658
|
operand: Value,
|
|
967
1659
|
instance: Value,
|
|
968
1660
|
detail: usize,
|
|
1661
|
+
message: Value,
|
|
1662
|
+
}
|
|
1663
|
+
fn report_outcome(result: std::thread::Result<Result<*mut Report, i32>>) -> (*mut Report, i32) {
|
|
1664
|
+
match result {
|
|
1665
|
+
Ok(Ok(report)) => (report, VERDICT_VALID),
|
|
1666
|
+
Ok(Err(code)) => (std::ptr::null_mut(), code),
|
|
1667
|
+
Err(_) => (std::ptr::null_mut(), STATUS_PANIC),
|
|
1668
|
+
}
|
|
969
1669
|
}
|
|
970
1670
|
/// # Safety
|
|
971
1671
|
/// Engine/status must be live under the GVL; root must be in bounds and
|
|
@@ -973,14 +1673,14 @@ pub struct Record {
|
|
|
973
1673
|
#[unsafe(no_mangle)]
|
|
974
1674
|
pub unsafe extern "C" fn sn_diagnose(
|
|
975
1675
|
engine: *const Engine,
|
|
976
|
-
caches: *const
|
|
1676
|
+
caches: *const runtime::Caches,
|
|
977
1677
|
root: usize,
|
|
978
1678
|
instance: Value,
|
|
979
1679
|
status: *mut i32,
|
|
980
1680
|
) -> *mut Report {
|
|
981
1681
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
982
1682
|
let engine = unsafe { &*engine };
|
|
983
|
-
let local =
|
|
1683
|
+
let local = runtime::Caches::default();
|
|
984
1684
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
985
1685
|
let execution = Execution {
|
|
986
1686
|
engine,
|
|
@@ -988,33 +1688,29 @@ pub unsafe extern "C" fn sn_diagnose(
|
|
|
988
1688
|
memo: memo::Memo::default(),
|
|
989
1689
|
budget: budget::Budget::default(),
|
|
990
1690
|
};
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1691
|
+
caches.admit(engine, root);
|
|
1692
|
+
let inherited = caches.initial_report();
|
|
1693
|
+
let resumed = inherited.is_some();
|
|
1694
|
+
let mut report = inherited.unwrap_or_else(|| Report {
|
|
1695
|
+
render_messages: engine.runtime_messages,
|
|
1696
|
+
..Report::default()
|
|
1697
|
+
});
|
|
1698
|
+
let result = execution
|
|
1699
|
+
.collect_report(&mut report, root, instance)
|
|
1700
|
+
.map_err(|verdict| execution.budget.status(verdict))?;
|
|
1701
|
+
report.evaluation = if resumed { Some(result) } else { None };
|
|
1702
|
+
Ok::<_, i32>(Box::into_raw(Box::new(report)))
|
|
994
1703
|
}));
|
|
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
|
-
}
|
|
1704
|
+
let (report, code) = report_outcome(result);
|
|
1705
|
+
unsafe { *status = code };
|
|
1706
|
+
report
|
|
1011
1707
|
}
|
|
1012
1708
|
/// # Safety
|
|
1013
1709
|
/// Same requirements as sn_diagnose; all limit/output pointers must be live.
|
|
1014
1710
|
#[unsafe(no_mangle)]
|
|
1015
1711
|
pub unsafe extern "C" fn sn_diagnose_with_limits(
|
|
1016
1712
|
engine: *const Engine,
|
|
1017
|
-
caches: *const
|
|
1713
|
+
caches: *const runtime::Caches,
|
|
1018
1714
|
root: usize,
|
|
1019
1715
|
instance: Value,
|
|
1020
1716
|
status: *mut i32,
|
|
@@ -1022,21 +1718,25 @@ pub unsafe extern "C" fn sn_diagnose_with_limits(
|
|
|
1022
1718
|
outcome: *mut budget::Outcome,
|
|
1023
1719
|
) -> *mut Report {
|
|
1024
1720
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
1025
|
-
let local =
|
|
1721
|
+
let local = runtime::Caches::default();
|
|
1026
1722
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
1027
1723
|
let limits = unsafe { *limits };
|
|
1028
1724
|
let execution = Execution {
|
|
1029
1725
|
engine: unsafe { &*engine },
|
|
1030
1726
|
caches,
|
|
1031
1727
|
memo: memo::Memo::default(),
|
|
1032
|
-
budget: budget
|
|
1728
|
+
budget: caches.budget(limits),
|
|
1033
1729
|
};
|
|
1034
|
-
|
|
1730
|
+
caches.admit(execution.engine, root);
|
|
1731
|
+
let inherited = caches.initial_report();
|
|
1732
|
+
let resumed = inherited.is_some();
|
|
1733
|
+
let mut report = inherited.unwrap_or_else(|| Report {
|
|
1035
1734
|
limits,
|
|
1735
|
+
render_messages: execution.runtime_messages,
|
|
1036
1736
|
..Report::default()
|
|
1037
|
-
};
|
|
1737
|
+
});
|
|
1038
1738
|
let result = if execution.budget.check_deadline() {
|
|
1039
|
-
execution.
|
|
1739
|
+
execution.collect_report(&mut report, root, instance)
|
|
1040
1740
|
} else {
|
|
1041
1741
|
Err(Unsupported)
|
|
1042
1742
|
};
|
|
@@ -1053,24 +1753,16 @@ pub unsafe extern "C" fn sn_diagnose_with_limits(
|
|
|
1053
1753
|
unsafe {
|
|
1054
1754
|
((*outcome).kind, (*outcome).attempted) = hit;
|
|
1055
1755
|
}
|
|
1056
|
-
result
|
|
1756
|
+
result
|
|
1757
|
+
.map(|evaluation| {
|
|
1758
|
+
report.evaluation = if resumed { Some(evaluation) } else { None };
|
|
1759
|
+
Box::into_raw(Box::new(report))
|
|
1760
|
+
})
|
|
1761
|
+
.map_err(|verdict| execution.budget.status(verdict))
|
|
1057
1762
|
}));
|
|
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
|
-
}
|
|
1763
|
+
let (report, code) = report_outcome(result);
|
|
1764
|
+
unsafe { *status = code };
|
|
1765
|
+
report
|
|
1074
1766
|
}
|
|
1075
1767
|
/// # Safety
|
|
1076
1768
|
/// Report must be live under the GVL.
|
|
@@ -1082,15 +1774,7 @@ pub unsafe extern "C" fn sn_report_len(report: *const Report) -> usize {
|
|
|
1082
1774
|
/// Report must be live under the GVL.
|
|
1083
1775
|
#[unsafe(no_mangle)]
|
|
1084
1776
|
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>()
|
|
1777
|
+
std::mem::size_of::<Report>() + unsafe { &*report }.memory_usage()
|
|
1094
1778
|
}
|
|
1095
1779
|
/// # Safety
|
|
1096
1780
|
/// Report must be live, index in bounds, and record a writable pointer.
|
|
@@ -1105,14 +1789,17 @@ pub unsafe extern "C" fn sn_report_record(
|
|
|
1105
1789
|
*record = Record {
|
|
1106
1790
|
keyword: e.keyword.as_ptr(),
|
|
1107
1791
|
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(),
|
|
1792
|
+
instance_path: e.instance_path.bytes().as_ptr(),
|
|
1793
|
+
instance_len: e.instance_path.bytes().len(),
|
|
1794
|
+
schema_path: e.schema_path.bytes().as_ptr(),
|
|
1795
|
+
schema_len: e.schema_path.bytes().len(),
|
|
1796
|
+
instance_pointer: e.instance_path.value(),
|
|
1797
|
+
schema_pointer: e.schema_path.value(),
|
|
1112
1798
|
opcode: e.opcode,
|
|
1113
1799
|
operand: e.operand,
|
|
1114
1800
|
instance: e.instance,
|
|
1115
1801
|
detail: e.detail,
|
|
1802
|
+
message: e.message,
|
|
1116
1803
|
}
|
|
1117
1804
|
};
|
|
1118
1805
|
}
|
|
@@ -1120,12 +1807,7 @@ pub unsafe extern "C" fn sn_report_record(
|
|
|
1120
1807
|
/// Report must be live; call only from its synchronous Ruby GC mark callback.
|
|
1121
1808
|
#[unsafe(no_mangle)]
|
|
1122
1809
|
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
|
-
}
|
|
1810
|
+
unsafe { &*report }.mark();
|
|
1129
1811
|
}
|
|
1130
1812
|
/// # Safety
|
|
1131
1813
|
/// Report must be owned, returned by sn_diagnose, and freed exactly once.
|
|
@@ -1133,3 +1815,60 @@ pub unsafe extern "C" fn sn_report_mark(report: *const Report) {
|
|
|
1133
1815
|
pub unsafe extern "C" fn sn_report_free(report: *mut Report) {
|
|
1134
1816
|
drop(unsafe { Box::from_raw(report) });
|
|
1135
1817
|
}
|
|
1818
|
+
|
|
1819
|
+
#[cfg(test)]
|
|
1820
|
+
mod tests {
|
|
1821
|
+
use super::*;
|
|
1822
|
+
|
|
1823
|
+
#[test]
|
|
1824
|
+
fn report_seeds_and_results_own_error_paths_limits_and_locations() {
|
|
1825
|
+
let mut report = Report {
|
|
1826
|
+
attempts: 2,
|
|
1827
|
+
failed: true,
|
|
1828
|
+
..Report::default()
|
|
1829
|
+
};
|
|
1830
|
+
report.instance_path.text.push_str("/item");
|
|
1831
|
+
report.schema_path.text.push_str("/allOf/1");
|
|
1832
|
+
report.limits = budget::Limits {
|
|
1833
|
+
enabled: 1,
|
|
1834
|
+
max_errors: 3,
|
|
1835
|
+
..budget::Limits::default()
|
|
1836
|
+
};
|
|
1837
|
+
report.errors.push(Error {
|
|
1838
|
+
keyword: "type",
|
|
1839
|
+
instance_path: pointer::Rendered::Native("/item".into()),
|
|
1840
|
+
schema_path: pointer::Rendered::Native("/allOf/0/type".into()),
|
|
1841
|
+
opcode: OP_TYPE_INTEGER,
|
|
1842
|
+
operand: 0,
|
|
1843
|
+
instance: 0,
|
|
1844
|
+
detail: 0,
|
|
1845
|
+
message: 0,
|
|
1846
|
+
});
|
|
1847
|
+
let mut locations = Tracked::new(Valid);
|
|
1848
|
+
locations.item(4);
|
|
1849
|
+
locations.item(65);
|
|
1850
|
+
report.evaluation = Some(locations);
|
|
1851
|
+
let mut seed = report.continuation(true);
|
|
1852
|
+
assert!(seed.annotations);
|
|
1853
|
+
assert!(seed.evaluation.is_none());
|
|
1854
|
+
assert_eq!(seed.attempts, 2);
|
|
1855
|
+
assert!(seed.failed);
|
|
1856
|
+
assert_eq!(seed.limits.max_errors, 3);
|
|
1857
|
+
seed.instance_path.text.push_str("/child");
|
|
1858
|
+
seed.errors.clear();
|
|
1859
|
+
assert_eq!(report.instance_path.text, "/item");
|
|
1860
|
+
assert_eq!(report.errors.len(), 1);
|
|
1861
|
+
assert!(report.memory_usage() > 0);
|
|
1862
|
+
let mut adopted = Report::default();
|
|
1863
|
+
let mut completed = report.clone();
|
|
1864
|
+
completed.annotations = true;
|
|
1865
|
+
let mut result = adopted.adopt(completed);
|
|
1866
|
+
result.item(8);
|
|
1867
|
+
assert_eq!(result.item_count(), 3);
|
|
1868
|
+
assert_eq!(report.evaluation.as_ref().unwrap().item_count(), 2);
|
|
1869
|
+
assert!(report.evaluation.as_ref().unwrap().contains_item(65));
|
|
1870
|
+
assert!(adopted.evaluation.is_none());
|
|
1871
|
+
assert!(!adopted.annotations);
|
|
1872
|
+
assert_eq!(adopted.attempts, 2);
|
|
1873
|
+
}
|
|
1874
|
+
}
|