jsonschema_rs 0.46.10 → 0.47.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/CHANGELOG.md +17 -1
- data/Cargo.toml +2 -2
- data/README.md +20 -1
- data/ext/jsonschema/Cargo.lock +89 -17
- data/ext/jsonschema/Cargo.toml +3 -3
- data/lib/jsonschema/version.rb +1 -1
- data/src/lib.rs +34 -4
- data/src/options.rs +63 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ac8c8fefbc1cb61a17c20ff939d639db9da1dfdd5fdc232847259b28495dc4e
|
|
4
|
+
data.tar.gz: 9ce91dc23dab850146636a4cb5aa5f3dea8ad62b1a045b93052c9ad5459baadd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e073285344d71b2179d2a63b4c6cfec6ade151baa84360a3015538fe148afd5223b1ca0cde689bf6ae3fce4b0060c1a0ab8b6ee49f0cfb49d573b1a8c2250ac1
|
|
7
|
+
data.tar.gz: 10fe811cf88137cfb5d195b76ae1fed97ed56b821b2e0ca502836211dacbfa4ecf92a4d96cc1def8a3c72a32230d7181112423372fb2a2e62b72bf5138e50674
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.47.0] - 2026-07-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Optional `iter_errors(instance)` method on custom keyword validators for reporting multiple errors from a single keyword. [#1071](https://github.com/Stranger6667/jsonschema/discussions/1071)
|
|
10
|
+
|
|
11
|
+
### Performance
|
|
12
|
+
|
|
13
|
+
- Faster validator construction, via compile-time meta-schema validators.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- `type` under `items` asserted with the Validation vocabulary disabled.
|
|
18
|
+
- Disabled vocabularies ignored for `$ref` targets without their own `$schema` (e.g. `$defs` entries).
|
|
19
|
+
|
|
5
20
|
## [0.46.10] - 2026-07-05
|
|
6
21
|
|
|
7
22
|
### Fixed
|
|
@@ -149,7 +164,8 @@
|
|
|
149
164
|
|
|
150
165
|
- Initial public release
|
|
151
166
|
|
|
152
|
-
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.
|
|
167
|
+
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.47.0...HEAD
|
|
168
|
+
[0.47.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.10...ruby-v0.47.0
|
|
153
169
|
[0.46.10]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.9...ruby-v0.46.10
|
|
154
170
|
[0.46.9]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.8...ruby-v0.46.9
|
|
155
171
|
[0.46.8]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.7...ruby-v0.46.8
|
data/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonschema-rb"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.47.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Dmitry Dygalo <dmitry@dygalo.dev>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -13,7 +13,7 @@ publish = false
|
|
|
13
13
|
crate-type = ["cdylib"]
|
|
14
14
|
|
|
15
15
|
[dependencies]
|
|
16
|
-
jsonschema = { version = "0.
|
|
16
|
+
jsonschema = { version = "0.47.0", default-features = false, features = ["arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
|
|
17
17
|
magnus = { version = "0.8", features = ["rb-sys"] }
|
|
18
18
|
rb-sys = "0.9"
|
|
19
19
|
serde = { workspace = true }
|
data/README.md
CHANGED
|
@@ -148,7 +148,26 @@ Each custom keyword class must implement:
|
|
|
148
148
|
- `initialize(parent_schema, value, schema_path)` - called during schema compilation
|
|
149
149
|
- `validate(instance)` - raise on failure, return normally on success
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
A class may also implement `iter_errors(instance)`, returning an array of exceptions, to report several problems from one keyword. `each_error` then surfaces each of them; when the method is absent it falls back to the single error from `validate`:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
class AllPositive
|
|
155
|
+
def initialize(parent_schema, value, schema_path); end
|
|
156
|
+
|
|
157
|
+
def validate(instance)
|
|
158
|
+
first_error = iter_errors(instance).first
|
|
159
|
+
raise first_error if first_error
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def iter_errors(instance)
|
|
163
|
+
return [] unless instance.is_a?(Array)
|
|
164
|
+
instance.each_index.select { |i| instance[i].negative? }
|
|
165
|
+
.map { |i| ArgumentError.new("item #{i} is negative") }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
When `validate` raises, the original exception is preserved as the `cause` of the `ValidationError`, so callers can inspect it (errors from `iter_errors` keep their exception as `cause` in the same way):
|
|
152
171
|
|
|
153
172
|
```ruby
|
|
154
173
|
begin
|
data/ext/jsonschema/Cargo.lock
CHANGED
|
@@ -378,21 +378,15 @@ dependencies = [
|
|
|
378
378
|
|
|
379
379
|
[[package]]
|
|
380
380
|
name = "hashbrown"
|
|
381
|
-
version = "0.
|
|
381
|
+
version = "0.17.0"
|
|
382
382
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
383
|
-
checksum = "
|
|
383
|
+
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
|
384
384
|
dependencies = [
|
|
385
385
|
"allocator-api2",
|
|
386
386
|
"equivalent",
|
|
387
387
|
"foldhash",
|
|
388
388
|
]
|
|
389
389
|
|
|
390
|
-
[[package]]
|
|
391
|
-
name = "hashbrown"
|
|
392
|
-
version = "0.17.0"
|
|
393
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
394
|
-
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
|
395
|
-
|
|
396
390
|
[[package]]
|
|
397
391
|
name = "http"
|
|
398
392
|
version = "1.4.0"
|
|
@@ -601,7 +595,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
601
595
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|
602
596
|
dependencies = [
|
|
603
597
|
"equivalent",
|
|
604
|
-
"hashbrown
|
|
598
|
+
"hashbrown",
|
|
605
599
|
]
|
|
606
600
|
|
|
607
601
|
[[package]]
|
|
@@ -693,9 +687,9 @@ dependencies = [
|
|
|
693
687
|
|
|
694
688
|
[[package]]
|
|
695
689
|
name = "jsonschema"
|
|
696
|
-
version = "0.
|
|
690
|
+
version = "0.47.0"
|
|
697
691
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
698
|
-
checksum = "
|
|
692
|
+
checksum = "281c43ff06dcb331e9356d30e38853d559ce3d0a3f693e0b0e102667dec14fb1"
|
|
699
693
|
dependencies = [
|
|
700
694
|
"ahash",
|
|
701
695
|
"bytecount",
|
|
@@ -706,6 +700,7 @@ dependencies = [
|
|
|
706
700
|
"getrandom 0.3.4",
|
|
707
701
|
"idna",
|
|
708
702
|
"itoa",
|
|
703
|
+
"jsonschema-macros",
|
|
709
704
|
"jsonschema-regex",
|
|
710
705
|
"num-bigint",
|
|
711
706
|
"num-cmp",
|
|
@@ -721,9 +716,38 @@ dependencies = [
|
|
|
721
716
|
"uuid-simd",
|
|
722
717
|
]
|
|
723
718
|
|
|
719
|
+
[[package]]
|
|
720
|
+
name = "jsonschema-macros"
|
|
721
|
+
version = "0.47.0"
|
|
722
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
723
|
+
checksum = "33814349f9eb1e2f5814207c40d92413098d4d57d0fb5b5972b1c47d63a79137"
|
|
724
|
+
dependencies = [
|
|
725
|
+
"jsonschema-macros-core",
|
|
726
|
+
"proc-macro2",
|
|
727
|
+
]
|
|
728
|
+
|
|
729
|
+
[[package]]
|
|
730
|
+
name = "jsonschema-macros-core"
|
|
731
|
+
version = "0.47.0"
|
|
732
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
733
|
+
checksum = "7bc971df1f0042bff8020f1db975da867b56e11c519821e6e573f489dd909a94"
|
|
734
|
+
dependencies = [
|
|
735
|
+
"fancy-regex",
|
|
736
|
+
"indexmap",
|
|
737
|
+
"jsonschema-regex",
|
|
738
|
+
"percent-encoding",
|
|
739
|
+
"proc-macro-crate",
|
|
740
|
+
"proc-macro2",
|
|
741
|
+
"quote",
|
|
742
|
+
"referencing",
|
|
743
|
+
"regex",
|
|
744
|
+
"serde_json",
|
|
745
|
+
"syn",
|
|
746
|
+
]
|
|
747
|
+
|
|
724
748
|
[[package]]
|
|
725
749
|
name = "jsonschema-rb-ext"
|
|
726
|
-
version = "0.
|
|
750
|
+
version = "0.47.0"
|
|
727
751
|
dependencies = [
|
|
728
752
|
"jsonschema",
|
|
729
753
|
"magnus",
|
|
@@ -735,9 +759,9 @@ dependencies = [
|
|
|
735
759
|
|
|
736
760
|
[[package]]
|
|
737
761
|
name = "jsonschema-regex"
|
|
738
|
-
version = "0.
|
|
762
|
+
version = "0.47.0"
|
|
739
763
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
740
|
-
checksum = "
|
|
764
|
+
checksum = "ee0b351864e7ffbc5db9273daf7fa1b4d5177b0946713d667ca571b83c0b4045"
|
|
741
765
|
dependencies = [
|
|
742
766
|
"regex-syntax",
|
|
743
767
|
]
|
|
@@ -988,6 +1012,15 @@ dependencies = [
|
|
|
988
1012
|
"zerovec",
|
|
989
1013
|
]
|
|
990
1014
|
|
|
1015
|
+
[[package]]
|
|
1016
|
+
name = "proc-macro-crate"
|
|
1017
|
+
version = "3.5.0"
|
|
1018
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1019
|
+
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
|
1020
|
+
dependencies = [
|
|
1021
|
+
"toml_edit",
|
|
1022
|
+
]
|
|
1023
|
+
|
|
991
1024
|
[[package]]
|
|
992
1025
|
name = "proc-macro2"
|
|
993
1026
|
version = "1.0.106"
|
|
@@ -1073,14 +1106,14 @@ dependencies = [
|
|
|
1073
1106
|
|
|
1074
1107
|
[[package]]
|
|
1075
1108
|
name = "referencing"
|
|
1076
|
-
version = "0.
|
|
1109
|
+
version = "0.47.0"
|
|
1077
1110
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1078
|
-
checksum = "
|
|
1111
|
+
checksum = "348e860aeb0b7bd035778fd11dd9cd5290d32e4aed3b8f2274a00287a9fd362b"
|
|
1079
1112
|
dependencies = [
|
|
1080
1113
|
"ahash",
|
|
1081
1114
|
"fluent-uri",
|
|
1082
1115
|
"getrandom 0.3.4",
|
|
1083
|
-
"hashbrown
|
|
1116
|
+
"hashbrown",
|
|
1084
1117
|
"itoa",
|
|
1085
1118
|
"micromap",
|
|
1086
1119
|
"parking_lot",
|
|
@@ -1495,6 +1528,36 @@ dependencies = [
|
|
|
1495
1528
|
"tokio",
|
|
1496
1529
|
]
|
|
1497
1530
|
|
|
1531
|
+
[[package]]
|
|
1532
|
+
name = "toml_datetime"
|
|
1533
|
+
version = "1.1.1+spec-1.1.0"
|
|
1534
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1535
|
+
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
|
1536
|
+
dependencies = [
|
|
1537
|
+
"serde_core",
|
|
1538
|
+
]
|
|
1539
|
+
|
|
1540
|
+
[[package]]
|
|
1541
|
+
name = "toml_edit"
|
|
1542
|
+
version = "0.25.12+spec-1.1.0"
|
|
1543
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1544
|
+
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
|
|
1545
|
+
dependencies = [
|
|
1546
|
+
"indexmap",
|
|
1547
|
+
"toml_datetime",
|
|
1548
|
+
"toml_parser",
|
|
1549
|
+
"winnow",
|
|
1550
|
+
]
|
|
1551
|
+
|
|
1552
|
+
[[package]]
|
|
1553
|
+
name = "toml_parser"
|
|
1554
|
+
version = "1.1.2+spec-1.1.0"
|
|
1555
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1556
|
+
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
|
1557
|
+
dependencies = [
|
|
1558
|
+
"winnow",
|
|
1559
|
+
]
|
|
1560
|
+
|
|
1498
1561
|
[[package]]
|
|
1499
1562
|
name = "tower"
|
|
1500
1563
|
version = "0.5.3"
|
|
@@ -1894,6 +1957,15 @@ version = "0.52.6"
|
|
|
1894
1957
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1895
1958
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
1896
1959
|
|
|
1960
|
+
[[package]]
|
|
1961
|
+
name = "winnow"
|
|
1962
|
+
version = "1.0.3"
|
|
1963
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1964
|
+
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
|
1965
|
+
dependencies = [
|
|
1966
|
+
"memchr",
|
|
1967
|
+
]
|
|
1968
|
+
|
|
1897
1969
|
[[package]]
|
|
1898
1970
|
name = "wit-bindgen"
|
|
1899
1971
|
version = "0.57.1"
|
data/ext/jsonschema/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonschema-rb-ext"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.47.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
publish = false
|
|
6
6
|
|
|
@@ -10,10 +10,10 @@ name = "jsonschema_rb"
|
|
|
10
10
|
path = "../../src/lib.rs"
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
-
jsonschema = { version = "0.
|
|
13
|
+
jsonschema = { version = "0.47.0", default-features = false, features = ["arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
|
|
14
14
|
magnus = { version = "0.8", features = ["rb-sys"] }
|
|
15
15
|
rb-sys = "0.9"
|
|
16
|
-
referencing = "0.
|
|
16
|
+
referencing = "0.47.0"
|
|
17
17
|
serde = { version = "1", features = ["derive"] }
|
|
18
18
|
serde_json = { version = "1", features = ["arbitrary_precision"] }
|
|
19
19
|
|
data/lib/jsonschema/version.rb
CHANGED
data/src/lib.rs
CHANGED
|
@@ -29,6 +29,7 @@ use magnus::{
|
|
|
29
29
|
use referencing::unescape_segment;
|
|
30
30
|
use std::{
|
|
31
31
|
cell::RefCell,
|
|
32
|
+
collections::VecDeque,
|
|
32
33
|
panic::{self, AssertUnwindSafe},
|
|
33
34
|
sync::Arc,
|
|
34
35
|
};
|
|
@@ -198,13 +199,36 @@ fn build_parsed_options(
|
|
|
198
199
|
|
|
199
200
|
thread_local! {
|
|
200
201
|
static LAST_CALLBACK_ERROR: RefCell<Option<Error>> = const { RefCell::new(None) };
|
|
201
|
-
// Stores the original Ruby
|
|
202
|
-
// set as the `cause` on the resulting ValidationError.
|
|
203
|
-
pub(crate) static CUSTOM_KEYWORD_CAUSE: RefCell<
|
|
202
|
+
// Stores the original Ruby exceptions from a custom keyword's validate()/iter_errors() calls so
|
|
203
|
+
// each can be set as the `cause` on the resulting ValidationError, in FIFO order.
|
|
204
|
+
pub(crate) static CUSTOM_KEYWORD_CAUSE: RefCell<VecDeque<Error>> = const { RefCell::new(VecDeque::new()) };
|
|
204
205
|
/// When `true`, the custom panic hook suppresses output (inside `catch_unwind` blocks).
|
|
205
206
|
static SUPPRESS_PANIC_OUTPUT: RefCell<bool> = const { RefCell::new(false) };
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
// RAII scope giving each surfacing call its own keyword-cause queue and restoring the caller's on
|
|
210
|
+
// exit. This discards causes a call leaves unconsumed (`evaluate`, an abandoned enumeration, a
|
|
211
|
+
// panic — the drop still runs) and lets a keyword re-enter validation without clobbering the outer
|
|
212
|
+
// call's pending causes.
|
|
213
|
+
pub(crate) struct KeywordCauseScope {
|
|
214
|
+
saved: VecDeque<Error>,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
impl KeywordCauseScope {
|
|
218
|
+
pub(crate) fn enter() -> Self {
|
|
219
|
+
Self {
|
|
220
|
+
saved: CUSTOM_KEYWORD_CAUSE.with(|cell| std::mem::take(&mut *cell.borrow_mut())),
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
impl Drop for KeywordCauseScope {
|
|
226
|
+
fn drop(&mut self) {
|
|
227
|
+
let saved = std::mem::take(&mut self.saved);
|
|
228
|
+
CUSTOM_KEYWORD_CAUSE.with(|cell| *cell.borrow_mut() = saved);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
208
232
|
static VALIDATION_ERROR_CLASS: Lazy<ExceptionClass> = Lazy::new(|ruby| {
|
|
209
233
|
let module: RModule = ruby
|
|
210
234
|
.class_object()
|
|
@@ -431,7 +455,7 @@ fn into_ruby_error(
|
|
|
431
455
|
exc.ivar_set(*ID_AT_ABSOLUTE_KEYWORD_LOCATION, absolute_location)?;
|
|
432
456
|
|
|
433
457
|
if is_custom {
|
|
434
|
-
if let Some(cause) = CUSTOM_KEYWORD_CAUSE.with(|cell| cell.borrow_mut().
|
|
458
|
+
if let Some(cause) = CUSTOM_KEYWORD_CAUSE.with(|cell| cell.borrow_mut().pop_front()) {
|
|
435
459
|
if let ErrorType::Exception(cause_exc) = cause.error_type() {
|
|
436
460
|
exc.ivar_set(*ID_CAUSE, cause_exc.as_value())?;
|
|
437
461
|
}
|
|
@@ -654,6 +678,7 @@ impl Validator {
|
|
|
654
678
|
|
|
655
679
|
#[allow(unsafe_code)]
|
|
656
680
|
fn validate(ruby: &Ruby, rb_self: &Self, instance: Value) -> Result<(), Error> {
|
|
681
|
+
let _scope = KeywordCauseScope::enter();
|
|
657
682
|
let json_instance = to_value(ruby, instance)?;
|
|
658
683
|
|
|
659
684
|
if rb_self.has_ruby_callbacks {
|
|
@@ -687,6 +712,7 @@ impl Validator {
|
|
|
687
712
|
|
|
688
713
|
#[allow(unsafe_code)]
|
|
689
714
|
fn iter_errors(ruby: &Ruby, rb_self: &Self, instance: Value) -> Result<Value, Error> {
|
|
715
|
+
let _scope = KeywordCauseScope::enter();
|
|
690
716
|
let json_instance = to_value(ruby, instance)?;
|
|
691
717
|
|
|
692
718
|
if ruby.block_given() {
|
|
@@ -773,6 +799,7 @@ impl Validator {
|
|
|
773
799
|
|
|
774
800
|
#[allow(unsafe_code)]
|
|
775
801
|
fn evaluate(ruby: &Ruby, rb_self: &Self, instance: Value) -> Result<Evaluation, Error> {
|
|
802
|
+
let _scope = KeywordCauseScope::enter();
|
|
776
803
|
let json_instance = to_value(ruby, instance)?;
|
|
777
804
|
|
|
778
805
|
if rb_self.has_ruby_callbacks {
|
|
@@ -1027,6 +1054,7 @@ fn is_valid(ruby: &Ruby, args: &[Value]) -> Result<bool, Error> {
|
|
|
1027
1054
|
|
|
1028
1055
|
#[allow(unsafe_code)]
|
|
1029
1056
|
fn validate(ruby: &Ruby, args: &[Value]) -> Result<(), Error> {
|
|
1057
|
+
let _scope = KeywordCauseScope::enter();
|
|
1030
1058
|
let parsed_args = scan_args::<(Value, Value), (), (), (), _, ()>(args)?;
|
|
1031
1059
|
let (schema, instance) = parsed_args.required;
|
|
1032
1060
|
let kw = extract_kwargs(ruby, parsed_args.keywords)?;
|
|
@@ -1079,6 +1107,7 @@ fn validate(ruby: &Ruby, args: &[Value]) -> Result<(), Error> {
|
|
|
1079
1107
|
|
|
1080
1108
|
#[allow(unsafe_code)]
|
|
1081
1109
|
fn each_error(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
1110
|
+
let _scope = KeywordCauseScope::enter();
|
|
1082
1111
|
let parsed_args = scan_args::<(Value, Value), (), (), (), _, ()>(args)?;
|
|
1083
1112
|
let (schema, instance) = parsed_args.required;
|
|
1084
1113
|
let kw = extract_kwargs(ruby, parsed_args.keywords)?;
|
|
@@ -1175,6 +1204,7 @@ fn each_error(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
|
1175
1204
|
|
|
1176
1205
|
#[allow(unsafe_code)]
|
|
1177
1206
|
fn evaluate(ruby: &Ruby, args: &[Value]) -> Result<Evaluation, Error> {
|
|
1207
|
+
let _scope = KeywordCauseScope::enter();
|
|
1178
1208
|
let parsed_args = scan_args::<(Value, Value), (), (), (), _, ()>(args)?;
|
|
1179
1209
|
let (schema, instance) = parsed_args.required;
|
|
1180
1210
|
let kw = extract_evaluate_kwargs(ruby, parsed_args.keywords)?;
|
data/src/options.rs
CHANGED
|
@@ -12,7 +12,7 @@ use magnus::{
|
|
|
12
12
|
prelude::*,
|
|
13
13
|
scan_args::{get_kwargs, scan_args, KwArgs},
|
|
14
14
|
value::Opaque,
|
|
15
|
-
Error, RHash, RModule, Ruby, TryConvert, Value,
|
|
15
|
+
Error, Exception, RArray, RHash, RModule, Ruby, TryConvert, Value,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
use crate::{
|
|
@@ -388,7 +388,7 @@ impl jsonschema::Keyword for RubyKeyword {
|
|
|
388
388
|
Err(e) => {
|
|
389
389
|
let msg = e.to_string();
|
|
390
390
|
CUSTOM_KEYWORD_CAUSE.with(|cell| {
|
|
391
|
-
|
|
391
|
+
cell.borrow_mut().push_back(e);
|
|
392
392
|
});
|
|
393
393
|
Err(jsonschema::ValidationError::custom(msg))
|
|
394
394
|
}
|
|
@@ -404,6 +404,67 @@ impl jsonschema::Keyword for RubyKeyword {
|
|
|
404
404
|
let result: Result<Value, _> = inst.funcall("validate", (rb_instance,));
|
|
405
405
|
result.is_ok()
|
|
406
406
|
}
|
|
407
|
+
|
|
408
|
+
fn iter_errors<'i>(
|
|
409
|
+
&self,
|
|
410
|
+
instance: &'i serde_json::Value,
|
|
411
|
+
) -> Box<dyn Iterator<Item = jsonschema::ValidationError<'i>> + 'i> {
|
|
412
|
+
let ruby = Ruby::get().expect("Ruby VM should be initialized");
|
|
413
|
+
let keyword = ruby.get_inner(self.instance);
|
|
414
|
+
// Without an `iter_errors` method, fall back to the single-error `validate` path.
|
|
415
|
+
if !keyword.respond_to("iter_errors", false).unwrap_or(false) {
|
|
416
|
+
return Box::new(self.validate(instance).err().into_iter());
|
|
417
|
+
}
|
|
418
|
+
let rb_instance = match value_to_ruby(&ruby, instance) {
|
|
419
|
+
Ok(rb_instance) => rb_instance,
|
|
420
|
+
Err(error) => return Box::new(std::iter::once(custom_error_from_ruby(error))),
|
|
421
|
+
};
|
|
422
|
+
let array = keyword
|
|
423
|
+
.funcall::<_, _, Value>("iter_errors", (rb_instance,))
|
|
424
|
+
.and_then(|result| result.funcall::<_, _, RArray>("to_a", ()));
|
|
425
|
+
let array = match array {
|
|
426
|
+
Ok(array) => array,
|
|
427
|
+
Err(error) => return Box::new(std::iter::once(custom_error_from_ruby(error))),
|
|
428
|
+
};
|
|
429
|
+
let mut errors: Vec<jsonschema::ValidationError<'i>> = Vec::with_capacity(array.len());
|
|
430
|
+
for index in 0..array.len() {
|
|
431
|
+
// `entry` (not `as_slice`) because `ruby_value_to_error` calls back into Ruby.
|
|
432
|
+
let item = isize::try_from(index)
|
|
433
|
+
.map_err(|_| {
|
|
434
|
+
Error::new(
|
|
435
|
+
ruby.exception_arg_error(),
|
|
436
|
+
"Array index exceeds supported range",
|
|
437
|
+
)
|
|
438
|
+
})
|
|
439
|
+
.and_then(|index| array.entry::<Value>(index));
|
|
440
|
+
let error = match item {
|
|
441
|
+
Ok(item) => ruby_value_to_error(&ruby, item),
|
|
442
|
+
Err(error) => error,
|
|
443
|
+
};
|
|
444
|
+
errors.push(custom_error_from_ruby(error));
|
|
445
|
+
}
|
|
446
|
+
Box::new(errors.into_iter())
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Wrap a yielded Ruby value as an `Error`, preserving exception instances so they surface as
|
|
451
|
+
// `#cause`; a non-exception is reported through a generic `RuntimeError`.
|
|
452
|
+
fn ruby_value_to_error(ruby: &Ruby, value: Value) -> Error {
|
|
453
|
+
if let Ok(exception) = Exception::try_convert(value) {
|
|
454
|
+
return Error::from(exception);
|
|
455
|
+
}
|
|
456
|
+
let message = value
|
|
457
|
+
.funcall::<_, _, String>("to_s", ())
|
|
458
|
+
.unwrap_or_else(|_| "custom keyword error".to_string());
|
|
459
|
+
Error::new(ruby.exception_runtime_error(), message)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Build a custom `ValidationError` from a Ruby error, queuing the exception as the error's cause so
|
|
463
|
+
// it surfaces as `#cause`, exactly like the single `validate` path.
|
|
464
|
+
fn custom_error_from_ruby<'i>(error: Error) -> jsonschema::ValidationError<'i> {
|
|
465
|
+
let message = error.to_string();
|
|
466
|
+
CUSTOM_KEYWORD_CAUSE.with(|cell| cell.borrow_mut().push_back(error));
|
|
467
|
+
jsonschema::ValidationError::custom(message)
|
|
407
468
|
}
|
|
408
469
|
|
|
409
470
|
#[allow(clippy::too_many_arguments)]
|