corvus_json_schema 0.1.4 → 0.2.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/Cargo.lock +13 -2
- data/README.md +6 -4
- data/THIRD-PARTY-NOTICES.txt +15 -14
- data/VERSIONHISTORY.md +9 -0
- data/ext/corvus_json_schema/Cargo.toml +2 -2
- data/ext/corvus_json_schema/src/lib.rs +29 -2
- data/ext/corvus_json_schema/vendor/corvus-json-schema/Cargo.toml +4 -9
- data/ext/corvus_json_schema/vendor/corvus-json-schema/README.md +30 -49
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/compiler.rs +5 -3
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/formats.rs +7 -6
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/lib.rs +4 -4
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/options.rs +12 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/pattern.rs +344 -974
- data/lib/corvus_json_schema/version.rb +1 -1
- data/lib/corvus_json_schema.rb +8 -3
- metadata +2 -15
- data/ext/corvus_json_schema/vendor/corvus-json-schema/THIRD-PARTY-NOTICES.txt +0 -93
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/charset.rs +0 -271
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/compile.rs +0 -941
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/dfa.rs +0 -366
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/fold.rs +0 -138
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/mod.rs +0 -329
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/parse.rs +0 -1128
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/re2.rs +0 -129
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tables_gen.rs +0 -6685
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tests.rs +0 -1672
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/ucd.rs +0 -309
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/units.rs +0 -165
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/vm.rs +0 -981
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e2347892c4bd0c1e41ef9b14747b260c5938d47f1a644f1fdbe0c3992f822a1
|
|
4
|
+
data.tar.gz: 78e9eda166cac2e81b48f87927299d2c092f45b1269179c58ed65495e9cf278f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5d369d4df79459748f2eeaf93a1586986ab37d8d112e3095ca22650c0144ca110b6f0d85559c0f4cd82b704ba1bcee3117813785fa796d120e643f500228253
|
|
7
|
+
data.tar.gz: 9fca29ee3cdf761a2caeebf80a6f8c83f788957d346c0d66fdd1d7af2735c439b6b9a0f7161e5e570a07aef2f91075f2d9077aa0785fd3ecaa194a4642695de2
|
data/Cargo.lock
CHANGED
|
@@ -63,15 +63,26 @@ dependencies = [
|
|
|
63
63
|
|
|
64
64
|
[[package]]
|
|
65
65
|
name = "corvus-json-schema"
|
|
66
|
-
version = "0.
|
|
66
|
+
version = "0.2.0"
|
|
67
67
|
dependencies = [
|
|
68
|
+
"corvus-regex-ecma262",
|
|
68
69
|
"regex",
|
|
69
70
|
"serde_json",
|
|
70
71
|
]
|
|
71
72
|
|
|
73
|
+
[[package]]
|
|
74
|
+
name = "corvus-regex-ecma262"
|
|
75
|
+
version = "0.2.7"
|
|
76
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
77
|
+
checksum = "be72de82781404398482a431a88c1d03b82d474a5fad3003cac18f5893fc71ee"
|
|
78
|
+
dependencies = [
|
|
79
|
+
"aho-corasick",
|
|
80
|
+
"memchr",
|
|
81
|
+
]
|
|
82
|
+
|
|
72
83
|
[[package]]
|
|
73
84
|
name = "corvus_json_schema"
|
|
74
|
-
version = "0.
|
|
85
|
+
version = "0.2.0"
|
|
75
86
|
dependencies = [
|
|
76
87
|
"corvus-json-schema",
|
|
77
88
|
"magnus",
|
data/README.md
CHANGED
|
@@ -54,6 +54,8 @@ Its options:
|
|
|
54
54
|
| `entry_point` | `nil` | A reference to the subschema to validate against, such as `"#/$defs/item"`. |
|
|
55
55
|
| `max_depth` | `128` | The deepest the evaluator recurses in place before it raises `DepthError`. |
|
|
56
56
|
| `pattern_match_limit` | `1.0` | The seconds one match of a `pattern` or `patternProperties` regular expression against one string may take before the validation raises `PatternMatchLimitError`. `nil` or `0` turns the limit off, for a caller who trusts both the schema and the instances. |
|
|
57
|
+
| `regex_edition` | `:dialect` | The edition of ECMA-262 `pattern`, `patternProperties` and the `regex` format are read with: `:dialect` (ECMAScript 2020 for 2019-09 and 2020-12, which name it, today's for the drafts before them), `:current` or `:es2020`. Under ECMAScript 2020 a modifier group such as `(?i:...)` is not valid. |
|
|
58
|
+
| `regex_unicode_version` | `:default` | The version of Unicode patterns are read with: `:default` (the version of the V8 the answers are held to, 17 today), `:latest`, `13`, `17` or `18`. |
|
|
57
59
|
|
|
58
60
|
A `Validator` is immutable and can be shared between threads.
|
|
59
61
|
|
|
@@ -114,7 +116,7 @@ repository's submodule (or `JSON_SCHEMA_TEST_SUITE`).
|
|
|
114
116
|
|
|
115
117
|
Apache-2.0
|
|
116
118
|
|
|
117
|
-
The gem is built on the `corvus-json-schema` Rust crate,
|
|
118
|
-
Database, which is under the Unicode License V3.
|
|
119
|
-
and the one of the `regress` crate
|
|
120
|
-
native gem, beside `LICENSE`.
|
|
119
|
+
The gem is built on the `corvus-json-schema` Rust crate, which matches patterns with the `corvus-regex-ecma262` crate,
|
|
120
|
+
whose Unicode tables are derived from the Unicode Character Database, which is under the Unicode License V3.
|
|
121
|
+
[THIRD-PARTY-NOTICES.txt](THIRD-PARTY-NOTICES.txt) has that notice, and the one of the `regress` crate, from whose
|
|
122
|
+
tables those were written. The file is in the source gem and in every native gem, beside `LICENSE`.
|
data/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -13,14 +13,16 @@ License notice for the Unicode Character Database
|
|
|
13
13
|
|
|
14
14
|
The gem is built on the corvus-json-schema Rust crate. The source gem holds
|
|
15
15
|
the crate's sources (ext/corvus_json_schema/vendor/corvus-json-schema), and a
|
|
16
|
-
native gem holds the extension compiled from them.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
native gem holds the extension compiled from them. That crate matches patterns
|
|
17
|
+
with the corvus-regex-ecma262 crate, which is compiled in with it.
|
|
18
|
+
corvus-regex-ecma262 holds tables derived from the data files of the Unicode
|
|
19
|
+
Character Database, versions 13.0.0, 17.0.0 and 18.0.0
|
|
20
|
+
(https://www.unicode.org/Public/), and from its emoji sequence files: the
|
|
21
|
+
General_Category values, the binary properties, the Script and
|
|
22
|
+
Script_Extensions values, the simple case folding, the simple uppercase
|
|
23
|
+
mapping and the properties of strings. The data files are licensed under the
|
|
24
|
+
Unicode License V3 (SPDX-License-Identifier: Unicode-3.0), as
|
|
25
|
+
https://www.unicode.org/license.txt gives it:
|
|
24
26
|
|
|
25
27
|
UNICODE LICENSE V3
|
|
26
28
|
|
|
@@ -65,12 +67,11 @@ authorization of the copyright holder.
|
|
|
65
67
|
License notice for https://github.com/ridiculousfish/regress
|
|
66
68
|
-------------------------------
|
|
67
69
|
|
|
68
|
-
The tables of
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
corvus-json-schema crate depends on it.
|
|
70
|
+
The tables of the corvus-regex-ecma262 crate were written, through the tables
|
|
71
|
+
of the Corvus Go module, from the property tables of regress (its gen-unicode
|
|
72
|
+
tool), which are generated from the Unicode Character Database. regress is
|
|
73
|
+
licensed under the MIT license or the Apache License, Version 2.0, at the
|
|
74
|
+
user's option. Neither the gem nor the crates it is built on depend on it.
|
|
74
75
|
|
|
75
76
|
MIT License
|
|
76
77
|
|
data/VERSIONHISTORY.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
The version history of the `corvus_json_schema` Ruby gem. It is versioned independently of the Corvus NuGet packages and of the [corvus-json-schema](../../src-rs/corvus-json-schema/VERSIONHISTORY.md) Rust crate it is built on.
|
|
4
4
|
|
|
5
|
+
## V0.2.0
|
|
6
|
+
|
|
7
|
+
V0.2.0 takes version 0.2.0 of the `corvus-json-schema` crate, which matches patterns with the [`corvus-regex-ecma262`](https://crates.io/crates/corvus-regex-ecma262) crate and reads them in the grammar of the dialect. It is a minor version because under 2019-09 and 2020-12 a pattern that uses what ECMAScript added after 2020 is no longer valid. Everything else that was there is unchanged, and there are two new options.
|
|
8
|
+
|
|
9
|
+
- **The grammar of the dialect.** JSON Schema 2019-09 and 2020-12 name the grammar of ECMAScript 2020, and patterns under them are now read with it: a modifier group such as `(?i:...)`, or a group name used by two groups, is not valid, and `CorvusJsonSchema.compile` raises `CompilationError` for a schema with one in a `pattern`. A property escape that the tables of 2020 do not have (`\p{Script=Yezidi}`) is not valid with the `u` flag, so the pattern is read without it, where `\p` is the letter `p`, as a pattern with `\&` is. The `regex` format reads only the grammar of the flag. Drafts 4 to 7 read today's grammar, as before.
|
|
10
|
+
- **`regex_edition:`** (`:dialect`, the default, `:current` or `:es2020`) chooses the edition for every dialect, and **`regex_unicode_version:`** (`:default`, the default, `:latest`, `13`, `17` or `18`) the version of Unicode. Another value raises `ArgumentError`.
|
|
11
|
+
- **The engine.** Every pattern that no dedicated matcher of the crate takes is matched by `corvus-regex-ecma262`, whose answers are V8's for the whole of the ECMA-262 regular expression test data. The limits are those of 0.1.4. Over the jsonschema-benchmark corpora the crate validates in 0.98 of the time of 0.1.7 once warm, and takes 1.06 of it for the first validation and 1.05 to compile, since the engine builds a pattern's automaton on its first match.
|
|
12
|
+
- **Notices.** `THIRD-PARTY-NOTICES.txt` names the tables of `corvus-regex-ecma262`, which the native gems now hold in place of the crate's own. The source gem no longer carries a copy of the crate's notice, which the crate no longer has.
|
|
13
|
+
|
|
5
14
|
## V0.1.4
|
|
6
15
|
|
|
7
16
|
V0.1.4 takes version 0.1.7 of the `corvus-json-schema` crate. That fixes wrong results for several forms of pattern and a validation that did not end, replaces the crate's regular expression engine, and gives every match of a pattern a limit of time. The functions and types that were there are unchanged, and there are additions for the limit. Versions 0.1.0 to 0.1.3 are affected.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "corvus_json_schema"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.89"
|
|
6
6
|
description = "The native extension of the corvus_json_schema gem: the corvus-json-schema crate, reading Ruby values in place."
|
|
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
|
|
|
12
12
|
|
|
13
13
|
[dependencies]
|
|
14
14
|
# The crate, copied in by `rake vendor` (from src-rs/corvus-json-schema) so that the source gem builds on its own.
|
|
15
|
-
corvus = { package = "corvus-json-schema", path = "vendor/corvus-json-schema", version = "0.
|
|
15
|
+
corvus = { package = "corvus-json-schema", path = "vendor/corvus-json-schema", version = "0.2.0" }
|
|
16
16
|
magnus = { version = "0.9.1", features = ["rb-sys"] }
|
|
17
17
|
rb-sys = "0.9.130"
|
|
18
18
|
serde_json = { version = "1.0.151", features = ["preserve_order", "float_roundtrip"] }
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
use std::cell::{Cell, RefCell};
|
|
5
5
|
|
|
6
6
|
use corvus::{
|
|
7
|
-
ArrayView, CompileOptions, Dialect, Instance, JsonSchemaResultsCollector, Kind, ObjectView,
|
|
7
|
+
ArrayView, CompileOptions, Dialect, Instance, JsonSchemaResultsCollector, Kind, ObjectView, RegexEdition,
|
|
8
|
+
RegexUnicodeVersion, ResultsLevel, View,
|
|
8
9
|
};
|
|
9
10
|
use magnus::prelude::*;
|
|
10
11
|
use magnus::rb_sys::AsRawValue;
|
|
@@ -481,6 +482,8 @@ fn compile(
|
|
|
481
482
|
entry_point: Option<String>,
|
|
482
483
|
max_depth: u32,
|
|
483
484
|
pattern_match_limit: Option<f64>,
|
|
485
|
+
regex_edition: String,
|
|
486
|
+
regex_unicode_version: String,
|
|
484
487
|
) -> Result<Validator, Error> {
|
|
485
488
|
let schema: Json = match RString::from_value(schema) {
|
|
486
489
|
Some(text) => serde_json::from_str(&text.to_string()?)
|
|
@@ -513,6 +516,30 @@ fn compile(
|
|
|
513
516
|
}
|
|
514
517
|
_ => None,
|
|
515
518
|
},
|
|
519
|
+
regex_edition: match regex_edition.as_str() {
|
|
520
|
+
"dialect" => None,
|
|
521
|
+
"current" => Some(RegexEdition::Current),
|
|
522
|
+
"es2020" => Some(RegexEdition::Es2020),
|
|
523
|
+
e => {
|
|
524
|
+
return Err(Error::new(
|
|
525
|
+
ruby.exception_arg_error(),
|
|
526
|
+
format!("regex_edition is :dialect, :current or :es2020, not {e:?}"),
|
|
527
|
+
));
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
regex_unicode_version: match regex_unicode_version.as_str() {
|
|
531
|
+
"default" => RegexUnicodeVersion::Default,
|
|
532
|
+
"latest" => RegexUnicodeVersion::Latest,
|
|
533
|
+
"13" => RegexUnicodeVersion::V13,
|
|
534
|
+
"17" => RegexUnicodeVersion::V17,
|
|
535
|
+
"18" => RegexUnicodeVersion::V18,
|
|
536
|
+
v => {
|
|
537
|
+
return Err(Error::new(
|
|
538
|
+
ruby.exception_arg_error(),
|
|
539
|
+
format!("regex_unicode_version is :default, :latest, 13, 17 or 18, not {v:?}"),
|
|
540
|
+
));
|
|
541
|
+
}
|
|
542
|
+
},
|
|
516
543
|
..CompileOptions::default()
|
|
517
544
|
};
|
|
518
545
|
let mut ruby_formats = false;
|
|
@@ -649,7 +676,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
649
676
|
module.define_error("InvalidJsonError", error)?;
|
|
650
677
|
|
|
651
678
|
let native = module.define_module("Native")?;
|
|
652
|
-
native.define_singleton_method("compile", function!(compile,
|
|
679
|
+
native.define_singleton_method("compile", function!(compile, 13))?;
|
|
653
680
|
native.define_singleton_method("crate_version", function!(|| corvus::VERSION, 0))?;
|
|
654
681
|
|
|
655
682
|
let validator = module.define_class("Validator", ruby.class_object())?;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "corvus-json-schema"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.89"
|
|
6
6
|
description = "A high-performance JSON Schema evaluator (draft 4 to 2020-12) with results collection and annotations, ported from the Corvus.Text.Json V5 runtime evaluator."
|
|
@@ -13,19 +13,14 @@ keywords = ["json-schema", "validation", "jsonschema", "validator"]
|
|
|
13
13
|
categories = ["encoding", "parser-implementations"]
|
|
14
14
|
# The published crate: the library and its documents. The tests read the repository's JSON-Schema-Test-Suite
|
|
15
15
|
# submodule and the C# metaschemas, so they only run from a checkout. LICENSE is a copy of the repository's (CI checks
|
|
16
|
-
# it matches).
|
|
17
|
-
|
|
18
|
-
include = ["src/**", "Cargo.toml", "README.md", "LICENSE", "THIRD-PARTY-NOTICES.txt"]
|
|
16
|
+
# it matches).
|
|
17
|
+
include = ["src/**", "Cargo.toml", "README.md", "LICENSE"]
|
|
19
18
|
|
|
20
19
|
[dependencies]
|
|
20
|
+
corvus-regex-ecma262 = "0.2.7"
|
|
21
21
|
regex = "1.13.1"
|
|
22
22
|
serde_json = { version = "1.0.151", features = ["preserve_order", "float_roundtrip"] }
|
|
23
23
|
|
|
24
|
-
[lints.rust]
|
|
25
|
-
# A build with this cfg makes every match of the regular expression engine use its memory of failed places from its
|
|
26
|
-
# first step (see src/ecmaregex/vm.rs), which is how the test data is run with the memory in use for every pattern.
|
|
27
|
-
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(corvus_memo_from_the_start)'] }
|
|
28
|
-
|
|
29
24
|
[profile.release]
|
|
30
25
|
codegen-units = 1
|
|
31
26
|
lto = "fat"
|
|
@@ -73,30 +73,35 @@ this way, through CPython's C API.
|
|
|
73
73
|
- **Patterns:** use ECMA-262 semantics. A pattern is compiled with the `u` flag. One that is not valid with it (it has
|
|
74
74
|
`\&` or `\-`, say) is compiled without it, as many validators accept such patterns, and is then matched as
|
|
75
75
|
ECMA-262 says for it: by UTF-16 code unit, so a character beyond the Basic Multilingual Plane is two. The `regex`
|
|
76
|
-
format accepts only the grammar of the flag. Common shapes (class sequences, literals and
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
crate depends on `regex` and `serde_json` alone.
|
|
76
|
+
format accepts only the grammar of the flag, and asks the grammar alone, without the engine's limits of size. Common shapes (class sequences, a few literals and alternatives, line
|
|
77
|
+
lengths) get dedicated matchers, as in the C# evaluator, where each was measured faster than the engine over the
|
|
78
|
+
strings of the jsonschema-benchmark corpora. Every other pattern is matched by
|
|
79
|
+
[`corvus-regex-ecma262`](https://crates.io/crates/corvus-regex-ecma262), an ECMA-262 engine. The crate depends
|
|
80
|
+
on it, `regex` (for the format checks) and `serde_json`.
|
|
82
81
|
|
|
83
82
|
**What is exact.** Everything. A pattern is matched as it is written, whichever matcher has it, and none is
|
|
84
|
-
refused for what an engine cannot do. The answers are V8's for
|
|
85
|
-
[ECMA-262 regular expression test data](https://github.com/corvus-dotnet/ecma262-regex-test-data)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
refused for what an engine cannot do. The engine's answers are V8's for the whole of the
|
|
84
|
+
[ECMA-262 regular expression test data](https://github.com/corvus-dotnet/ecma262-regex-test-data): lookaheads and
|
|
85
|
+
lookbehinds, backreferences into and out of repetitions and lookarounds, named groups, a name shared by groups in
|
|
86
|
+
separate alternatives, modifier groups, repetitions inside repetitions with any count, and case-insensitive
|
|
87
|
+
matching in both grammars.
|
|
88
|
+
|
|
89
|
+
**The grammar of the dialect.** JSON Schema 2019-09 and 2020-12 name the grammar of ECMAScript 2020, so under them a
|
|
90
|
+
pattern is read with `RegexEdition::Es2020`: what later editions added (modifier groups such as `(?i:...)`, a group
|
|
91
|
+
name used by two groups) is not valid, and a schema that uses it is refused. A property escape that is not in the
|
|
92
|
+
tables of 2020 is not valid with the `u` flag, so the pattern is read without it, where `\p` is the letter `p`. Drafts
|
|
93
|
+
4 to 7 read today's grammar. `CompileOptions::regex_edition` chooses another edition and
|
|
94
|
+
`CompileOptions::regex_unicode_version` the version of Unicode (by default the version of the V8 the engine's answers
|
|
95
|
+
are held to, 17 today), for `pattern`, `patternProperties` and the `regex` format alike.
|
|
90
96
|
|
|
91
97
|
**Limits.** A schema and an instance may both come from outside, so a pattern is bounded in what it costs.
|
|
92
98
|
|
|
93
99
|
- *A match has a limit of time.* `CompileOptions::pattern_match_limit` is the time one match against one string may
|
|
94
100
|
take. The default is one second (`DEFAULT_PATTERN_MATCH_LIMIT`), and `None` or zero turns it off for a caller who
|
|
95
101
|
trusts both the schema and the instances. A match that reaches it is abandoned, and so is one whose backtracking
|
|
96
|
-
|
|
97
|
-
`try_validate_json`, `try_evaluate` and the rest) return
|
|
98
|
-
|
|
99
|
-
nothing that allocates and is met within tens of microseconds.
|
|
102
|
+
runs out of the memory the engine allows it. The validation then has no answer: the `try_` functions
|
|
103
|
+
(`try_validate`, `try_validate_json`, `try_evaluate` and the rest) return
|
|
104
|
+
`SchemaEvaluationError::PatternMatchLimit` with the pattern and the limit.
|
|
100
105
|
- *The functions without the new error.* `is_valid` returns a `bool` and fails closed: it returns `false` for a
|
|
101
106
|
match that reached its limit, as it does for the maximum depth, and never `true`. `validate`,
|
|
102
107
|
`validate_instance`, `validate_json` and the `evaluate` functions report it as the one error their type has for
|
|
@@ -107,34 +112,18 @@ this way, through CPython's C API.
|
|
|
107
112
|
counted as more than 1,048,576 as it is read (64 for a term, 32 for an alternative, 8 for a range of a class) is
|
|
108
113
|
refused when the schema is compiled. The message says it is too large, not that it is not valid, and
|
|
109
114
|
`SchemaCompilationError::kind` is `PatternTooLarge`. A pattern of plain characters can be 16,384 characters long.
|
|
115
|
+
These are the engine's limits.
|
|
110
116
|
- *What the crate keeps is bounded.* The cache of compiled patterns of the process holds 1,024 patterns and 64 MiB
|
|
111
|
-
at most
|
|
117
|
+
at most.
|
|
112
118
|
- *The format checks take time proportional to the string.* They read strings of the instance and are not under
|
|
113
119
|
the limit of a match. Dates, times, UUIDs, IP addresses and host names are read by scanners that pass over the
|
|
114
120
|
string once, and the others are regular expressions on the `regex` crate, which is linear. `regex` reads the
|
|
115
|
-
string
|
|
121
|
+
string with the engine's grammar-only reader, without the limits of size. `idn-hostname`, and the domain of an `idn-email`, read nothing of
|
|
116
122
|
a string over 1,012 bytes: an internationalized host name has 253 characters at most, and its check decodes and
|
|
117
123
|
encodes Punycode, which takes time that grows with the square of a label.
|
|
118
124
|
- *What is not bounded.* The limit is for each match, not for a validation. The matchers that read a string once
|
|
119
|
-
are not stopped
|
|
120
|
-
|
|
121
|
-
doubles with each character of some strings.
|
|
122
|
-
|
|
123
|
-
**Where V8 departs from ECMA-262,** the crate follows ECMA-262. With the `u` flag V8 starts a match between the two
|
|
124
|
-
halves of a surrogate pair, compares a case-insensitive backreference to a group of several characters by full
|
|
125
|
-
case folding, and answers some case-insensitive modifier groups differently from the same pattern with the `i`
|
|
126
|
-
flag. The test data marks those records. V8 13.6 also differs in what it takes as a pattern. It takes
|
|
127
|
-
`\p{WSpace}`, a count whose first number is the greater when the numbers are large
|
|
128
|
-
(`a{2147483648,2147483647}`), and a name of two groups that can both take part in one match
|
|
129
|
-
(`(|((?<m>))(?<m>|))`), none of which ECMA-262 allows, and the crate refuses them. It refuses the Script value
|
|
130
|
-
`Hrkt` (`Katakana_Or_Hiragana`), which is in Unicode's list of values, and the crate takes it (no character has
|
|
131
|
-
it, so it matches nothing).
|
|
132
|
-
|
|
133
|
-
**Unicode versions.** Properties and case-insensitive matching use tables of the crate's own, for Unicode 17: the
|
|
134
|
-
same tables as the Go module, and nothing from the Rust standard library or from the `regex` crate, so the answers
|
|
135
|
-
do not depend on the toolchain or on a dependency's release. V8 13.6 has Unicode 16. They agree on every
|
|
136
|
-
character of Unicode 16. The four scripts Unicode 17 adds (`Sidetic`, `Tolong_Siki`, `Beria_Erfe`, `Tai_Yo`) are
|
|
137
|
-
scripts here, where V8 13.6 does not know them. The tables are generated from the Unicode Character Database.
|
|
125
|
+
are not stopped. With the limit turned off, nothing bounds a match: a group read back inside a repetition, as in
|
|
126
|
+
`^(?:(a+)\1*)+$`, takes time that doubles with each character of some strings.
|
|
138
127
|
|
|
139
128
|
**Lone surrogates.** A JSON string with the escape of half of a surrogate pair (`"\ud800"`) is not a Rust string.
|
|
140
129
|
`validate_json` and `serde_json` both refuse the document as not valid JSON.
|
|
@@ -201,22 +190,14 @@ This runs:
|
|
|
201
190
|
- **`tests/results.rs`:** the results-collection expectations shared with the C# and TypeScript evaluators.
|
|
202
191
|
- **`tests/metaschemas.rs`:** checks that `src/metaschemas.rs` matches `src/Corvus.Text.Json/metaschema`.
|
|
203
192
|
Regenerate it with `UPDATE_METASCHEMAS=1 cargo test --test metaschemas`.
|
|
204
|
-
- **The
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
or a matcher answered wrongly at some point, with V8's answers.
|
|
193
|
+
- **The tests of `src/pattern.rs`:** every dedicated matcher agrees with the engine on strings made to probe it,
|
|
194
|
+
and the cache of compiled patterns stays within its bounds.
|
|
195
|
+
- **`tests/pattern_anchors.rs` and `tests/pattern_class.rs`:** patterns that an engine or a matcher answered wrongly
|
|
196
|
+
at some point, with V8's answers.
|
|
209
197
|
- **`tests/pattern_limits.rs`:** the limit of a match and the limits of size, through the public functions.
|
|
210
198
|
- **`tests/allocations.rs`:** validating JSON text allocates nothing in the steady state, with patterns the engine
|
|
211
199
|
matches and with the limit on or off.
|
|
212
200
|
|
|
213
|
-
The Unicode tables are `src/ecmaregex/tables_gen.rs`. `pwsh tools/gen-ucd-tables.ps1` writes them from the tables of
|
|
214
|
-
the Go module (`src-go/corvus-json-schema/internal/ucd/tables_gen.go`), so the two ports hold the same data.
|
|
215
|
-
|
|
216
201
|
## License
|
|
217
202
|
|
|
218
203
|
Apache 2.0.
|
|
219
|
-
|
|
220
|
-
The Unicode tables are derived from the Unicode Character Database, which is under the Unicode License V3.
|
|
221
|
-
[THIRD-PARTY-NOTICES.txt](THIRD-PARTY-NOTICES.txt) has that notice, and the one of the `regress` crate that the
|
|
222
|
-
tables were first read from. The file is in the published crate, beside `LICENSE`.
|
|
@@ -271,6 +271,7 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
|
|
|
271
271
|
(r.dialect, r.vocabularies)
|
|
272
272
|
};
|
|
273
273
|
let legacy = dialect.is_legacy();
|
|
274
|
+
let grammar = crate::pattern::Grammar::of(dialect, self.options);
|
|
274
275
|
|
|
275
276
|
if legacy && let Some(Value::String(r)) = e.get("$ref") {
|
|
276
277
|
// In draft 7 and earlier, $ref replaces every sibling keyword.
|
|
@@ -344,7 +345,8 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
|
|
|
344
345
|
if let Some(Value::Object(pp)) = e.get("patternProperties") {
|
|
345
346
|
let mut list = Vec::with_capacity(pp.len());
|
|
346
347
|
for (pattern, v) in pp {
|
|
347
|
-
let compiled =
|
|
348
|
+
let compiled =
|
|
349
|
+
crate::pattern::compile(pattern, grammar).map_err(|e| e.of(pattern, "patternProperties"))?;
|
|
348
350
|
let c = self.child(target, v, &format!("/patternProperties/{}", escape_pointer_token(pattern)));
|
|
349
351
|
list.push(PatternProperty { pattern: compiled, node: c });
|
|
350
352
|
}
|
|
@@ -467,7 +469,7 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
|
|
|
467
469
|
n.min_length = get_uint(e.get("minLength"));
|
|
468
470
|
n.max_length = get_uint(e.get("maxLength"));
|
|
469
471
|
if let Some(Value::String(p)) = e.get("pattern") {
|
|
470
|
-
n.pattern = Some(crate::pattern::compile(p).map_err(|e| e.of(p, "pattern"))?);
|
|
472
|
+
n.pattern = Some(crate::pattern::compile(p, grammar).map_err(|e| e.of(p, "pattern"))?);
|
|
471
473
|
}
|
|
472
474
|
if let Some(Value::Number(m)) = e.get("multipleOf") {
|
|
473
475
|
n.multiple_of = Some(m.clone());
|
|
@@ -510,7 +512,7 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
|
|
|
510
512
|
|
|
511
513
|
if let Some(Value::String(f)) = e.get("format") {
|
|
512
514
|
n.format = Some(f.clone());
|
|
513
|
-
n.format_kind = FormatKind::of(f, dialect);
|
|
515
|
+
n.format_kind = FormatKind::of(f, dialect, grammar);
|
|
514
516
|
n.assert_format = format_assert;
|
|
515
517
|
}
|
|
516
518
|
|
|
@@ -30,7 +30,8 @@ pub(crate) enum FormatKind {
|
|
|
30
30
|
UriTemplate,
|
|
31
31
|
JsonPointer,
|
|
32
32
|
RelativeJsonPointer,
|
|
33
|
-
|
|
33
|
+
/// The regex format, read in the grammar the dialect reads patterns with.
|
|
34
|
+
Regex(crate::pattern::Grammar),
|
|
34
35
|
// Numeric formats (a Corvus extension).
|
|
35
36
|
Byte,
|
|
36
37
|
UInt16,
|
|
@@ -49,7 +50,7 @@ pub(crate) enum FormatKind {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
impl FormatKind {
|
|
52
|
-
pub fn of(format: &str, dialect: Dialect) -> FormatKind {
|
|
53
|
+
pub fn of(format: &str, dialect: Dialect, grammar: crate::pattern::Grammar) -> FormatKind {
|
|
53
54
|
use FormatKind::*;
|
|
54
55
|
let at_least = |d: Dialect, k: FormatKind| if dialect >= d { k } else { Unknown };
|
|
55
56
|
match format {
|
|
@@ -78,7 +79,7 @@ impl FormatKind {
|
|
|
78
79
|
"json-pointer" => at_least(Dialect::Draft6, JsonPointer),
|
|
79
80
|
"date" => at_least(Dialect::Draft7, Date),
|
|
80
81
|
"time" => at_least(Dialect::Draft7, Time),
|
|
81
|
-
"regex" => at_least(Dialect::Draft7, Regex),
|
|
82
|
+
"regex" => at_least(Dialect::Draft7, Regex(grammar)),
|
|
82
83
|
"relative-json-pointer" => at_least(Dialect::Draft7, RelativeJsonPointer),
|
|
83
84
|
"idn-email" => at_least(Dialect::Draft7, IdnEmail),
|
|
84
85
|
"idn-hostname" => at_least(Dialect::Draft7, IdnHostname),
|
|
@@ -133,7 +134,7 @@ impl FormatKind {
|
|
|
133
134
|
UriTemplate => "uri-template",
|
|
134
135
|
JsonPointer => "json-pointer",
|
|
135
136
|
RelativeJsonPointer => "relative-json-pointer",
|
|
136
|
-
Regex => "regex",
|
|
137
|
+
Regex(_) => "regex",
|
|
137
138
|
Byte => "byte",
|
|
138
139
|
UInt16 => "uint16",
|
|
139
140
|
UInt32 => "uint32",
|
|
@@ -175,7 +176,7 @@ impl FormatKind {
|
|
|
175
176
|
RelativeJsonPointer => {
|
|
176
177
|
"Expected a Relative JSON Pointer. (https://json-schema.org/draft/2020-12/relative-json-pointer)."
|
|
177
178
|
}
|
|
178
|
-
Regex => "Expected a regular expression specification.",
|
|
179
|
+
Regex(_) => "Expected a regular expression specification.",
|
|
179
180
|
_ => return None,
|
|
180
181
|
})
|
|
181
182
|
}
|
|
@@ -208,7 +209,7 @@ impl FormatKind {
|
|
|
208
209
|
UriTemplate => URI_TEMPLATE_RE.is_match(s),
|
|
209
210
|
JsonPointer => JSON_POINTER_RE.is_match(s),
|
|
210
211
|
RelativeJsonPointer => RELATIVE_JSON_POINTER_RE.is_match(s),
|
|
211
|
-
Regex => crate::pattern::is_valid_ecma_regex(s),
|
|
212
|
+
Regex(grammar) => crate::pattern::is_valid_ecma_regex(s, grammar),
|
|
212
213
|
_ => true,
|
|
213
214
|
}
|
|
214
215
|
}
|
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
mod compiler;
|
|
64
64
|
mod dialect;
|
|
65
65
|
mod document;
|
|
66
|
-
mod ecmaregex;
|
|
67
66
|
mod eval;
|
|
68
67
|
mod formats;
|
|
69
68
|
mod instance;
|
|
@@ -86,8 +85,8 @@ pub use document::{JsonDocument, JsonDocumentArray, JsonDocumentObject, JsonDocu
|
|
|
86
85
|
pub use instance::{ArrayView, Instance, Kind, ObjectView, View};
|
|
87
86
|
pub use options::{
|
|
88
87
|
CompileOptions, DEFAULT_PATTERN_MATCH_LIMIT, DocumentResolver, FormatValidator, PatternMatchLimitError,
|
|
89
|
-
PatternTooLargeError, SchemaCompilationError, SchemaCompilationErrorKind,
|
|
90
|
-
SchemaEvaluationError,
|
|
88
|
+
PatternTooLargeError, RegexEdition, RegexUnicodeVersion, SchemaCompilationError, SchemaCompilationErrorKind,
|
|
89
|
+
SchemaEvaluationDepthError, SchemaEvaluationError,
|
|
91
90
|
};
|
|
92
91
|
pub use results::{
|
|
93
92
|
Annotation, JsonSchemaResultsCollector, ResultsLevel, SchemaResult, collect_annotations, encode_pointer_segment,
|
|
@@ -106,7 +105,8 @@ pub enum JsonValidationError {
|
|
|
106
105
|
InvalidJson(JsonParseError),
|
|
107
106
|
/// The validation was abandoned at a limit: it recursed in place beyond the maximum depth, or a match of a
|
|
108
107
|
/// regular expression reached its limit. The name is from when the depth was the one limit, and the message
|
|
109
|
-
/// names both
|
|
108
|
+
/// names both. [`SchemaEvaluationDepthError`] has one value for the two, and the `try_` functions, which return
|
|
109
|
+
/// [`SchemaEvaluationError`], say which it was.
|
|
110
110
|
DepthExceeded(SchemaEvaluationDepthError),
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -8,6 +8,7 @@ use std::time::Duration;
|
|
|
8
8
|
use serde_json::Value;
|
|
9
9
|
|
|
10
10
|
use crate::dialect::Dialect;
|
|
11
|
+
pub use corvus_regex_ecma262::{Edition as RegexEdition, UnicodeVersion as RegexUnicodeVersion};
|
|
11
12
|
|
|
12
13
|
/// Resolves a schema document by absolute URI; returns `None` if it is unknown.
|
|
13
14
|
pub type DocumentResolver = Arc<dyn Fn(&str) -> Option<Value> + Send + Sync>;
|
|
@@ -48,6 +49,13 @@ pub struct CompileOptions {
|
|
|
48
49
|
/// matchers that take time proportional to the string are not stopped: a string is given to one only when it
|
|
49
50
|
/// can answer within the limit.
|
|
50
51
|
pub pattern_match_limit: Option<Duration>,
|
|
52
|
+
/// The edition of ECMA-262 whose grammar every `pattern`, `patternProperties` name and `regex` format value is read
|
|
53
|
+
/// with, or `None` (the default) for each dialect's: ECMAScript 2020 for draft 2019-09 and draft 2020-12, which
|
|
54
|
+
/// cite it, and today's for the earlier drafts. A pattern that is not valid in it is refused when the schema is
|
|
55
|
+
/// compiled.
|
|
56
|
+
pub regex_edition: Option<RegexEdition>,
|
|
57
|
+
/// The version of Unicode the patterns are read with: by default the version of the V8 the answers are held to.
|
|
58
|
+
pub regex_unicode_version: RegexUnicodeVersion,
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/// The time one match of a `pattern` or `patternProperties` regular expression against one string may take, unless
|
|
@@ -67,6 +75,8 @@ impl Default for CompileOptions {
|
|
|
67
75
|
entry_point: None,
|
|
68
76
|
max_depth: 128,
|
|
69
77
|
pattern_match_limit: Some(DEFAULT_PATTERN_MATCH_LIMIT),
|
|
78
|
+
regex_edition: None,
|
|
79
|
+
regex_unicode_version: RegexUnicodeVersion::Default,
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
}
|
|
@@ -84,6 +94,8 @@ impl fmt::Debug for CompileOptions {
|
|
|
84
94
|
.field("entry_point", &self.entry_point)
|
|
85
95
|
.field("max_depth", &self.max_depth)
|
|
86
96
|
.field("pattern_match_limit", &self.pattern_match_limit)
|
|
97
|
+
.field("regex_edition", &self.regex_edition)
|
|
98
|
+
.field("regex_unicode_version", &self.regex_unicode_version)
|
|
87
99
|
.finish()
|
|
88
100
|
}
|
|
89
101
|
}
|