jsonschema_rs 0.49.9 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -1
- data/Cargo.toml +3 -2
- data/README.md +78 -0
- data/ext/jsonschema/Cargo.lock +19 -18
- data/ext/jsonschema/Cargo.toml +4 -3
- data/lib/jsonschema/version.rb +1 -1
- data/sig/jsonschema.rbs +96 -10
- data/src/canonical.rs +86 -15
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8eab3d3f6e9ccf2a4ad5359fee53b8d6da2a5693b1e245bb1d1352a238988c5d
|
|
4
|
+
data.tar.gz: 29f0ca7bcf0bf62631378ca5f97a8bd92e356001de903274fde4542f1e4c5387
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a42a2ae9ad830281bfaac0c896ac731b7627ee17388321b84e005f587d1734e289cc21f3794d85c7f0e477be1b8dbc79a401abc9369aaa85ab3eef1c687dee5
|
|
7
|
+
data.tar.gz: 3c3ebba033ddd6e005d60ccec91cfab87c4f6e5b1e4752036d1b58eb8492070f0749fbed3c3c7f0187d5e21cdb1fe6ac7fe1782df9d6661d16f11e38a1fc0eec
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.50.0] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `CanonicalSchema#union` and `CanonicalSchema#subtract`.
|
|
10
|
+
- `JSONSchema::Canonical::UnsupportedResult`, raised where the canonical form does not support a set operation's result.
|
|
11
|
+
- `JSONSchema::Canonical::IncompatibleOperands` where both operands read `#` and it names a different document on each side.
|
|
12
|
+
- `JSONSchema::Canonical::Containment`, `Satisfiability`, `Distinctness` and `Kind`: named constants for the symbols `covers`, `satisfiability`, `kind` and `ArrayView#distinctness` return, each with an `ALL` list.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `CanonicalSchema#satisfiable?` is now `CanonicalSchema#satisfiability`, answering `:yes`, `:no`, or `:unknown` - `:yes` wherever a value can be exhibited, not only for the forms listing their members.
|
|
17
|
+
- `CanonicalSchema#covers` decides through the difference as well: `:no` where the argument keeps values the receiver rejects, `:yes` where nothing is left over.
|
|
18
|
+
- `CanonicalSchema#is_subset_of` is now `CanonicalSchema#covers`, answering `:yes`, `:no`, or `:unknown` for whether the receiver admits every value the argument admits.
|
|
19
|
+
- `CanonicalSchema#negate` raises where it used to return `nil`.
|
|
20
|
+
- `JSONSchema::Canonical::UnmodeledOperand` is now `JSONSchema::Canonical::UnsupportedOperand`, and means only that an operand is a `Raw` pass-through.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Combining nodes of two different documents repointing a `$ref` to `#` at the combined result instead of the document it was written in, including one named by a definition the result keeps.
|
|
25
|
+
- Intersecting a `$ref` whose target is `true` or `false`, which crashed the interpreter.
|
|
26
|
+
- A key constraint left un-narrowed once a run is out of intersections, which crashed the interpreter on the next read of it.
|
|
27
|
+
- `CanonicalSchema#covers` answering `:unknown` for a schema against itself, where that schema is a `$ref`.
|
|
28
|
+
- `CanonicalSchema#union` keeping a `$ref` beside the schema it names, where the other three operations read through it.
|
|
29
|
+
- `CanonicalSchema#covers` and `CanonicalSchema#subtract` cancelling two nodes written the same way whose `#` names a different document.
|
|
30
|
+
- Two results accepting the same values comparing unequal over the part of their documents neither reads.
|
|
31
|
+
- `additionalProperties` reaching a key the pattern map matches when a finite key constraint closes the map, which dropped values both operands accept.
|
|
32
|
+
- A recursive definition stopping every other pointer in the document from being read through.
|
|
33
|
+
- One `$defs` entry read past a wider schema written out at every use, rather than kept as the pointer it was.
|
|
34
|
+
- Set operations rejecting one document canonicalized twice, and a pruned result rejected against the document it came from, where the maps resolve every shared reference the same way.
|
|
35
|
+
- Set operations comparing a `$ref` as a pointer instead of reading through it, so a schema written with a `$ref` did not cancel against the same schema written out.
|
|
36
|
+
- `CanonicalSchema#subtract` asking for a complement where the difference is one of the operands or empty, declining on schemas it can subtract.
|
|
37
|
+
- Set operations keeping `$defs` entries the result no longer references, which then showed up in the emitted schema.
|
|
38
|
+
- `CanonicalSchema#union` declining over an approximated intersection, where the union itself is exact.
|
|
39
|
+
|
|
5
40
|
## [0.49.9] - 2026-08-10
|
|
6
41
|
|
|
7
42
|
### Added
|
|
@@ -387,7 +422,8 @@
|
|
|
387
422
|
|
|
388
423
|
- Initial public release
|
|
389
424
|
|
|
390
|
-
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.
|
|
425
|
+
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.50.0...HEAD
|
|
426
|
+
[0.50.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.49.9...ruby-v0.50.0
|
|
391
427
|
[0.49.9]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.49.8...ruby-v0.49.9
|
|
392
428
|
[0.49.8]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.49.7...ruby-v0.49.8
|
|
393
429
|
[0.49.7]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.49.6...ruby-v0.49.7
|
data/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonschema-rb"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.50.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Dmitry Dygalo <dmitry@dygalo.dev>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -13,7 +13,8 @@ publish = false
|
|
|
13
13
|
crate-type = ["cdylib"]
|
|
14
14
|
|
|
15
15
|
[dependencies]
|
|
16
|
-
|
|
16
|
+
strum = "0.28.0"
|
|
17
|
+
jsonschema = { version = "0.50.0", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
|
|
17
18
|
magnus = { version = "0.8", features = ["rb-sys"] }
|
|
18
19
|
rb-sys = "0.9"
|
|
19
20
|
serde = { workspace = true }
|
data/README.md
CHANGED
|
@@ -50,6 +50,7 @@ end
|
|
|
50
50
|
- 🔧 Custom keywords and format validators
|
|
51
51
|
- ✨ Meta-schema validation for schema documents
|
|
52
52
|
- 📦 Schema bundling into Compound Schema Documents
|
|
53
|
+
- 🧮 Experimental schema canonicalization
|
|
53
54
|
- ♦️ Supports Ruby 3.2, 3.4 and 4.0
|
|
54
55
|
|
|
55
56
|
### Supported drafts
|
|
@@ -291,6 +292,83 @@ dump_a == dump_b # => true
|
|
|
291
292
|
|
|
292
293
|
Main use case: deduplicating equivalent JSON Schemas.
|
|
293
294
|
|
|
295
|
+
## Schema Canonicalization
|
|
296
|
+
|
|
297
|
+
> **Experimental**: the canonicalization API may change in minor releases.
|
|
298
|
+
|
|
299
|
+
`JSONSchema.canonicalize` reduces a schema to a normal form without changing the values it accepts. Schemas that accept the same values reduce to equal `CanonicalSchema` objects, and contradictions reduce to `false`:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
canonical = JSONSchema.canonicalize(
|
|
303
|
+
{ "allOf" => [{ "type" => "integer", "minimum" => 0 }, { "minimum" => 10, "maximum" => 100 }] }
|
|
304
|
+
)
|
|
305
|
+
canonical.to_json_schema
|
|
306
|
+
# => {"$schema"=>"https://json-schema.org/draft/2020-12/schema",
|
|
307
|
+
# "type"=>"integer", "minimum"=>10, "maximum"=>100}
|
|
308
|
+
|
|
309
|
+
# However they were written, equivalent schemas compare equal
|
|
310
|
+
canonical == JSONSchema.canonicalize({ "type" => "integer", "maximum" => 100, "minimum" => 10 })
|
|
311
|
+
# => true
|
|
312
|
+
|
|
313
|
+
# A schema no value can satisfy collapses
|
|
314
|
+
JSONSchema.canonicalize({ "type" => "integer", "minimum" => 10, "maximum" => 5 }).satisfiability
|
|
315
|
+
# => :no, which is JSONSchema::Canonical::Satisfiability::NO
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
A canonical schema *is* the set of values it accepts, so canonical schemas combine as sets. Every emitted schema carries `$schema`, left out of the comments below:
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
positive = JSONSchema.canonicalize({ "type" => "integer", "minimum" => 0 })
|
|
322
|
+
bounded = JSONSchema.canonicalize({ "type" => "integer", "maximum" => 100 })
|
|
323
|
+
|
|
324
|
+
# Every value both admit
|
|
325
|
+
positive.intersect(bounded).to_json_schema
|
|
326
|
+
# => {"type"=>"integer", "minimum"=>0, "maximum"=>100}
|
|
327
|
+
|
|
328
|
+
# Every value either admits
|
|
329
|
+
positive.union(bounded).to_json_schema
|
|
330
|
+
# => {"type"=>"integer"}
|
|
331
|
+
|
|
332
|
+
# Every value `positive` admits and `bounded` rejects
|
|
333
|
+
positive.subtract(bounded).to_json_schema
|
|
334
|
+
# => {"type"=>"integer", "minimum"=>101}
|
|
335
|
+
|
|
336
|
+
# Every value `positive` rejects, across the types it never admitted
|
|
337
|
+
positive.negate.to_json_schema
|
|
338
|
+
# => {"anyOf"=>[{"type"=>["null", "boolean", "string", "array", "object"]},
|
|
339
|
+
# {"type"=>"integer", "maximum"=>-1},
|
|
340
|
+
# {"type"=>"number", "not"=>{"multipleOf"=>1}}]}
|
|
341
|
+
|
|
342
|
+
positive.covers(bounded) # => :no - `bounded` takes negative integers, `positive` does not
|
|
343
|
+
positive.satisfiability # => :yes
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Comparing two versions of a schema
|
|
347
|
+
|
|
348
|
+
The difference answers what editing a schema did to the values it takes: it accepts exactly what the old schema took and the new one turns away, and is unsatisfiable exactly when nothing was lost.
|
|
349
|
+
|
|
350
|
+
```ruby
|
|
351
|
+
old = JSONSchema.canonicalize({ "type" => "string" })
|
|
352
|
+
new = JSONSchema.canonicalize({ "type" => "string", "maxLength" => 50 })
|
|
353
|
+
|
|
354
|
+
# What `new` stopped accepting, as a schema
|
|
355
|
+
old.subtract(new).to_json_schema
|
|
356
|
+
# => {"$schema"=>"https://json-schema.org/draft/2020-12/schema",
|
|
357
|
+
# "type"=>"string", "minLength"=>51}
|
|
358
|
+
|
|
359
|
+
# Nothing is accepted that was not accepted before, so the change only narrows
|
|
360
|
+
new.subtract(old).satisfiability
|
|
361
|
+
# => :no
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Compare a request schema new-against-old and a response schema old-against-new: narrowing a request turns away payloads a caller used to send, widening a response returns values a caller never agreed to read.
|
|
365
|
+
|
|
366
|
+
`:no` on the difference proves nothing was lost. `:unknown` proves nothing either way.
|
|
367
|
+
|
|
368
|
+
`JSONSchema::Canonical::Containment`, `Satisfiability`, `Distinctness` and `Kind` hold these symbols as constants, each with an `ALL` list.
|
|
369
|
+
|
|
370
|
+
Both operands must share one setup - the same draft, format policy, regular-expression engine and definitions - or `IncompatibleOperands` is raised. `UnsupportedOperand` means an operand is a `Raw` pass-through, and `UnsupportedResult` that the canonical form does not support the result. All three live under `JSONSchema::Canonical`.
|
|
371
|
+
|
|
294
372
|
## Schema Bundling
|
|
295
373
|
|
|
296
374
|
Produce a Compound Schema Document ([Appendix B](https://json-schema.org/draft/2020-12/json-schema-core#appendix-B)) by embedding all external `$ref` targets into a draft-appropriate container. The result validates identically to the original.
|
data/ext/jsonschema/Cargo.lock
CHANGED
|
@@ -269,12 +269,12 @@ dependencies = [
|
|
|
269
269
|
|
|
270
270
|
[[package]]
|
|
271
271
|
name = "fraction"
|
|
272
|
-
version = "0.
|
|
272
|
+
version = "0.16.0"
|
|
273
273
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
274
|
-
checksum = "
|
|
274
|
+
checksum = "33fee0259ffdc3d7bd64438b6b08437884d4df5700f9cb8a23b079c3958ae578"
|
|
275
275
|
dependencies = [
|
|
276
|
-
"lazy_static",
|
|
277
276
|
"num",
|
|
277
|
+
"num-bigint",
|
|
278
278
|
]
|
|
279
279
|
|
|
280
280
|
[[package]]
|
|
@@ -693,9 +693,9 @@ dependencies = [
|
|
|
693
693
|
|
|
694
694
|
[[package]]
|
|
695
695
|
name = "jsonschema"
|
|
696
|
-
version = "0.
|
|
696
|
+
version = "0.50.0"
|
|
697
697
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
698
|
-
checksum = "
|
|
698
|
+
checksum = "7bd2225bc56c338f18465ce618e5e56fff1295fcb0a0cb338b28010193f26949"
|
|
699
699
|
dependencies = [
|
|
700
700
|
"ahash",
|
|
701
701
|
"bytecount",
|
|
@@ -726,9 +726,9 @@ dependencies = [
|
|
|
726
726
|
|
|
727
727
|
[[package]]
|
|
728
728
|
name = "jsonschema-macros"
|
|
729
|
-
version = "0.
|
|
729
|
+
version = "0.50.0"
|
|
730
730
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
731
|
-
checksum = "
|
|
731
|
+
checksum = "b98c87ae37713900b4c502fc934846e91ef7f0aded16b73c7b19e47efb257cec"
|
|
732
732
|
dependencies = [
|
|
733
733
|
"jsonschema-macros-core",
|
|
734
734
|
"proc-macro2",
|
|
@@ -736,9 +736,9 @@ dependencies = [
|
|
|
736
736
|
|
|
737
737
|
[[package]]
|
|
738
738
|
name = "jsonschema-macros-core"
|
|
739
|
-
version = "0.
|
|
739
|
+
version = "0.50.0"
|
|
740
740
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
741
|
-
checksum = "
|
|
741
|
+
checksum = "be61ad4d81d0e74cc842ad8f28af0ca2e3e2c15d06fa9a3d091ff29816b780c4"
|
|
742
742
|
dependencies = [
|
|
743
743
|
"fancy-regex",
|
|
744
744
|
"indexmap",
|
|
@@ -755,7 +755,7 @@ dependencies = [
|
|
|
755
755
|
|
|
756
756
|
[[package]]
|
|
757
757
|
name = "jsonschema-rb-ext"
|
|
758
|
-
version = "0.
|
|
758
|
+
version = "0.50.0"
|
|
759
759
|
dependencies = [
|
|
760
760
|
"jsonschema",
|
|
761
761
|
"magnus",
|
|
@@ -763,23 +763,24 @@ dependencies = [
|
|
|
763
763
|
"referencing",
|
|
764
764
|
"serde",
|
|
765
765
|
"serde_json",
|
|
766
|
+
"strum",
|
|
766
767
|
"zmij",
|
|
767
768
|
]
|
|
768
769
|
|
|
769
770
|
[[package]]
|
|
770
771
|
name = "jsonschema-regex"
|
|
771
|
-
version = "0.
|
|
772
|
+
version = "0.50.0"
|
|
772
773
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
773
|
-
checksum = "
|
|
774
|
+
checksum = "b11520e5257651f31068fa2c959ce28b09d8a217ffe42338bc27d690f06c0f8c"
|
|
774
775
|
dependencies = [
|
|
775
776
|
"regex-syntax",
|
|
776
777
|
]
|
|
777
778
|
|
|
778
779
|
[[package]]
|
|
779
780
|
name = "jsonschema-value"
|
|
780
|
-
version = "0.
|
|
781
|
+
version = "0.50.0"
|
|
781
782
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
782
|
-
checksum = "
|
|
783
|
+
checksum = "ccefde026ba6e6e23002c4e1e087b5f411ef5bc90f3e7febb7cf3c498ca8b169"
|
|
783
784
|
dependencies = [
|
|
784
785
|
"ahash",
|
|
785
786
|
"bytecount",
|
|
@@ -913,9 +914,9 @@ dependencies = [
|
|
|
913
914
|
|
|
914
915
|
[[package]]
|
|
915
916
|
name = "num-bigint"
|
|
916
|
-
version = "0.4.
|
|
917
|
+
version = "0.4.8"
|
|
917
918
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
918
|
-
checksum = "
|
|
919
|
+
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
|
919
920
|
dependencies = [
|
|
920
921
|
"num-integer",
|
|
921
922
|
"num-traits",
|
|
@@ -1132,9 +1133,9 @@ dependencies = [
|
|
|
1132
1133
|
|
|
1133
1134
|
[[package]]
|
|
1134
1135
|
name = "referencing"
|
|
1135
|
-
version = "0.
|
|
1136
|
+
version = "0.50.0"
|
|
1136
1137
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1137
|
-
checksum = "
|
|
1138
|
+
checksum = "3b228fc6035ae00a9008ba156c0dfc98f0b6ebc5f3c8db7ac7006dcf8e56f15a"
|
|
1138
1139
|
dependencies = [
|
|
1139
1140
|
"ahash",
|
|
1140
1141
|
"fluent-uri",
|
data/ext/jsonschema/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonschema-rb-ext"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.50.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
publish = false
|
|
6
6
|
|
|
@@ -10,12 +10,13 @@ name = "jsonschema_rb"
|
|
|
10
10
|
path = "../../src/lib.rs"
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
-
jsonschema = { version = "0.
|
|
13
|
+
jsonschema = { version = "0.50.0", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
|
|
14
14
|
magnus = { version = "0.8", features = ["rb-sys"] }
|
|
15
15
|
rb-sys = "0.9"
|
|
16
|
-
referencing = "0.
|
|
16
|
+
referencing = "0.50.0"
|
|
17
17
|
serde = { version = "1", features = ["derive"] }
|
|
18
18
|
serde_json = { version = "1", features = ["arbitrary_precision"] }
|
|
19
|
+
strum = "0.28.0"
|
|
19
20
|
zmij = "1"
|
|
20
21
|
|
|
21
22
|
[workspace]
|
data/lib/jsonschema/version.rb
CHANGED
data/sig/jsonschema.rbs
CHANGED
|
@@ -17,11 +17,64 @@ module JSONSchema
|
|
|
17
17
|
|
|
18
18
|
# Structural discriminant of a canonical schema, one per view class.
|
|
19
19
|
type kind = :multi_type | :typed_group | :string | :integer | :number | :array
|
|
20
|
-
| :object | :const | :enum | :
|
|
20
|
+
| :object | :const | :enum | :not | :all_of | :any_of | :one_of
|
|
21
|
+
| :reference | :true | :false | :raw
|
|
22
|
+
|
|
23
|
+
# What an array requires of its elements: all distinct, some repeated, or neither.
|
|
24
|
+
type distinctness = :unconstrained | :all_distinct | :some_repeated
|
|
25
|
+
|
|
26
|
+
# Whether one schema admits every value another does.
|
|
27
|
+
type containment = :yes | :no | :unknown
|
|
28
|
+
|
|
29
|
+
# Whether any value satisfies a schema.
|
|
30
|
+
type satisfiability = :yes | :no | :unknown
|
|
21
31
|
|
|
22
32
|
type view = TrueView | FalseView | MultiTypeView | TypedGroupView | StringView
|
|
23
|
-
| IntegerView | NumberView | ArrayView | ObjectView |
|
|
24
|
-
|
|
|
33
|
+
| IntegerView | NumberView | ArrayView | ObjectView | NotView
|
|
34
|
+
| AllOfView | AnyOfView | OneOfView | ReferenceView | ConstView
|
|
35
|
+
| EnumView | RawView
|
|
36
|
+
|
|
37
|
+
module Containment
|
|
38
|
+
YES: :yes
|
|
39
|
+
NO: :no
|
|
40
|
+
UNKNOWN: :unknown
|
|
41
|
+
ALL: Array[containment]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module Satisfiability
|
|
45
|
+
YES: :yes
|
|
46
|
+
NO: :no
|
|
47
|
+
UNKNOWN: :unknown
|
|
48
|
+
ALL: Array[satisfiability]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module Distinctness
|
|
52
|
+
UNCONSTRAINED: :unconstrained
|
|
53
|
+
ALL_DISTINCT: :all_distinct
|
|
54
|
+
SOME_REPEATED: :some_repeated
|
|
55
|
+
ALL: Array[distinctness]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module Kind
|
|
59
|
+
MULTI_TYPE: :multi_type
|
|
60
|
+
TYPED_GROUP: :typed_group
|
|
61
|
+
STRING: :string
|
|
62
|
+
INTEGER: :integer
|
|
63
|
+
NUMBER: :number
|
|
64
|
+
ARRAY: :array
|
|
65
|
+
OBJECT: :object
|
|
66
|
+
CONST: :const
|
|
67
|
+
ENUM: :enum
|
|
68
|
+
NOT: :not
|
|
69
|
+
ALL_OF: :all_of
|
|
70
|
+
ANY_OF: :any_of
|
|
71
|
+
ONE_OF: :one_of
|
|
72
|
+
REFERENCE: :reference
|
|
73
|
+
TRUE: :true
|
|
74
|
+
FALSE: :false
|
|
75
|
+
RAW: :raw
|
|
76
|
+
ALL: Array[kind]
|
|
77
|
+
end
|
|
25
78
|
|
|
26
79
|
class CanonicalizationError < StandardError
|
|
27
80
|
end
|
|
@@ -35,7 +88,10 @@ module JSONSchema
|
|
|
35
88
|
class IncompatibleOperands < CanonicalizationError
|
|
36
89
|
end
|
|
37
90
|
|
|
38
|
-
class
|
|
91
|
+
class UnsupportedOperand < CanonicalizationError
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class UnsupportedResult < CanonicalizationError
|
|
39
95
|
end
|
|
40
96
|
|
|
41
97
|
# A schema reduced to canonical form: schemas accepting the same values share one form.
|
|
@@ -45,11 +101,13 @@ module JSONSchema
|
|
|
45
101
|
def kind: () -> kind
|
|
46
102
|
def view: () -> view
|
|
47
103
|
def intersect: (CanonicalSchema other) -> CanonicalSchema
|
|
48
|
-
def
|
|
49
|
-
def
|
|
104
|
+
def union: (CanonicalSchema other) -> CanonicalSchema
|
|
105
|
+
def subtract: (CanonicalSchema other) -> CanonicalSchema
|
|
106
|
+
def covers: (CanonicalSchema other) -> containment
|
|
107
|
+
def negate: () -> CanonicalSchema
|
|
50
108
|
def definition: (String uri) -> CanonicalSchema?
|
|
51
109
|
def definitions: () -> Hash[String, CanonicalSchema]
|
|
52
|
-
def
|
|
110
|
+
def satisfiability: () -> satisfiability
|
|
53
111
|
def inspect: () -> String
|
|
54
112
|
def ==: (untyped other) -> bool
|
|
55
113
|
def eql?: (untyped other) -> bool
|
|
@@ -121,7 +179,7 @@ module JSONSchema
|
|
|
121
179
|
class ArrayView
|
|
122
180
|
def min_items: () -> Integer?
|
|
123
181
|
def max_items: () -> Integer?
|
|
124
|
-
def distinctness: () ->
|
|
182
|
+
def distinctness: () -> distinctness
|
|
125
183
|
def prefix_items: () -> Array[CanonicalSchema]
|
|
126
184
|
def items: () -> CanonicalSchema?
|
|
127
185
|
def contains: () -> Array[ContainsView]
|
|
@@ -129,7 +187,7 @@ module JSONSchema
|
|
|
129
187
|
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
130
188
|
end
|
|
131
189
|
|
|
132
|
-
# One `contains`
|
|
190
|
+
# One `contains` requirement of an array; an absent minimum means the default of one.
|
|
133
191
|
class ContainsView
|
|
134
192
|
def schema: () -> CanonicalSchema
|
|
135
193
|
def min_contains: () -> Integer?
|
|
@@ -168,6 +226,34 @@ module JSONSchema
|
|
|
168
226
|
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
169
227
|
end
|
|
170
228
|
|
|
229
|
+
# The complement of an opaque schema.
|
|
230
|
+
class NotView
|
|
231
|
+
def schema: () -> CanonicalSchema
|
|
232
|
+
def inspect: () -> String
|
|
233
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# A value matches iff every branch matches.
|
|
237
|
+
class AllOfView
|
|
238
|
+
def branches: () -> Array[CanonicalSchema]
|
|
239
|
+
def inspect: () -> String
|
|
240
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# A value matches iff exactly one branch matches.
|
|
244
|
+
class OneOfView
|
|
245
|
+
def branches: () -> Array[CanonicalSchema]
|
|
246
|
+
def inspect: () -> String
|
|
247
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# The target a `$ref` names.
|
|
251
|
+
class ReferenceView
|
|
252
|
+
def uri: () -> String
|
|
253
|
+
def inspect: () -> String
|
|
254
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
255
|
+
end
|
|
256
|
+
|
|
171
257
|
# A value matches iff at least one branch matches.
|
|
172
258
|
class AnyOfView
|
|
173
259
|
def branches: () -> Array[CanonicalSchema]
|
|
@@ -189,7 +275,7 @@ module JSONSchema
|
|
|
189
275
|
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
190
276
|
end
|
|
191
277
|
|
|
192
|
-
# A schema the canonical form does not
|
|
278
|
+
# A schema the canonical form does not support structurally, kept verbatim.
|
|
193
279
|
class RawView
|
|
194
280
|
def schema: () -> untyped
|
|
195
281
|
def inspect: () -> String
|
data/src/canonical.rs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
use jsonschema::{
|
|
2
2
|
canonical::{
|
|
3
|
-
CanonicalSchema, CanonicalView, CanonicalizationError,
|
|
4
|
-
|
|
3
|
+
CanonicalKind, CanonicalSchema, CanonicalView, CanonicalizationError, Containment,
|
|
4
|
+
ContainsView as CoreContainsView, Distinctness,
|
|
5
|
+
ObjectViolationView as CoreObjectViolationView, Satisfiability,
|
|
5
6
|
},
|
|
6
7
|
JsonType,
|
|
7
8
|
};
|
|
9
|
+
use strum::VariantArray;
|
|
10
|
+
|
|
8
11
|
use magnus::{
|
|
9
12
|
function, method,
|
|
10
13
|
prelude::*,
|
|
@@ -64,7 +67,8 @@ canonical_error_class!(CANONICALIZATION_ERROR_CLASS, "CanonicalizationError");
|
|
|
64
67
|
canonical_error_class!(INVALID_SCHEMA_TYPE_CLASS, "InvalidSchemaType");
|
|
65
68
|
canonical_error_class!(INVALID_PATTERN_CLASS, "InvalidPattern");
|
|
66
69
|
canonical_error_class!(INCOMPATIBLE_OPERANDS_CLASS, "IncompatibleOperands");
|
|
67
|
-
canonical_error_class!(
|
|
70
|
+
canonical_error_class!(UNSUPPORTED_OPERAND_CLASS, "UnsupportedOperand");
|
|
71
|
+
canonical_error_class!(UNSUPPORTED_RESULT_CLASS, "UnsupportedResult");
|
|
68
72
|
|
|
69
73
|
fn canonicalization_error(ruby: &Ruby, error: CanonicalizationError) -> Error {
|
|
70
74
|
if let CanonicalizationError::ValidationError(validation_error) = error {
|
|
@@ -81,8 +85,11 @@ fn canonicalization_error(ruby: &Ruby, error: CanonicalizationError) -> Error {
|
|
|
81
85
|
CanonicalizationError::IncompatibleOperands(_) => {
|
|
82
86
|
Error::new(ruby.get_inner(&INCOMPATIBLE_OPERANDS_CLASS), message)
|
|
83
87
|
}
|
|
84
|
-
CanonicalizationError::
|
|
85
|
-
Error::new(ruby.get_inner(&
|
|
88
|
+
CanonicalizationError::UnsupportedOperand => {
|
|
89
|
+
Error::new(ruby.get_inner(&UNSUPPORTED_OPERAND_CLASS), message)
|
|
90
|
+
}
|
|
91
|
+
CanonicalizationError::UnsupportedResult => {
|
|
92
|
+
Error::new(ruby.get_inner(&UNSUPPORTED_RESULT_CLASS), message)
|
|
86
93
|
}
|
|
87
94
|
// `ValidationError` returns above; future variants fall back to the base canonical error.
|
|
88
95
|
_ => Error::new(ruby.get_inner(&CANONICALIZATION_ERROR_CLASS), message),
|
|
@@ -121,8 +128,9 @@ impl RbCanonicalSchema {
|
|
|
121
128
|
ruby.sym_new(rb_self.inner.kind().as_str()).as_value()
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
fn
|
|
125
|
-
rb_self.inner.
|
|
131
|
+
fn satisfiability(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
132
|
+
ruby.sym_new(rb_self.inner.satisfiability().as_str())
|
|
133
|
+
.as_value()
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
|
|
@@ -230,18 +238,36 @@ impl RbCanonicalSchema {
|
|
|
230
238
|
.map_err(|error| canonicalization_error(ruby, error))
|
|
231
239
|
}
|
|
232
240
|
|
|
233
|
-
fn
|
|
241
|
+
fn union(ruby: &Ruby, rb_self: &Self, other: &Self) -> Result<Value, Error> {
|
|
242
|
+
rb_self
|
|
243
|
+
.inner
|
|
244
|
+
.union(&other.inner)
|
|
245
|
+
.map(|inner| ruby.obj_wrap(RbCanonicalSchema { inner }).as_value())
|
|
246
|
+
.map_err(|error| canonicalization_error(ruby, error))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
fn subtract(ruby: &Ruby, rb_self: &Self, other: &Self) -> Result<Value, Error> {
|
|
234
250
|
rb_self
|
|
235
251
|
.inner
|
|
236
|
-
.
|
|
252
|
+
.subtract(&other.inner)
|
|
253
|
+
.map(|inner| ruby.obj_wrap(RbCanonicalSchema { inner }).as_value())
|
|
237
254
|
.map_err(|error| canonicalization_error(ruby, error))
|
|
238
255
|
}
|
|
239
256
|
|
|
240
|
-
fn
|
|
257
|
+
fn covers(ruby: &Ruby, rb_self: &Self, other: &Self) -> Result<Value, Error> {
|
|
258
|
+
rb_self
|
|
259
|
+
.inner
|
|
260
|
+
.covers(&other.inner)
|
|
261
|
+
.map(|containment| ruby.sym_new(containment.as_str()).as_value())
|
|
262
|
+
.map_err(|error| canonicalization_error(ruby, error))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn negate(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
241
266
|
rb_self
|
|
242
267
|
.inner
|
|
243
268
|
.negate()
|
|
244
269
|
.map(|inner| ruby.obj_wrap(RbCanonicalSchema { inner }).as_value())
|
|
270
|
+
.map_err(|error| canonicalization_error(ruby, error))
|
|
245
271
|
}
|
|
246
272
|
|
|
247
273
|
#[allow(clippy::needless_pass_by_value)]
|
|
@@ -262,7 +288,7 @@ impl RbCanonicalSchema {
|
|
|
262
288
|
}
|
|
263
289
|
}
|
|
264
290
|
|
|
265
|
-
/// A schema the canonical form does not
|
|
291
|
+
/// A schema the canonical form does not support structurally, kept verbatim.
|
|
266
292
|
#[derive(magnus::TypedData)]
|
|
267
293
|
#[magnus(class = "JSONSchema::Canonical::RawView", free_immediately)]
|
|
268
294
|
pub struct RawView {
|
|
@@ -741,7 +767,7 @@ impl ArrayView {
|
|
|
741
767
|
}
|
|
742
768
|
}
|
|
743
769
|
|
|
744
|
-
/// One `contains`
|
|
770
|
+
/// One `contains` requirement of an array. An absent minimum means the default of one.
|
|
745
771
|
#[derive(magnus::TypedData)]
|
|
746
772
|
#[magnus(class = "JSONSchema::Canonical::ContainsView", free_immediately)]
|
|
747
773
|
pub struct ContainsView {
|
|
@@ -1345,6 +1371,25 @@ fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
|
1345
1371
|
.map_err(|error| canonicalization_error(ruby, error))
|
|
1346
1372
|
}
|
|
1347
1373
|
|
|
1374
|
+
fn define_labels<Label: Copy + Into<&'static str>>(
|
|
1375
|
+
ruby: &Ruby,
|
|
1376
|
+
canonical_module: &RModule,
|
|
1377
|
+
name: &str,
|
|
1378
|
+
labels: &[Label],
|
|
1379
|
+
) -> Result<(), Error> {
|
|
1380
|
+
let module = canonical_module.define_module(name)?;
|
|
1381
|
+
let all = ruby.ary_new_capa(labels.len());
|
|
1382
|
+
for label in labels {
|
|
1383
|
+
let label: &'static str = (*label).into();
|
|
1384
|
+
let symbol = ruby.sym_new(label).as_value();
|
|
1385
|
+
module.const_set(label.to_uppercase().as_str(), symbol)?;
|
|
1386
|
+
all.push(symbol)?;
|
|
1387
|
+
}
|
|
1388
|
+
all.freeze();
|
|
1389
|
+
module.const_set("ALL", all)?;
|
|
1390
|
+
Ok(())
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1348
1393
|
pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
|
|
1349
1394
|
let canonical_module = module.define_module("Canonical")?;
|
|
1350
1395
|
|
|
@@ -1353,7 +1398,28 @@ pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error>
|
|
|
1353
1398
|
canonical_module.define_error("InvalidSchemaType", base_error)?;
|
|
1354
1399
|
canonical_module.define_error("InvalidPattern", base_error)?;
|
|
1355
1400
|
canonical_module.define_error("IncompatibleOperands", base_error)?;
|
|
1356
|
-
canonical_module.define_error("
|
|
1401
|
+
canonical_module.define_error("UnsupportedOperand", base_error)?;
|
|
1402
|
+
canonical_module.define_error("UnsupportedResult", base_error)?;
|
|
1403
|
+
|
|
1404
|
+
define_labels(
|
|
1405
|
+
ruby,
|
|
1406
|
+
&canonical_module,
|
|
1407
|
+
"Containment",
|
|
1408
|
+
Containment::VARIANTS,
|
|
1409
|
+
)?;
|
|
1410
|
+
define_labels(
|
|
1411
|
+
ruby,
|
|
1412
|
+
&canonical_module,
|
|
1413
|
+
"Satisfiability",
|
|
1414
|
+
Satisfiability::VARIANTS,
|
|
1415
|
+
)?;
|
|
1416
|
+
define_labels(
|
|
1417
|
+
ruby,
|
|
1418
|
+
&canonical_module,
|
|
1419
|
+
"Distinctness",
|
|
1420
|
+
Distinctness::VARIANTS,
|
|
1421
|
+
)?;
|
|
1422
|
+
define_labels(ruby, &canonical_module, "Kind", CanonicalKind::VARIANTS)?;
|
|
1357
1423
|
|
|
1358
1424
|
let canonical_schema = canonical_module.define_class("CanonicalSchema", ruby.class_object())?;
|
|
1359
1425
|
canonical_schema.define_method(
|
|
@@ -1374,11 +1440,16 @@ pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error>
|
|
|
1374
1440
|
)?;
|
|
1375
1441
|
canonical_schema.define_method("view", method!(RbCanonicalSchema::view, 0))?;
|
|
1376
1442
|
canonical_schema.define_method("intersect", method!(RbCanonicalSchema::intersect, 1))?;
|
|
1377
|
-
canonical_schema.define_method("
|
|
1443
|
+
canonical_schema.define_method("union", method!(RbCanonicalSchema::union, 1))?;
|
|
1444
|
+
canonical_schema.define_method("subtract", method!(RbCanonicalSchema::subtract, 1))?;
|
|
1445
|
+
canonical_schema.define_method("covers", method!(RbCanonicalSchema::covers, 1))?;
|
|
1378
1446
|
canonical_schema.define_method("negate", method!(RbCanonicalSchema::negate, 0))?;
|
|
1379
1447
|
canonical_schema.define_method("definition", method!(RbCanonicalSchema::definition, 1))?;
|
|
1380
1448
|
canonical_schema.define_method("definitions", method!(RbCanonicalSchema::definitions, 0))?;
|
|
1381
|
-
canonical_schema.define_method(
|
|
1449
|
+
canonical_schema.define_method(
|
|
1450
|
+
"satisfiability",
|
|
1451
|
+
method!(RbCanonicalSchema::satisfiability, 0),
|
|
1452
|
+
)?;
|
|
1382
1453
|
|
|
1383
1454
|
let true_view = canonical_module.define_class("TrueView", ruby.class_object())?;
|
|
1384
1455
|
true_view.define_method("inspect", method!(TrueView::inspect, 0))?;
|