schemurai 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -4
- data/ext/schemurai_native/Cargo.lock +55 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +1 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +74 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1087 -307
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +126 -14
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/integer_scan.rs +132 -0
- data/ext/schemurai_native/src/lib.rs +917 -238
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +147 -109
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +7 -22
- metadata +25 -2
|
@@ -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,12 +187,24 @@ 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];
|
|
167
194
|
decode_json_turbo(bytes, length, &mut scratch)
|
|
168
195
|
}
|
|
169
196
|
|
|
197
|
+
#[cfg(target_arch = "aarch64")]
|
|
198
|
+
fn decode_json_turbo(bytes: &[u8], length: usize, scratch: &mut [u8]) -> Verdict {
|
|
199
|
+
let capacity = base64_turbo::STANDARD.decoded_len_estimate(bytes.len());
|
|
200
|
+
if decode_groups(bytes, &mut scratch[..capacity], length) {
|
|
201
|
+
json(&scratch[..length])
|
|
202
|
+
} else {
|
|
203
|
+
Invalid
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[cfg(not(target_arch = "aarch64"))]
|
|
170
208
|
fn decode_json_turbo(bytes: &[u8], length: usize, scratch: &mut [u8]) -> Verdict {
|
|
171
209
|
// The slice API requires an initialized upper-bound buffer, including
|
|
172
210
|
// up to two padding bytes beyond the exact decoded length. Only the
|
|
@@ -195,6 +233,13 @@ fn decode_json_stream(bytes: &[u8], length: usize) -> Verdict {
|
|
|
195
233
|
|
|
196
234
|
// Decode borrowed bytes into the caller's fixed stack scratch. The STANDARD
|
|
197
235
|
// variant also checks each chunk's alphabet, padding and unused trailing bits.
|
|
236
|
+
#[cfg(target_arch = "aarch64")]
|
|
237
|
+
fn decode_groups(bytes: &[u8], output: &mut [u8], expected: usize) -> bool {
|
|
238
|
+
use base64_simd::AsOut;
|
|
239
|
+
matches!(base64_simd::STANDARD.decode(bytes, output.as_out()), Ok(decoded) if decoded.len() == expected)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#[cfg(not(target_arch = "aarch64"))]
|
|
198
243
|
fn decode_groups(bytes: &[u8], output: &mut [u8], expected: usize) -> bool {
|
|
199
244
|
matches!(
|
|
200
245
|
base64_turbo::STANDARD.decode_slice(bytes, output),
|
|
@@ -210,6 +255,9 @@ trait Input {
|
|
|
210
255
|
}
|
|
211
256
|
}
|
|
212
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.
|
|
213
261
|
#[inline(always)]
|
|
214
262
|
fn byte(&self, at: usize) -> Option<u8> {
|
|
215
263
|
self.get(at).copied()
|
|
@@ -309,7 +357,6 @@ impl Input for &[u8] {
|
|
|
309
357
|
.count()
|
|
310
358
|
}
|
|
311
359
|
}
|
|
312
|
-
#[inline]
|
|
313
360
|
fn string_special_word(bytes: [u8; 8]) -> u64 {
|
|
314
361
|
let word = u64::from_le_bytes(bytes);
|
|
315
362
|
let quote = word ^ 0x2222_2222_2222_2222;
|
|
@@ -380,11 +427,9 @@ impl BufferedBase64<'_> {
|
|
|
380
427
|
}
|
|
381
428
|
}
|
|
382
429
|
impl Input for BufferedBase64<'_> {
|
|
383
|
-
#[inline(always)]
|
|
384
430
|
fn byte(&self, _at: usize) -> Option<u8> {
|
|
385
431
|
self.decoded[..self.len].get(self.at).copied()
|
|
386
432
|
}
|
|
387
|
-
#[inline(always)]
|
|
388
433
|
fn advance(&mut self) {
|
|
389
434
|
self.at += 1;
|
|
390
435
|
if self.at == self.len {
|
|
@@ -414,15 +459,14 @@ struct Parser<I> {
|
|
|
414
459
|
at: usize,
|
|
415
460
|
}
|
|
416
461
|
impl<I: Input> Parser<I> {
|
|
417
|
-
#[inline(always)]
|
|
418
462
|
fn peek(&self) -> Option<u8> {
|
|
419
463
|
self.input.byte(self.at)
|
|
420
464
|
}
|
|
421
|
-
#[inline(always)]
|
|
422
465
|
fn advance(&mut self) {
|
|
423
466
|
self.at += 1;
|
|
424
467
|
self.input.advance();
|
|
425
468
|
}
|
|
469
|
+
// Part of the measured byte/take/whitespace specialization above.
|
|
426
470
|
#[inline(always)]
|
|
427
471
|
fn take(&mut self, byte: u8) -> bool {
|
|
428
472
|
if self.peek() == Some(byte) {
|
|
@@ -432,12 +476,15 @@ impl<I: Input> Parser<I> {
|
|
|
432
476
|
false
|
|
433
477
|
}
|
|
434
478
|
}
|
|
479
|
+
// Keep whitespace state visible to value/container dispatch (inline audit).
|
|
435
480
|
#[inline(always)]
|
|
436
481
|
fn whitespace(&mut self) {
|
|
437
482
|
while matches!(self.peek(), Some(b' ' | b'\t' | b'\r' | b'\n')) {
|
|
438
483
|
self.advance();
|
|
439
484
|
}
|
|
440
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".
|
|
441
488
|
#[inline(always)]
|
|
442
489
|
fn value(&mut self, depth: usize) -> bool {
|
|
443
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
|
+
}
|