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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +13 -2
  3. data/README.md +6 -4
  4. data/THIRD-PARTY-NOTICES.txt +15 -14
  5. data/VERSIONHISTORY.md +9 -0
  6. data/ext/corvus_json_schema/Cargo.toml +2 -2
  7. data/ext/corvus_json_schema/src/lib.rs +29 -2
  8. data/ext/corvus_json_schema/vendor/corvus-json-schema/Cargo.toml +4 -9
  9. data/ext/corvus_json_schema/vendor/corvus-json-schema/README.md +30 -49
  10. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/compiler.rs +5 -3
  11. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/formats.rs +7 -6
  12. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/lib.rs +4 -4
  13. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/options.rs +12 -0
  14. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/pattern.rs +344 -974
  15. data/lib/corvus_json_schema/version.rb +1 -1
  16. data/lib/corvus_json_schema.rb +8 -3
  17. metadata +2 -15
  18. data/ext/corvus_json_schema/vendor/corvus-json-schema/THIRD-PARTY-NOTICES.txt +0 -93
  19. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/charset.rs +0 -271
  20. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/compile.rs +0 -941
  21. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/dfa.rs +0 -366
  22. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/fold.rs +0 -138
  23. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/mod.rs +0 -329
  24. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/parse.rs +0 -1128
  25. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/re2.rs +0 -129
  26. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tables_gen.rs +0 -6685
  27. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tests.rs +0 -1672
  28. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/ucd.rs +0 -309
  29. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/units.rs +0 -165
  30. 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: 7c2d9281b9ea94c2bb26aee9a8ee85263a955fe854815e40b1c5534de0f46e49
4
- data.tar.gz: cb454e6575a895befccfa1c29e82c8863d0e5e24cfddc9a3364d76c00007b6d8
3
+ metadata.gz: 7e2347892c4bd0c1e41ef9b14747b260c5938d47f1a644f1fdbe0c3992f822a1
4
+ data.tar.gz: 78e9eda166cac2e81b48f87927299d2c092f45b1269179c58ed65495e9cf278f
5
5
  SHA512:
6
- metadata.gz: b9c879b1801c552f58955f4de2bab5b2a2fee5eb2a233762ebf038ef19dd87f6431393d4c6fb9a093e02024106b02b2f109dc268889d7a959047dd3d2e97a35f
7
- data.tar.gz: dce28a0a4889558d6e3040259bb28c3aa77e1773b04f486dc1aeeffb3b942edf354b7ad326efa45b65ac40573a56d70793793880fc8c9d6590a7645425813e99
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.1.7"
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.1.4"
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, whose Unicode tables are derived from the Unicode Character
118
- Database, which is under the Unicode License V3. [THIRD-PARTY-NOTICES.txt](THIRD-PARTY-NOTICES.txt) has that notice,
119
- and the one of the `regress` crate that the tables were first read from. The file is in the source gem and in every
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`.
@@ -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. src/ecmaregex/tables_gen.rs
17
- of that crate holds tables derived from the data files of the Unicode
18
- Character Database, version 17.0.0
19
- (https://www.unicode.org/Public/17.0.0/ucd/): the General_Category values, the
20
- binary properties, the Script and Script_Extensions values, the simple case
21
- folding and the simple uppercase mapping. The data files are "© 2025 Unicode®,
22
- Inc." and are licensed under the Unicode License V3 (SPDX-License-Identifier:
23
- Unicode-3.0), as https://www.unicode.org/license.txt gives it:
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 src/ecmaregex/tables_gen.rs of the corvus-json-schema crate were
69
- written from the tables of the Corvus Go module, which were written from
70
- src/unicodetables.rs of regress 0.12.0. That crate generates the file from the
71
- Unicode Character Database. regress is licensed under the MIT license or the
72
- Apache License, Version 2.0, at the user's option. Neither the gem nor the
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.1.4"
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.1.7" }
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, ResultsLevel, View,
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, 11))?;
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.1.7"
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). THIRD-PARTY-NOTICES.txt has the notice of the Unicode data that src/ecmaregex/tables_gen.rs is derived
17
- # from, which must go wherever the crate goes.
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 their alternatives,
77
- separated lists, line lengths) get dedicated matchers, as in the C# evaluator. Patterns within the common subset of
78
- ECMA-262 and the [`regex`](https://crates.io/crates/regex) crate's syntax are translated and run by `regex`.
79
- Anything else (lookarounds, backreferences, `\B`, modifier groups) is matched by the crate's own ECMA-262 engine
80
- (`src/ecmaregex`), a port of the engine of the Go module, which the Object Pascal and OCaml ports also carry. The
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 every pattern and text of the
85
- [ECMA-262 regular expression test data](https://github.com/corvus-dotnet/ecma262-regex-test-data) that a Rust string
86
- can hold (98,839 patterns, 4,268,313 texts), the records where V8 itself departs from ECMA-262 apart. That covers
87
- lookaheads and lookbehinds, backreferences into and out of repetitions and lookarounds, named groups, a name shared
88
- by groups in separate alternatives, modifier groups, repetitions inside repetitions with any count, and
89
- case-insensitive matching in both grammars.
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
- holds 1,048,576 entries (24 MB). The validation then has no answer: the `try_` functions (`try_validate`,
97
- `try_validate_json`, `try_evaluate` and the rest) return `SchemaEvaluationError::PatternMatchLimit` with the
98
- pattern and the limit. The engine counts its steps and reads the clock every 4,096 of them, so the limit costs
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, and the `regex` crate is given no program over 2 MiB.
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 as a pattern, with the limits of size. `idn-hostname`, and the domain of an `idn-email`, read nothing of
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, and `regex` is given a string only when it can answer within the limit. With the limit turned
120
- off, nothing bounds a match: a group read back inside a repetition, as in `^(?:(a+)\1*)+$`, takes time that
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 regular expression engine's own tests** (`src/ecmaregex/tests.rs`): the tests of the Go package it is a port
205
- of, with V8's answers read from that package's `testdata` (`v8_cases.json`, `v8_oracle.json`) and the sizes the
206
- ports must agree on (`size_cases.json`). One test compares the Unicode tables with the Go module's.
207
- - **`tests/pattern_engine.rs`, `tests/pattern_anchors.rs` and `tests/pattern_class.rs`:** patterns that an engine
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 = crate::pattern::compile(pattern).map_err(|e| e.of(pattern, "patternProperties"))?;
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
- Regex,
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, SchemaEvaluationDepthError,
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 (see [`without_the_limit`] for why the two are one here).
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
  }