corvus_json_schema 0.1.3 → 0.1.4

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +2 -12
  3. data/README.md +10 -1
  4. data/THIRD-PARTY-NOTICES.txt +95 -0
  5. data/VERSIONHISTORY.md +24 -0
  6. data/ext/corvus_json_schema/Cargo.toml +2 -2
  7. data/ext/corvus_json_schema/clippy.toml +3 -4
  8. data/ext/corvus_json_schema/src/lib.rs +42 -19
  9. data/ext/corvus_json_schema/vendor/corvus-json-schema/Cargo.toml +9 -4
  10. data/ext/corvus_json_schema/vendor/corvus-json-schema/README.md +87 -4
  11. data/ext/corvus_json_schema/vendor/corvus-json-schema/THIRD-PARTY-NOTICES.txt +93 -0
  12. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/compiler.rs +88 -96
  13. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/document.rs +2 -2
  14. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/charset.rs +271 -0
  15. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/compile.rs +941 -0
  16. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/dfa.rs +366 -0
  17. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/fold.rs +138 -0
  18. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/mod.rs +329 -0
  19. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/parse.rs +1128 -0
  20. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/re2.rs +129 -0
  21. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tables_gen.rs +6685 -0
  22. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tests.rs +1672 -0
  23. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/ucd.rs +309 -0
  24. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/units.rs +165 -0
  25. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/vm.rs +981 -0
  26. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan/fused.rs +13 -9
  27. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan.rs +12 -11
  28. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval.rs +105 -73
  29. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/formats.rs +95 -1
  30. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/lib.rs +165 -30
  31. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/loader.rs +19 -21
  32. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/numbers.rs +1 -1
  33. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/options.rs +199 -4
  34. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/pattern.rs +758 -79
  35. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/uri.rs +4 -4
  36. data/lib/corvus_json_schema/version.rb +1 -1
  37. data/lib/corvus_json_schema.rb +13 -3
  38. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c49a0e22cb2d5d434a1cc48ff218ee2d8a1c75e12721672f8555ec1d7829ea4e
4
- data.tar.gz: da2296f3cf10c8b9cd4e55c5455c4e159ba447530eaafaeda3d1692893d2b71a
3
+ metadata.gz: 7c2d9281b9ea94c2bb26aee9a8ee85263a955fe854815e40b1c5534de0f46e49
4
+ data.tar.gz: cb454e6575a895befccfa1c29e82c8863d0e5e24cfddc9a3364d76c00007b6d8
5
5
  SHA512:
6
- metadata.gz: 131e37b22d753a2c9bd803a333a014796fd9dd0cf66f91a87f83d442b9afb34d6d60960da00d62c213be2e502634e96dcb6222a6f07395b1b245c9a23456a63b
7
- data.tar.gz: 77e5b94a420597102e3c70d138778f9ad0744efdda767d258d47a20131b8b06a9aa571c4c159b8136180693c3c70623aa352d6863b0274e68bff7f340d456916
6
+ metadata.gz: b9c879b1801c552f58955f4de2bab5b2a2fee5eb2a233762ebf038ef19dd87f6431393d4c6fb9a093e02024106b02b2f109dc268889d7a959047dd3d2e97a35f
7
+ data.tar.gz: dce28a0a4889558d6e3040259bb28c3aa77e1773b04f486dc1aeeffb3b942edf354b7ad326efa45b65ac40573a56d70793793880fc8c9d6590a7645425813e99
data/Cargo.lock CHANGED
@@ -63,16 +63,15 @@ dependencies = [
63
63
 
64
64
  [[package]]
65
65
  name = "corvus-json-schema"
66
- version = "0.1.6"
66
+ version = "0.1.7"
67
67
  dependencies = [
68
68
  "regex",
69
- "regress",
70
69
  "serde_json",
71
70
  ]
72
71
 
73
72
  [[package]]
74
73
  name = "corvus_json_schema"
75
- version = "0.1.3"
74
+ version = "0.1.4"
76
75
  dependencies = [
77
76
  "corvus-json-schema",
78
77
  "magnus",
@@ -273,15 +272,6 @@ version = "0.8.11"
273
272
  source = "registry+https://github.com/rust-lang/crates.io-index"
274
273
  checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
275
274
 
276
- [[package]]
277
- name = "regress"
278
- version = "0.12.0"
279
- source = "registry+https://github.com/rust-lang/crates.io-index"
280
- checksum = "32eef8b209c3c1c15dbad02c1f30f9539f00dc7253e0cbcdaae442a50a09d7c1"
281
- dependencies = [
282
- "memchr",
283
- ]
284
-
285
275
  [[package]]
286
276
  name = "rustc-hash"
287
277
  version = "2.1.3"
data/README.md CHANGED
@@ -53,6 +53,7 @@ Its options:
53
53
  | `base_uri` | `nil` | The schema's base URI, when it has no `$id`. |
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
+ | `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. |
56
57
 
57
58
  A `Validator` is immutable and can be shared between threads.
58
59
 
@@ -65,7 +66,10 @@ A `Validator` is immutable and can be shared between threads.
65
66
  schema location.
66
67
 
67
68
  Errors are subclasses of `CorvusJsonSchema::Error`: `CompilationError` (an invalid schema, or a reference that cannot
68
- be resolved), `DepthError` and `InvalidJsonError` (for `valid_json?`). An error raised by a format or resolver callable
69
+ be resolved), `PatternTooLargeError` (a `CompilationError` for a regular expression over a limit of size: 1,048,576
70
+ bytes, 1,000 levels of groups, or about 16,000 terms), `DepthError`, `PatternMatchLimitError` (a match of a regular
71
+ expression reached its limit, so the instance is neither valid nor not valid) and `InvalidJsonError` (for
72
+ `valid_json?`). An error raised by a format or resolver callable
69
73
  propagates.
70
74
 
71
75
  ## Values
@@ -109,3 +113,8 @@ repository's submodule (or `JSON_SCHEMA_TEST_SUITE`).
109
113
  ## License
110
114
 
111
115
  Apache-2.0
116
+
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`.
@@ -0,0 +1,95 @@
1
+ corvus_json_schema uses third-party libraries or other resources that may be
2
+ distributed under licenses different than the corvus_json_schema software.
3
+
4
+ In the event that we accidentally failed to list a required notice, please
5
+ bring it to our attention. Post an issue or email us:
6
+
7
+ licensing@endjin.com
8
+
9
+ The attached notices are provided for information only.
10
+
11
+ License notice for the Unicode Character Database
12
+ -------------------------------
13
+
14
+ The gem is built on the corvus-json-schema Rust crate. The source gem holds
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:
24
+
25
+ UNICODE LICENSE V3
26
+
27
+ COPYRIGHT AND PERMISSION NOTICE
28
+
29
+ Copyright © 1991-2026 Unicode, Inc.
30
+
31
+ NOTICE TO USER: Carefully read the following legal agreement. BY
32
+ DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
33
+ SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
34
+ TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
35
+ DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining a
38
+ copy of data files and any associated documentation (the "Data Files") or
39
+ software and any associated documentation (the "Software") to deal in the
40
+ Data Files or Software without restriction, including without limitation
41
+ the rights to use, copy, modify, merge, publish, distribute, and/or sell
42
+ copies of the Data Files or Software, and to permit persons to whom the
43
+ Data Files or Software are furnished to do so, provided that either (a)
44
+ this copyright and permission notice appear with all copies of the Data
45
+ Files or Software, or (b) this copyright and permission notice appear in
46
+ associated Documentation.
47
+
48
+ THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
49
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
50
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
51
+ THIRD PARTY RIGHTS.
52
+
53
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
54
+ BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
55
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
56
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
57
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
58
+ FILES OR SOFTWARE.
59
+
60
+ Except as contained in this notice, the name of a copyright holder shall
61
+ not be used in advertising or otherwise to promote the sale, use or other
62
+ dealings in these Data Files or Software without prior written
63
+ authorization of the copyright holder.
64
+
65
+ License notice for https://github.com/ridiculousfish/regress
66
+ -------------------------------
67
+
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.
74
+
75
+ MIT License
76
+
77
+ Copyright (c) 2020 ridiculous_fish
78
+
79
+ Permission is hereby granted, free of charge, to any person obtaining a copy
80
+ of this software and associated documentation files (the "Software"), to deal
81
+ in the Software without restriction, including without limitation the rights
82
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83
+ copies of the Software, and to permit persons to whom the Software is
84
+ furnished to do so, subject to the following conditions:
85
+
86
+ The above copyright notice and this permission notice shall be included in all
87
+ copies or substantial portions of the Software.
88
+
89
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
95
+ SOFTWARE.
data/VERSIONHISTORY.md CHANGED
@@ -2,6 +2,30 @@
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.1.4
6
+
7
+ 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.
8
+
9
+ ### The regular expression engine
10
+
11
+ The crate no longer uses the `regress` crate for the patterns with a lookaround, a backreference or a modifier group. It matches them with an engine of its own, a port of the ECMA-262 engine of the Corvus Go module, and matches every pattern as it is written. The crate's [version history](../../src-rs/corvus-json-schema/VERSIONHISTORY.md) lists the patterns that were answered wrongly before (a repetition inside a repetition that lost its count or did not end, a backreference from inside its group, two groups with one name, case-insensitive matching without the `u` flag, a pattern that is valid only without the `u` flag and a character beyond the Basic Multilingual Plane, `\B` beside a character of several bytes, and others), and what was measured: V8's answer for all 4,268,313 texts of the ECMA-262 regular expression test data that can be asked.
12
+
13
+ ### Limits on regular expressions
14
+
15
+ A schema and an instance may both come from outside, so a pattern is now bounded in what it costs. Nothing that was there has changed. There is one new option and there are two new errors.
16
+
17
+ - **A match has a limit of one second.** `pattern_match_limit`, a keyword of `CorvusJsonSchema.compile`, is the seconds one match of a `pattern` or `patternProperties` regular expression against one string may take. The default is `CorvusJsonSchema::DEFAULT_PATTERN_MATCH_LIMIT`, `1.0`. `nil` or `0` turns the limit off for a caller who trusts both the schema and the instances. The limit is on each match, not on the validation.
18
+ - **`CorvusJsonSchema::PatternMatchLimitError`** is raised by `valid?`, `valid_json?` and `evaluate` when a match reached the limit, or when its backtracking held 1,048,576 entries. The instance is then neither valid nor not valid. The message names the pattern and the limit. `compile` can raise it too, because a pattern of `patternProperties` is matched against the property names the schema declares.
19
+ - **`CorvusJsonSchema::PatternTooLargeError`** is raised by `compile` for a schema with a regular expression over a limit of size: 1,048,576 bytes of pattern, 1,000 levels of groups, or 1,048,576 counted for it as it is read (a pattern of plain characters can be 16,384 characters long). It is a `CompilationError`, so code that rescues those still does. A regular expression that is not valid raises `CompilationError` itself, as before.
20
+
21
+ ### Bug fixes
22
+
23
+ - **A separated list with a `$` inside it.** The crate matches a pattern of the form `^item(separator item)*$`, such as `^([a-z]+)(\.[a-z]+)*$`, without a regular expression engine. It dropped a `$` at the end of the first item or of the repeated group, so `^x(,a$)*$` was matched as `^x(,a)*$`. A `$` holds only at the end of the string, so the group of `^x(,a$)*$` can match once at most. `CorvusJsonSchema.compile({ "pattern" => '^x(,a$)*$' }).valid?("x,a,a")` returned `true`, where the instance is not valid. `^x$(,a)*$` took `"x,a"`, and `^(a,$)*b$` took `"a,b"`. `valid?`, `valid_json?` and `evaluate` were all affected, and so was a pattern used for `patternProperties` or `propertyNames`. Such a pattern is now matched by a regular expression engine. An escaped `\$` is a literal character and is matched as before. No other form of pattern was affected. The extension's crate requirement names 0.1.7, so the copy of the crate vendored at build time cannot be an older one.
24
+
25
+ ### Notices
26
+
27
+ - **The notice of the Unicode data is in the gem.** The Unicode tables of the `corvus-json-schema` crate are derived from the Unicode Character Database, which is under the Unicode License V3. That license asks that its notice goes with every copy of the data. `THIRD-PARTY-NOTICES.txt` has it, with the notice of the `regress` crate that the tables were first read from, and is now in the source gem and in every native gem. Versions 0.1.0 to 0.1.3 carried no such notice.
28
+
5
29
  ## V0.1.3
6
30
 
7
31
  V0.1.3 fixes `gem install corvus_json_schema` on Alpine Linux and other musl systems, where RubyGems before 4.0 could install the glibc gem, which does not load there. There are no API changes, and the extension is the one in 0.1.2. Versions 0.1.0 to 0.1.2 are affected.
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "corvus_json_schema"
3
- version = "0.1.3"
3
+ version = "0.1.4"
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.6" }
15
+ corvus = { package = "corvus-json-schema", path = "vendor/corvus-json-schema", version = "0.1.7" }
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"] }
@@ -1,4 +1,3 @@
1
- # Clippy suggests idioms by the minimum Rust version. The declared minimum (rust-version in Cargo.toml) was corrected
2
- # from 1.85 to 1.89 with no change to the source, so the lints stay at the level the source was written to. Raise this
3
- # when the source is next revised for the newer idioms.
4
- msrv = "1.85"
1
+ # Clippy suggests idioms by the minimum Rust version, which is the one declared in Cargo.toml (rust-version). Keep the
2
+ # two the same: clippy warns when they differ, and uses this one.
3
+ msrv = "1.89"
@@ -401,8 +401,14 @@ struct Validator {
401
401
  ruby_formats: bool,
402
402
  }
403
403
 
404
- fn depth_error(ruby: &Ruby, e: corvus::SchemaEvaluationDepthError) -> Error {
405
- raise(ruby, "DepthError", e.to_string())
404
+ /// The exception for an evaluation that gave no answer: it recursed in place beyond the maximum depth, or a match
405
+ /// of a regular expression reached its limit.
406
+ fn evaluation_error(ruby: &Ruby, e: corvus::SchemaEvaluationError) -> Error {
407
+ match e {
408
+ corvus::SchemaEvaluationError::InvalidJson(e) => raise(ruby, "InvalidJsonError", e.to_string()),
409
+ corvus::SchemaEvaluationError::PatternMatchLimit(e) => raise(ruby, "PatternMatchLimitError", e.to_string()),
410
+ other => raise(ruby, "DepthError", other.to_string()),
411
+ }
406
412
  }
407
413
 
408
414
  impl Validator {
@@ -411,15 +417,15 @@ impl Validator {
411
417
  fn check(&self, ruby: &Ruby, value: Value) -> Result<bool, Error> {
412
418
  if !self.ruby_formats {
413
419
  let ctx = Context::new();
414
- let result = self.inner.validate_instance(RbInstance::root(value, &ctx));
420
+ let result = self.inner.try_validate_instance(RbInstance::root(value, &ctx));
415
421
  if !ctx.fallback.get() {
416
- return result.map_err(|e| depth_error(ruby, e));
422
+ return result.map_err(|e| evaluation_error(ruby, e));
417
423
  }
418
424
  }
419
425
  let json = to_json(ruby, value, 0)?;
420
- let result = self.inner.validate(&json);
426
+ let result = self.inner.try_validate(&json);
421
427
  take_callback_error()?;
422
- result.map_err(|e| depth_error(ruby, e))
428
+ result.map_err(|e| evaluation_error(ruby, e))
423
429
  }
424
430
 
425
431
  fn valid(ruby: &Ruby, rb_self: &Self, value: Value) -> Result<bool, Error> {
@@ -435,13 +441,9 @@ impl Validator {
435
441
  } else {
436
442
  str_of(text).ok_or_else(|| raise(ruby, "InvalidJsonError", "the JSON text is not valid UTF-8"))?
437
443
  };
438
- let result = rb_self.inner.validate_json(json);
444
+ let result = rb_self.inner.try_validate_json(json);
439
445
  take_callback_error()?;
440
- match result {
441
- Ok(valid) => Ok(valid),
442
- Err(corvus::JsonValidationError::InvalidJson(e)) => Err(raise(ruby, "InvalidJsonError", e.to_string())),
443
- Err(corvus::JsonValidationError::DepthExceeded(e)) => Err(depth_error(ruby, e)),
444
- }
446
+ result.map_err(|e| evaluation_error(ruby, e))
445
447
  }
446
448
 
447
449
  /// Evaluates into the collector (replacing its results), reporting every keyword at its level.
@@ -449,18 +451,18 @@ impl Validator {
449
451
  let mut c = JsonSchemaResultsCollector::new(collector.level);
450
452
  if !rb_self.ruby_formats {
451
453
  let ctx = Context::new();
452
- let result = rb_self.inner.evaluate_instance(RbInstance::root(value, &ctx), &mut c);
454
+ let result = rb_self.inner.try_evaluate_instance(RbInstance::root(value, &ctx), &mut c);
453
455
  if !ctx.fallback.get() {
454
456
  *collector.inner.borrow_mut() = c;
455
- return result.map_err(|e| depth_error(ruby, e));
457
+ return result.map_err(|e| evaluation_error(ruby, e));
456
458
  }
457
459
  c = JsonSchemaResultsCollector::new(collector.level);
458
460
  }
459
461
  let json = to_json(ruby, value, 0)?;
460
- let result = rb_self.inner.evaluate(&json, &mut c);
462
+ let result = rb_self.inner.try_evaluate(&json, &mut c);
461
463
  take_callback_error()?;
462
464
  *collector.inner.borrow_mut() = c;
463
- result.map_err(|e| depth_error(ruby, e))
465
+ result.map_err(|e| evaluation_error(ruby, e))
464
466
  }
465
467
  }
466
468
 
@@ -478,6 +480,7 @@ fn compile(
478
480
  base_uri: Option<String>,
479
481
  entry_point: Option<String>,
480
482
  max_depth: u32,
483
+ pattern_match_limit: Option<f64>,
481
484
  ) -> Result<Validator, Error> {
482
485
  let schema: Json = match RString::from_value(schema) {
483
486
  Some(text) => serde_json::from_str(&text.to_string()?)
@@ -499,6 +502,17 @@ fn compile(
499
502
  base_uri,
500
503
  entry_point,
501
504
  max_depth,
505
+ // In seconds. nil, zero or less turns the limit off.
506
+ pattern_match_limit: match pattern_match_limit {
507
+ Some(seconds) if seconds.is_nan() => {
508
+ return Err(Error::new(ruby.exception_arg_error(), "pattern_match_limit is not a number of seconds"));
509
+ }
510
+ Some(seconds) if seconds > 0.0 => {
511
+ Some(std::time::Duration::try_from_secs_f64(seconds).unwrap_or(std::time::Duration::MAX))
512
+ .filter(|limit| !limit.is_zero())
513
+ }
514
+ _ => None,
515
+ },
502
516
  ..CompileOptions::default()
503
517
  };
504
518
  let mut ruby_formats = false;
@@ -560,7 +574,12 @@ fn compile(
560
574
  Some(r) => format!("{} ({r})", e.message()),
561
575
  None => e.message().to_owned(),
562
576
  };
563
- Err(raise(ruby, "CompilationError", message))
577
+ let class = match e.kind() {
578
+ corvus::SchemaCompilationErrorKind::PatternTooLarge(_) => "PatternTooLargeError",
579
+ corvus::SchemaCompilationErrorKind::PatternMatchLimit(_) => "PatternMatchLimitError",
580
+ _ => "CompilationError",
581
+ };
582
+ Err(raise(ruby, class, message))
564
583
  }
565
584
  }
566
585
  }
@@ -621,12 +640,16 @@ impl Collector {
621
640
  fn init(ruby: &Ruby) -> Result<(), Error> {
622
641
  let module = ruby.define_module("CorvusJsonSchema")?;
623
642
  let error = module.define_error("Error", ruby.exception_standard_error())?;
624
- module.define_error("CompilationError", error)?;
643
+ let compilation = module.define_error("CompilationError", error)?;
644
+ // A regular expression over a limit of size. It is a CompilationError, so code that rescues those still does.
645
+ module.define_error("PatternTooLargeError", compilation)?;
625
646
  module.define_error("DepthError", error)?;
647
+ // A match of a regular expression that reached its limit: the instance is neither valid nor not valid.
648
+ module.define_error("PatternMatchLimitError", error)?;
626
649
  module.define_error("InvalidJsonError", error)?;
627
650
 
628
651
  let native = module.define_module("Native")?;
629
- native.define_singleton_method("compile", function!(compile, 10))?;
652
+ native.define_singleton_method("compile", function!(compile, 11))?;
630
653
  native.define_singleton_method("crate_version", function!(|| corvus::VERSION, 0))?;
631
654
 
632
655
  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.6"
3
+ version = "0.1.7"
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,14 +13,19 @@ 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
- include = ["src/**", "Cargo.toml", "README.md", "LICENSE"]
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"]
18
19
 
19
20
  [dependencies]
20
21
  regex = "1.13.1"
21
- regress = "0.12.0"
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
+
24
29
  [profile.release]
25
30
  codegen-units = 1
26
31
  lto = "fat"
@@ -70,10 +70,74 @@ this way, through CPython's C API.
70
70
 
71
71
  - **Numbers:** compared exactly. Integers are compared as integers and doubles against integers without rounding.
72
72
  `multipleOf` is decided on decimal forms, so `0.0075` is a multiple of `0.0001`.
73
- - **Patterns:** use ECMA-262 semantics with the `u` flag. Common shapes (class sequences, literals and their
74
- alternatives, separated lists, line lengths) get dedicated matchers, as in the C# evaluator. Patterns within the
75
- common subset of ECMA-262 and the [`regex`](https://crates.io/crates/regex) crate's syntax are translated and run by
76
- `regex`, and anything else by [`regress`](https://crates.io/crates/regress).
73
+ - **Patterns:** use ECMA-262 semantics. A pattern is compiled with the `u` flag. One that is not valid with it (it has
74
+ `\&` or `\-`, say) is compiled without it, as many validators accept such patterns, and is then matched as
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.
82
+
83
+ **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.
90
+
91
+ **Limits.** A schema and an instance may both come from outside, so a pattern is bounded in what it costs.
92
+
93
+ - *A match has a limit of time.* `CompileOptions::pattern_match_limit` is the time one match against one string may
94
+ take. The default is one second (`DEFAULT_PATTERN_MATCH_LIMIT`), and `None` or zero turns it off for a caller who
95
+ 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.
100
+ - *The functions without the new error.* `is_valid` returns a `bool` and fails closed: it returns `false` for a
101
+ match that reached its limit, as it does for the maximum depth, and never `true`. `validate`,
102
+ `validate_instance`, `validate_json` and the `evaluate` functions report it as the one error their type has for
103
+ a validation that was abandoned (`SchemaEvaluationDepthError`, named when the depth was the one limit). Its
104
+ message says the validation was abandoned at a limit, the maximum depth or the limit of a match, and that the
105
+ `try_` functions say which. At a limit the instance is not known to be not valid, whatever `is_valid` returned.
106
+ - *A pattern has limits of size.* A pattern longer than 1,048,576 bytes, nested more than 1,000 groups deep, or
107
+ 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
+ refused when the schema is compiled. The message says it is too large, not that it is not valid, and
109
+ `SchemaCompilationError::kind` is `PatternTooLarge`. A pattern of plain characters can be 16,384 characters long.
110
+ - *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.
112
+ - *The format checks take time proportional to the string.* They read strings of the instance and are not under
113
+ the limit of a match. Dates, times, UUIDs, IP addresses and host names are read by scanners that pass over the
114
+ 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
116
+ a string over 1,012 bytes: an internationalized host name has 253 characters at most, and its check decodes and
117
+ encodes Punycode, which takes time that grows with the square of a label.
118
+ - *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.
138
+
139
+ **Lone surrogates.** A JSON string with the escape of half of a surrogate pair (`"\ud800"`) is not a Rust string.
140
+ `validate_json` and `serde_json` both refuse the document as not valid JSON.
77
141
  - **Formats:** are annotations unless asserted by the vocabulary or by `CompileOptions::assert_format`. The format
78
142
  validators follow the C# implementations (RFC 3339 dates and times, IDN hostnames, IRIs, and so on).
79
143
 
@@ -137,3 +201,22 @@ This runs:
137
201
  - **`tests/results.rs`:** the results-collection expectations shared with the C# and TypeScript evaluators.
138
202
  - **`tests/metaschemas.rs`:** checks that `src/metaschemas.rs` matches `src/Corvus.Text.Json/metaschema`.
139
203
  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.
209
+ - **`tests/pattern_limits.rs`:** the limit of a match and the limits of size, through the public functions.
210
+ - **`tests/allocations.rs`:** validating JSON text allocates nothing in the steady state, with patterns the engine
211
+ matches and with the limit on or off.
212
+
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
+ ## License
217
+
218
+ 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`.
@@ -0,0 +1,93 @@
1
+ corvus-json-schema uses third-party libraries or other resources that may be
2
+ distributed under licenses different than the corvus-json-schema software.
3
+
4
+ In the event that we accidentally failed to list a required notice, please
5
+ bring it to our attention. Post an issue or email us:
6
+
7
+ licensing@endjin.com
8
+
9
+ The attached notices are provided for information only.
10
+
11
+ License notice for the Unicode Character Database
12
+ -------------------------------
13
+
14
+ src/ecmaregex/tables_gen.rs holds tables derived from the data files of
15
+ the Unicode Character Database, version 17.0.0
16
+ (https://www.unicode.org/Public/17.0.0/ucd/): the General_Category values,
17
+ the binary properties, the Script and Script_Extensions values, the simple
18
+ case folding and the simple uppercase mapping. The data files are
19
+ "© 2025 Unicode®, Inc." and are licensed under the Unicode License V3
20
+ (SPDX-License-Identifier: Unicode-3.0), as https://www.unicode.org/license.txt
21
+ gives it:
22
+
23
+ UNICODE LICENSE V3
24
+
25
+ COPYRIGHT AND PERMISSION NOTICE
26
+
27
+ Copyright © 1991-2026 Unicode, Inc.
28
+
29
+ NOTICE TO USER: Carefully read the following legal agreement. BY
30
+ DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
31
+ SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
32
+ TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
33
+ DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a
36
+ copy of data files and any associated documentation (the "Data Files") or
37
+ software and any associated documentation (the "Software") to deal in the
38
+ Data Files or Software without restriction, including without limitation
39
+ the rights to use, copy, modify, merge, publish, distribute, and/or sell
40
+ copies of the Data Files or Software, and to permit persons to whom the
41
+ Data Files or Software are furnished to do so, provided that either (a)
42
+ this copyright and permission notice appear with all copies of the Data
43
+ Files or Software, or (b) this copyright and permission notice appear in
44
+ associated Documentation.
45
+
46
+ THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
47
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
48
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
49
+ THIRD PARTY RIGHTS.
50
+
51
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
52
+ BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
53
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
54
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
55
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
56
+ FILES OR SOFTWARE.
57
+
58
+ Except as contained in this notice, the name of a copyright holder shall
59
+ not be used in advertising or otherwise to promote the sale, use or other
60
+ dealings in these Data Files or Software without prior written
61
+ authorization of the copyright holder.
62
+
63
+ License notice for https://github.com/ridiculousfish/regress
64
+ -------------------------------
65
+
66
+ The tables of src/ecmaregex/tables_gen.rs were written by
67
+ tools/gen-ucd-tables.ps1 from the tables of the Corvus Go module, which were
68
+ written from src/unicodetables.rs of regress 0.12.0. That crate generates the
69
+ file from the Unicode Character Database. regress is licensed under the MIT
70
+ license or the Apache License, Version 2.0, at the user's option. This crate
71
+ does not depend on it.
72
+
73
+ MIT License
74
+
75
+ Copyright (c) 2020 ridiculous_fish
76
+
77
+ Permission is hereby granted, free of charge, to any person obtaining a copy
78
+ of this software and associated documentation files (the "Software"), to deal
79
+ in the Software without restriction, including without limitation the rights
80
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
81
+ copies of the Software, and to permit persons to whom the Software is
82
+ furnished to do so, subject to the following conditions:
83
+
84
+ The above copyright notice and this permission notice shall be included in all
85
+ copies or substantial portions of the Software.
86
+
87
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
88
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
89
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
90
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
91
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
92
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
93
+ SOFTWARE.