schemurai 3.0.1 → 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 +48 -1
- data/ext/schemurai_native/Cargo.toml +3 -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 +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 +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 +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 +154 -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_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 +24 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//! Content validation borrows Ruby string storage. Base64 decoding uses bounded
|
|
2
|
-
//! stack scratch; JSON scanning never constructs a tree.
|
|
2
|
+
//! stack scratch; native JSON scanning never constructs a tree. Other source
|
|
3
|
+
//! encodings request Ruby parsing after Rust evaluation returns.
|
|
3
4
|
use super::*;
|
|
4
5
|
use std::mem::MaybeUninit;
|
|
5
6
|
|
|
@@ -14,23 +15,28 @@ pub(super) struct Content {
|
|
|
14
15
|
pub(super) json: bool,
|
|
15
16
|
}
|
|
16
17
|
impl Content {
|
|
17
|
-
pub(super) fn eval_items(&self, required: i32, values: &[Value]) -> Verdict {
|
|
18
|
-
self.visit_invalid_items(required, values, |_, _| Invalid)
|
|
19
|
-
}
|
|
20
18
|
pub(super) fn visit_invalid_items(
|
|
21
19
|
&self,
|
|
20
|
+
caches: &runtime::Caches,
|
|
22
21
|
required: i32,
|
|
23
22
|
values: &[Value],
|
|
24
23
|
invalid: impl FnMut(usize, Value) -> Verdict,
|
|
25
24
|
) -> Verdict {
|
|
26
25
|
match (self.base64, self.json) {
|
|
27
|
-
(true, true) => Self::visit_items_impl::<true, true>(required, values, invalid),
|
|
28
|
-
(true, false) =>
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
(true, true) => Self::visit_items_impl::<true, true>(caches, required, values, invalid),
|
|
27
|
+
(true, false) => {
|
|
28
|
+
Self::visit_items_impl::<true, false>(caches, required, values, invalid)
|
|
29
|
+
}
|
|
30
|
+
(false, true) => {
|
|
31
|
+
Self::visit_items_impl::<false, true>(caches, required, values, invalid)
|
|
32
|
+
}
|
|
33
|
+
(false, false) => {
|
|
34
|
+
Self::visit_items_impl::<false, false>(caches, required, values, invalid)
|
|
35
|
+
}
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
fn visit_items_impl<const BASE64: bool, const JSON: bool>(
|
|
39
|
+
caches: &runtime::Caches,
|
|
34
40
|
required: i32,
|
|
35
41
|
values: &[Value],
|
|
36
42
|
mut invalid: impl FnMut(usize, Value) -> Verdict,
|
|
@@ -45,9 +51,15 @@ impl Content {
|
|
|
45
51
|
} else {
|
|
46
52
|
unsafe { sn_format_item_view(value, data.as_mut_ptr(), len.as_mut_ptr()) }
|
|
47
53
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
if kind == -TYPE_UNSUPPORTED {
|
|
55
|
+
let status = invalid(index, value);
|
|
56
|
+
if status != Valid {
|
|
57
|
+
return status;
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
let result = if JSON && !BASE64 && kind == ENCODING_OTHER {
|
|
62
|
+
caches.json(value)
|
|
51
63
|
} else if kind < 0 {
|
|
52
64
|
verdict(required == 0)
|
|
53
65
|
} else {
|
|
@@ -55,12 +67,12 @@ impl Content {
|
|
|
55
67
|
let bytes = unsafe { byte_view(data.assume_init(), len.assume_init()) };
|
|
56
68
|
if JSON && !BASE64 {
|
|
57
69
|
if kind == ENCODING_ASCII && !bytes.is_ascii() {
|
|
58
|
-
|
|
70
|
+
caches.json(value)
|
|
59
71
|
} else {
|
|
60
72
|
json(bytes)
|
|
61
73
|
}
|
|
62
74
|
} else {
|
|
63
|
-
Self::eval_impl::<true, BASE64, JSON>(value, bytes, 0)
|
|
75
|
+
Self::eval_impl::<true, BASE64, JSON>(caches, value, bytes, 0)
|
|
64
76
|
}
|
|
65
77
|
};
|
|
66
78
|
match result {
|
|
@@ -76,26 +88,40 @@ impl Content {
|
|
|
76
88
|
}
|
|
77
89
|
Valid
|
|
78
90
|
}
|
|
79
|
-
pub(super) fn eval(&self, value: Value) -> Verdict {
|
|
80
|
-
self.eval_selected::<false>(value, &[], 0)
|
|
91
|
+
pub(super) fn eval(&self, caches: &runtime::Caches, value: Value) -> Verdict {
|
|
92
|
+
self.eval_selected::<false>(caches, value, &[], 0)
|
|
81
93
|
}
|
|
82
|
-
pub(super) fn eval_borrowed(
|
|
83
|
-
self
|
|
94
|
+
pub(super) fn eval_borrowed(
|
|
95
|
+
&self,
|
|
96
|
+
caches: &runtime::Caches,
|
|
97
|
+
value: Value,
|
|
98
|
+
bytes: &[u8],
|
|
99
|
+
encoding: i32,
|
|
100
|
+
) -> Verdict {
|
|
101
|
+
self.eval_selected::<true>(caches, value, bytes, encoding)
|
|
84
102
|
}
|
|
85
103
|
fn eval_selected<const BORROWED: bool>(
|
|
86
104
|
&self,
|
|
105
|
+
caches: &runtime::Caches,
|
|
87
106
|
value: Value,
|
|
88
107
|
bytes: &[u8],
|
|
89
108
|
encoding: i32,
|
|
90
109
|
) -> Verdict {
|
|
91
110
|
match (self.base64, self.json) {
|
|
92
|
-
(true, true) => Self::eval_impl::<BORROWED, true, true>(value, bytes, encoding),
|
|
93
|
-
(true, false) =>
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
(true, true) => Self::eval_impl::<BORROWED, true, true>(caches, value, bytes, encoding),
|
|
112
|
+
(true, false) => {
|
|
113
|
+
Self::eval_impl::<BORROWED, true, false>(caches, value, bytes, encoding)
|
|
114
|
+
}
|
|
115
|
+
(false, true) => {
|
|
116
|
+
Self::eval_impl::<BORROWED, false, true>(caches, value, bytes, encoding)
|
|
117
|
+
}
|
|
118
|
+
(false, false) => {
|
|
119
|
+
Self::eval_impl::<BORROWED, false, false>(caches, value, bytes, encoding)
|
|
120
|
+
}
|
|
96
121
|
}
|
|
97
122
|
}
|
|
98
123
|
fn eval_impl<const BORROWED: bool, const BASE64: bool, const JSON: bool>(
|
|
124
|
+
caches: &runtime::Caches,
|
|
99
125
|
value: Value,
|
|
100
126
|
bytes: &[u8],
|
|
101
127
|
encoding: i32,
|
|
@@ -142,7 +168,7 @@ impl Content {
|
|
|
142
168
|
_ => unsafe { sn_json_encoding(value) },
|
|
143
169
|
};
|
|
144
170
|
if encoding == ENCODING_OTHER || (encoding == ENCODING_ASCII && !bytes.is_ascii()) {
|
|
145
|
-
return
|
|
171
|
+
return caches.json(value);
|
|
146
172
|
}
|
|
147
173
|
}
|
|
148
174
|
if !JSON {
|
|
@@ -161,6 +187,7 @@ fn decode_json_medium(bytes: &[u8], length: usize) -> Verdict {
|
|
|
161
187
|
decode_json_turbo(bytes, length, &mut scratch)
|
|
162
188
|
}
|
|
163
189
|
|
|
190
|
+
// Keep the 8 KiB scratch frame out of shorter decoded-content paths.
|
|
164
191
|
#[inline(never)]
|
|
165
192
|
fn decode_json_direct(bytes: &[u8], length: usize) -> Verdict {
|
|
166
193
|
let mut scratch = [0; DIRECT_BYTES + 2];
|
|
@@ -228,6 +255,9 @@ trait Input {
|
|
|
228
255
|
}
|
|
229
256
|
}
|
|
230
257
|
impl Input for &[u8] {
|
|
258
|
+
// Retain byte/take/whitespace specialization together: removing their
|
|
259
|
+
// hints changed parser register/state handling and slowed numeric/literal
|
|
260
|
+
// and nested JSON controls. See benchmark/README.md's inline audit.
|
|
231
261
|
#[inline(always)]
|
|
232
262
|
fn byte(&self, at: usize) -> Option<u8> {
|
|
233
263
|
self.get(at).copied()
|
|
@@ -327,7 +357,6 @@ impl Input for &[u8] {
|
|
|
327
357
|
.count()
|
|
328
358
|
}
|
|
329
359
|
}
|
|
330
|
-
#[inline]
|
|
331
360
|
fn string_special_word(bytes: [u8; 8]) -> u64 {
|
|
332
361
|
let word = u64::from_le_bytes(bytes);
|
|
333
362
|
let quote = word ^ 0x2222_2222_2222_2222;
|
|
@@ -398,11 +427,9 @@ impl BufferedBase64<'_> {
|
|
|
398
427
|
}
|
|
399
428
|
}
|
|
400
429
|
impl Input for BufferedBase64<'_> {
|
|
401
|
-
#[inline(always)]
|
|
402
430
|
fn byte(&self, _at: usize) -> Option<u8> {
|
|
403
431
|
self.decoded[..self.len].get(self.at).copied()
|
|
404
432
|
}
|
|
405
|
-
#[inline(always)]
|
|
406
433
|
fn advance(&mut self) {
|
|
407
434
|
self.at += 1;
|
|
408
435
|
if self.at == self.len {
|
|
@@ -432,15 +459,14 @@ struct Parser<I> {
|
|
|
432
459
|
at: usize,
|
|
433
460
|
}
|
|
434
461
|
impl<I: Input> Parser<I> {
|
|
435
|
-
#[inline(always)]
|
|
436
462
|
fn peek(&self) -> Option<u8> {
|
|
437
463
|
self.input.byte(self.at)
|
|
438
464
|
}
|
|
439
|
-
#[inline(always)]
|
|
440
465
|
fn advance(&mut self) {
|
|
441
466
|
self.at += 1;
|
|
442
467
|
self.input.advance();
|
|
443
468
|
}
|
|
469
|
+
// Part of the measured byte/take/whitespace specialization above.
|
|
444
470
|
#[inline(always)]
|
|
445
471
|
fn take(&mut self, byte: u8) -> bool {
|
|
446
472
|
if self.peek() == Some(byte) {
|
|
@@ -450,12 +476,15 @@ impl<I: Input> Parser<I> {
|
|
|
450
476
|
false
|
|
451
477
|
}
|
|
452
478
|
}
|
|
479
|
+
// Keep whitespace state visible to value/container dispatch (inline audit).
|
|
453
480
|
#[inline(always)]
|
|
454
481
|
fn whitespace(&mut self) {
|
|
455
482
|
while matches!(self.peek(), Some(b' ' | b'\t' | b'\r' | b'\n')) {
|
|
456
483
|
self.advance();
|
|
457
484
|
}
|
|
458
485
|
}
|
|
486
|
+
// Paired JSON controls improved 5–6% with dispatch in the callers; see
|
|
487
|
+
// benchmark/README.md, "Full audit after JSON value dispatch inlining".
|
|
459
488
|
#[inline(always)]
|
|
460
489
|
fn value(&mut self, depth: usize) -> bool {
|
|
461
490
|
self.whitespace();
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
//! Array report collection through C-owned handles, outside native schema frames.
|
|
2
|
+
use super::*;
|
|
3
|
+
|
|
4
|
+
/// # Safety
|
|
5
|
+
/// Report is live, immutable and rooted by C; the returned clone is owned by C.
|
|
6
|
+
#[unsafe(no_mangle)]
|
|
7
|
+
pub unsafe extern "C" fn sn_report_clone(report: *const Report) -> *mut Report {
|
|
8
|
+
Box::into_raw(Box::new(unsafe { &*report }.clone()))
|
|
9
|
+
}
|
|
10
|
+
/// # Safety
|
|
11
|
+
/// Report is the live callback collection, immutable and rooted by C.
|
|
12
|
+
#[unsafe(no_mangle)]
|
|
13
|
+
pub unsafe extern "C" fn sn_report_callback_seed(report: *const Report) -> *mut Report {
|
|
14
|
+
Box::into_raw(Box::new(unsafe { &*report }.continuation(true)))
|
|
15
|
+
}
|
|
16
|
+
/// # Safety
|
|
17
|
+
/// Report is live and C-rooted. Keyword is a valid library-owned UTF-8 segment.
|
|
18
|
+
/// The returned seed is owned by C and freed with sn_report_free.
|
|
19
|
+
#[unsafe(no_mangle)]
|
|
20
|
+
pub unsafe extern "C" fn sn_array_report_child(
|
|
21
|
+
report: *const Report,
|
|
22
|
+
index: usize,
|
|
23
|
+
keyword: *const u8,
|
|
24
|
+
len: usize,
|
|
25
|
+
child: usize,
|
|
26
|
+
trial: i32,
|
|
27
|
+
) -> *mut Report {
|
|
28
|
+
let mut seed = if trial != 0 {
|
|
29
|
+
Report {
|
|
30
|
+
silent: true,
|
|
31
|
+
annotations: true,
|
|
32
|
+
..Report::default()
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
unsafe { &*report }.continuation(false)
|
|
36
|
+
};
|
|
37
|
+
if trial == 0 {
|
|
38
|
+
seed.instance_path.index(index);
|
|
39
|
+
seed.schema_path.push_str("/");
|
|
40
|
+
seed.schema_path.push_str(
|
|
41
|
+
std::str::from_utf8(unsafe { byte_view(keyword, len) }).expect("array keyword"),
|
|
42
|
+
);
|
|
43
|
+
if child != usize::MAX {
|
|
44
|
+
seed.schema_path.index(child);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
Box::into_raw(Box::new(seed))
|
|
48
|
+
}
|
|
49
|
+
/// # Safety
|
|
50
|
+
/// Report/engine are live and rooted, node contains the current Array instruction,
|
|
51
|
+
/// keyword is one of the library's array diagnostic IDs, and frame is C-rooted.
|
|
52
|
+
#[unsafe(no_mangle)]
|
|
53
|
+
pub unsafe extern "C" fn sn_array_report_error(
|
|
54
|
+
report: *const Report,
|
|
55
|
+
engine: *const Engine,
|
|
56
|
+
node: usize,
|
|
57
|
+
instance: Value,
|
|
58
|
+
keyword: i32,
|
|
59
|
+
frame: Value,
|
|
60
|
+
) -> *mut Report {
|
|
61
|
+
let keyword = match keyword {
|
|
62
|
+
0 => "type",
|
|
63
|
+
1 => "maxItems",
|
|
64
|
+
2 => "minItems",
|
|
65
|
+
3 => "uniqueItems",
|
|
66
|
+
4 => "contains",
|
|
67
|
+
_ => unreachable!(),
|
|
68
|
+
};
|
|
69
|
+
let seed = unsafe { &*report }.container_error(
|
|
70
|
+
unsafe { &*engine },
|
|
71
|
+
node,
|
|
72
|
+
instance,
|
|
73
|
+
keyword,
|
|
74
|
+
frame,
|
|
75
|
+
false,
|
|
76
|
+
);
|
|
77
|
+
Box::into_raw(Box::new(seed))
|
|
78
|
+
}
|
|
79
|
+
/// # Safety
|
|
80
|
+
/// Collector report is live and exclusively borrowed. C marks its immutable
|
|
81
|
+
/// seed and all completed results instead while this mutation is in progress.
|
|
82
|
+
#[unsafe(no_mangle)]
|
|
83
|
+
pub unsafe extern "C" fn sn_array_report_adopt(
|
|
84
|
+
report: *mut Report,
|
|
85
|
+
completed: *const Report,
|
|
86
|
+
) -> i32 {
|
|
87
|
+
unsafe { &mut *report }
|
|
88
|
+
.adopt(unsafe { &*completed }.clone())
|
|
89
|
+
.status as i32
|
|
90
|
+
}
|
|
91
|
+
/// # Safety
|
|
92
|
+
/// Report is live and holds an active array accumulator; C uses the same mutation
|
|
93
|
+
/// root guard as sn_array_report_adopt. Index is produced by library traversal.
|
|
94
|
+
#[unsafe(no_mangle)]
|
|
95
|
+
pub unsafe extern "C" fn sn_array_report_record(report: *mut Report, index: usize) {
|
|
96
|
+
unsafe { &mut *report }
|
|
97
|
+
.evaluation
|
|
98
|
+
.as_mut()
|
|
99
|
+
.expect("array locations")
|
|
100
|
+
.item(index);
|
|
101
|
+
}
|
|
102
|
+
/// # Safety
|
|
103
|
+
/// Report is live and immutable during this call; index is a library index.
|
|
104
|
+
#[unsafe(no_mangle)]
|
|
105
|
+
pub unsafe extern "C" fn sn_array_report_contains(
|
|
106
|
+
report: *const Report,
|
|
107
|
+
index: usize,
|
|
108
|
+
prior: i32,
|
|
109
|
+
) -> i32 {
|
|
110
|
+
let report = unsafe { &*report };
|
|
111
|
+
i32::from(
|
|
112
|
+
if prior != 0 {
|
|
113
|
+
&report.container_prior
|
|
114
|
+
} else {
|
|
115
|
+
&report.evaluation
|
|
116
|
+
}
|
|
117
|
+
.as_ref()
|
|
118
|
+
.is_some_and(|locations| locations.contains_item(index)),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
/// # Safety
|
|
122
|
+
/// Report is live and immutable during this call.
|
|
123
|
+
#[unsafe(no_mangle)]
|
|
124
|
+
pub unsafe extern "C" fn sn_report_silent(report: *const Report) -> i32 {
|
|
125
|
+
i32::from(unsafe { &*report }.silent)
|
|
126
|
+
}
|
|
127
|
+
/// # Safety
|
|
128
|
+
/// Report is live, complete and immutable during this call.
|
|
129
|
+
#[unsafe(no_mangle)]
|
|
130
|
+
pub unsafe extern "C" fn sn_report_verdict(report: *const Report) -> i32 {
|
|
131
|
+
unsafe { &*report }
|
|
132
|
+
.evaluation
|
|
133
|
+
.as_ref()
|
|
134
|
+
.expect("completed report")
|
|
135
|
+
.status as i32
|
|
136
|
+
}
|
|
137
|
+
/// # Safety
|
|
138
|
+
/// Report is live with an active array accumulator and C guards mutation as for
|
|
139
|
+
/// sn_array_report_adopt. Only the library completes an array traversal.
|
|
140
|
+
#[unsafe(no_mangle)]
|
|
141
|
+
pub unsafe extern "C" fn sn_array_report_finish(report: *mut Report, valid: i32) {
|
|
142
|
+
let report = unsafe { &mut *report };
|
|
143
|
+
report.container_prior = None;
|
|
144
|
+
if valid == 0 {
|
|
145
|
+
report.failed = true;
|
|
146
|
+
report.evaluation = Some(Tracked::new(Invalid));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
#[cfg(test)]
|
|
150
|
+
mod tests {
|
|
151
|
+
use super::*;
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn runtime_effects_keep_the_receiving_path_and_detect_silent_failure() {
|
|
155
|
+
let mut report = Report {
|
|
156
|
+
attempts: 2,
|
|
157
|
+
..Report::default()
|
|
158
|
+
};
|
|
159
|
+
report.instance_path.index(7);
|
|
160
|
+
report.schema_path.push_str("/items");
|
|
161
|
+
let before = report.continuation(true);
|
|
162
|
+
assert!(before.effect_since(&before).is_none());
|
|
163
|
+
let mut completed = before.clone();
|
|
164
|
+
completed.reserve_error().unwrap();
|
|
165
|
+
completed.errors.push(Error {
|
|
166
|
+
keyword: "type",
|
|
167
|
+
instance_path: pointer::Rendered::Native("/7/0".into()),
|
|
168
|
+
schema_path: pointer::Rendered::Native("/items/items/type".into()),
|
|
169
|
+
opcode: OP_TYPE_INTEGER,
|
|
170
|
+
operand: 0,
|
|
171
|
+
instance: 0,
|
|
172
|
+
detail: 0,
|
|
173
|
+
message: 0,
|
|
174
|
+
});
|
|
175
|
+
let effect = completed.effect_since(&before).unwrap();
|
|
176
|
+
report.apply_effect(effect);
|
|
177
|
+
assert_eq!(report.attempts, 3);
|
|
178
|
+
assert!(report.failed);
|
|
179
|
+
assert_eq!(report.instance_path.text, "/7");
|
|
180
|
+
assert_eq!(report.schema_path.text, "/items");
|
|
181
|
+
assert_eq!(report.errors.len(), 1);
|
|
182
|
+
assert_eq!(report.errors[0].instance_path.bytes(), b"/7/0");
|
|
183
|
+
assert!(!report.annotations);
|
|
184
|
+
let silent = Report {
|
|
185
|
+
silent: true,
|
|
186
|
+
..Report::default()
|
|
187
|
+
};
|
|
188
|
+
let mut completed = silent.clone();
|
|
189
|
+
completed.failed = true;
|
|
190
|
+
let effect = completed.effect_since(&silent).unwrap();
|
|
191
|
+
assert!(effect.failed);
|
|
192
|
+
assert_eq!(effect.attempts, 0);
|
|
193
|
+
assert!(effect.errors.is_empty());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn collection_reset_replaces_prior_errors_and_counts_without_exporting_trial_policy() {
|
|
198
|
+
let before = Report {
|
|
199
|
+
attempts: 2,
|
|
200
|
+
failed: true,
|
|
201
|
+
..Report::default()
|
|
202
|
+
};
|
|
203
|
+
let mut completed = before.clone();
|
|
204
|
+
completed.instance_path.index(7);
|
|
205
|
+
completed.clear_collection(true);
|
|
206
|
+
assert_eq!(completed.attempts, 0);
|
|
207
|
+
assert!(!completed.failed);
|
|
208
|
+
assert!(completed.silent);
|
|
209
|
+
let effect = completed.effect_since(&before).unwrap();
|
|
210
|
+
let mut receiver = before.container_continuation(true, &Tracked::new(Valid));
|
|
211
|
+
receiver.instance_path.index(5);
|
|
212
|
+
receiver.apply_effect(effect);
|
|
213
|
+
assert!(receiver.annotations);
|
|
214
|
+
assert!(receiver.silent);
|
|
215
|
+
assert_eq!(receiver.attempts, 0);
|
|
216
|
+
assert!(!receiver.failed);
|
|
217
|
+
assert!(receiver.instance_path.text.is_empty());
|
|
218
|
+
assert_eq!(receiver.collection_reset, 1);
|
|
219
|
+
assert!(receiver.evaluation.is_some());
|
|
220
|
+
let mut outer = before.clone();
|
|
221
|
+
let trial = outer.continuation(true);
|
|
222
|
+
let mut trial_result = trial.clone();
|
|
223
|
+
trial_result.clear_collection(true);
|
|
224
|
+
assert!(!outer.same_collection_policy(&trial_result));
|
|
225
|
+
outer.apply_effect(Report {
|
|
226
|
+
attempts: 1,
|
|
227
|
+
failed: true,
|
|
228
|
+
..Report::default()
|
|
229
|
+
});
|
|
230
|
+
assert_eq!(outer.attempts, 3);
|
|
231
|
+
assert!(!outer.silent);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[test]
|
|
235
|
+
fn item_results_preserve_accumulators_paths_and_independent_trial_policy() {
|
|
236
|
+
let mut base = Report {
|
|
237
|
+
attempts: 1,
|
|
238
|
+
..Report::default()
|
|
239
|
+
};
|
|
240
|
+
base.instance_path.text.push_str("/outer");
|
|
241
|
+
base.schema_path.text.push_str("/allOf/1");
|
|
242
|
+
let mut prior = Tracked::new(Valid);
|
|
243
|
+
prior.item(65);
|
|
244
|
+
let mut report = base.container_continuation(true, &prior);
|
|
245
|
+
unsafe {
|
|
246
|
+
sn_array_report_record(&mut report, 4);
|
|
247
|
+
}
|
|
248
|
+
assert_eq!(unsafe { sn_array_report_contains(&report, 65, 1) }, 1);
|
|
249
|
+
assert_eq!(unsafe { sn_array_report_contains(&report, 4, 0) }, 1);
|
|
250
|
+
assert_eq!(unsafe { sn_array_report_contains(&report, 4, 1) }, 0);
|
|
251
|
+
let keyword = b"prefixItems";
|
|
252
|
+
let mut completed = unsafe {
|
|
253
|
+
Box::from_raw(sn_array_report_child(
|
|
254
|
+
&report,
|
|
255
|
+
4,
|
|
256
|
+
keyword.as_ptr(),
|
|
257
|
+
keyword.len(),
|
|
258
|
+
4,
|
|
259
|
+
0,
|
|
260
|
+
))
|
|
261
|
+
};
|
|
262
|
+
assert_eq!(completed.instance_path.text, "/outer/4");
|
|
263
|
+
assert_eq!(completed.schema_path.text, "/allOf/1/prefixItems/4");
|
|
264
|
+
completed.attempts = 2;
|
|
265
|
+
completed.failed = true;
|
|
266
|
+
completed.evaluation = Some(Tracked::new(Invalid));
|
|
267
|
+
assert_eq!(
|
|
268
|
+
unsafe { sn_array_report_adopt(&mut report, &*completed) },
|
|
269
|
+
VERDICT_INVALID
|
|
270
|
+
);
|
|
271
|
+
assert_eq!(report.instance_path.text, "/outer");
|
|
272
|
+
assert_eq!(report.schema_path.text, "/allOf/1");
|
|
273
|
+
assert_eq!(unsafe { sn_array_report_contains(&report, 65, 1) }, 1);
|
|
274
|
+
assert_eq!(unsafe { sn_array_report_contains(&report, 4, 0) }, 1);
|
|
275
|
+
assert_eq!(report.attempts, 2);
|
|
276
|
+
assert!(report.annotations);
|
|
277
|
+
let trial = unsafe {
|
|
278
|
+
Box::from_raw(sn_array_report_child(
|
|
279
|
+
&report,
|
|
280
|
+
0,
|
|
281
|
+
std::ptr::null(),
|
|
282
|
+
0,
|
|
283
|
+
usize::MAX,
|
|
284
|
+
1,
|
|
285
|
+
))
|
|
286
|
+
};
|
|
287
|
+
assert!(trial.silent && trial.annotations);
|
|
288
|
+
assert_eq!(trial.attempts, 0);
|
|
289
|
+
assert!(trial.instance_path.text.is_empty());
|
|
290
|
+
assert!(trial.schema_path.text.is_empty());
|
|
291
|
+
unsafe {
|
|
292
|
+
sn_array_report_finish(&mut report, 1);
|
|
293
|
+
}
|
|
294
|
+
assert_eq!(unsafe { sn_report_verdict(&report) }, VERDICT_VALID);
|
|
295
|
+
assert!(report.failed);
|
|
296
|
+
assert!(report.container_prior.is_none());
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// # Safety
|
|
301
|
+
/// Report is the current idle C-owned collection and C guards mutation as for adoption.
|
|
302
|
+
#[unsafe(no_mangle)]
|
|
303
|
+
pub unsafe extern "C" fn sn_report_clear_collection(report: *mut Report, paths_nil: i32) {
|
|
304
|
+
unsafe { &mut *report }.clear_collection(paths_nil != 0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/// # Safety
|
|
308
|
+
/// Path arguments are nil or library-owned Arrays rooted by C. No Ruby calls or
|
|
309
|
+
/// allocation occur before the completed owned seed is returned to C.
|
|
310
|
+
#[unsafe(no_mangle)]
|
|
311
|
+
pub unsafe extern "C" fn sn_report_context(
|
|
312
|
+
silent: i32,
|
|
313
|
+
attempts: usize,
|
|
314
|
+
instance_path: Value,
|
|
315
|
+
schema_path: Value,
|
|
316
|
+
) -> *mut Report {
|
|
317
|
+
let mut report = Report {
|
|
318
|
+
silent: silent != 0,
|
|
319
|
+
attempts,
|
|
320
|
+
annotations: true,
|
|
321
|
+
render_messages: true,
|
|
322
|
+
..Report::default()
|
|
323
|
+
};
|
|
324
|
+
for (value, path) in [
|
|
325
|
+
(instance_path, &mut report.instance_path),
|
|
326
|
+
(schema_path, &mut report.schema_path),
|
|
327
|
+
] {
|
|
328
|
+
if value == unsafe { sn_nil() } {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
for i in 0..unsafe { sn_len(value) } {
|
|
332
|
+
path.text.push('/');
|
|
333
|
+
path.names
|
|
334
|
+
.push((path.text.len(), unsafe { sn_entry(value, i) }));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
Box::into_raw(Box::new(report))
|
|
338
|
+
}
|
|
339
|
+
/// # Safety
|
|
340
|
+
/// Report is live, idle and C-rooted. Baseline is its original collection count.
|
|
341
|
+
#[unsafe(no_mangle)]
|
|
342
|
+
pub unsafe extern "C" fn sn_report_context_count(report: *const Report, baseline: usize) -> usize {
|
|
343
|
+
let report = unsafe { &*report };
|
|
344
|
+
let count = if report.collection_reset == 0 {
|
|
345
|
+
report.attempts.saturating_sub(baseline)
|
|
346
|
+
} else {
|
|
347
|
+
report.attempts
|
|
348
|
+
};
|
|
349
|
+
if report.silent && report.failed {
|
|
350
|
+
count.max(1)
|
|
351
|
+
} else {
|
|
352
|
+
count
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/// # Safety
|
|
356
|
+
/// Report is live, idle and C-rooted.
|
|
357
|
+
#[unsafe(no_mangle)]
|
|
358
|
+
pub unsafe extern "C" fn sn_report_context_reset(report: *const Report) -> i32 {
|
|
359
|
+
let report = unsafe { &*report };
|
|
360
|
+
if report.collection_reset == 0 {
|
|
361
|
+
0
|
|
362
|
+
} else if report.reset_path_pops {
|
|
363
|
+
1
|
|
364
|
+
} else {
|
|
365
|
+
2
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/// # Safety
|
|
370
|
+
/// Report is live, immutable and rooted by C. C owns the partial result.
|
|
371
|
+
#[unsafe(no_mangle)]
|
|
372
|
+
pub unsafe extern "C" fn sn_report_partial(report: *const Report) -> *mut Report {
|
|
373
|
+
let mut report = unsafe { &*report }.clone();
|
|
374
|
+
report.complete_callback(false);
|
|
375
|
+
Box::into_raw(Box::new(report))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/// # Safety
|
|
379
|
+
/// Report is live, immutable and C-rooted. Prior selects inherited locations.
|
|
380
|
+
#[unsafe(no_mangle)]
|
|
381
|
+
pub unsafe extern "C" fn sn_report_items_set(report: *const Report, prior: i32) -> Value {
|
|
382
|
+
let report = unsafe { &*report };
|
|
383
|
+
let locations = if prior != 0 {
|
|
384
|
+
&report.container_prior
|
|
385
|
+
} else {
|
|
386
|
+
&report.evaluation
|
|
387
|
+
};
|
|
388
|
+
locations
|
|
389
|
+
.as_ref()
|
|
390
|
+
.and_then(Tracked::items_set)
|
|
391
|
+
.unwrap_or_else(|| unsafe { sn_nil() })
|
|
392
|
+
}
|