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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 968f65f7d26fc9a7e97240b37a09de918bfcc8b8e9df8838298637836e34936b
4
- data.tar.gz: 92301af7adc4c52111553735274aee5311d8b6a24c99c5530ed4c6ed6f3d7139
3
+ metadata.gz: 8eab3d3f6e9ccf2a4ad5359fee53b8d6da2a5693b1e245bb1d1352a238988c5d
4
+ data.tar.gz: 29f0ca7bcf0bf62631378ca5f97a8bd92e356001de903274fde4542f1e4c5387
5
5
  SHA512:
6
- metadata.gz: 55edc52371748802513a6b1b70eca2d3def405cda0caa2cccaadf4bbd0e7d3910830d97f965e4df8fa16ed829db9199299ae6cf325a43a32871558f8dca607fa
7
- data.tar.gz: 3d0dc3033bf542529b078d0ec96b61f1dc2682485bfeb7403ff284de81fe2c0c753baf4d1bb12570edc1b1842e08c928c4ad39f99841bd29244779da13dad7c3
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.49.9...HEAD
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.49.9"
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
- jsonschema = { version = "0.49.9", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
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.
@@ -269,12 +269,12 @@ dependencies = [
269
269
 
270
270
  [[package]]
271
271
  name = "fraction"
272
- version = "0.15.3"
272
+ version = "0.16.0"
273
273
  source = "registry+https://github.com/rust-lang/crates.io-index"
274
- checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7"
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.49.9"
696
+ version = "0.50.0"
697
697
  source = "registry+https://github.com/rust-lang/crates.io-index"
698
- checksum = "59ec8a241beed129f06114aa68007e905ca350e7baeb6e17a7631bb7978d91b2"
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.49.9"
729
+ version = "0.50.0"
730
730
  source = "registry+https://github.com/rust-lang/crates.io-index"
731
- checksum = "6ba6d64053e49c693c9f3fbdc97d0b50011b381fd2653756c783b483b9ca7bfb"
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.49.9"
739
+ version = "0.50.0"
740
740
  source = "registry+https://github.com/rust-lang/crates.io-index"
741
- checksum = "660a4d9630fc867a0e5260f520f3c0dde13e8e83d3d63df50b3c6593188be4da"
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.49.9"
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.49.9"
772
+ version = "0.50.0"
772
773
  source = "registry+https://github.com/rust-lang/crates.io-index"
773
- checksum = "91994f45017ed5e66aa8e59b8415f4cb033a6380d7200387b7cf117595fbdf85"
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.49.9"
781
+ version = "0.50.0"
781
782
  source = "registry+https://github.com/rust-lang/crates.io-index"
782
- checksum = "7ec7637f83e510868ae6ed625f7ebfbbde4554ee8ce49854caa5126a8b9b9ecb"
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.6"
917
+ version = "0.4.8"
917
918
  source = "registry+https://github.com/rust-lang/crates.io-index"
918
- checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
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.49.9"
1136
+ version = "0.50.0"
1136
1137
  source = "registry+https://github.com/rust-lang/crates.io-index"
1137
- checksum = "6efa2154ea6f5ce0fdecdd2a8d18f2fa1a39a8fbba91564f555a592e4dce8278"
1138
+ checksum = "3b228fc6035ae00a9008ba156c0dfc98f0b6ebc5f3c8db7ac7006dcf8e56f15a"
1138
1139
  dependencies = [
1139
1140
  "ahash",
1140
1141
  "fluent-uri",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonschema-rb-ext"
3
- version = "0.49.9"
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.49.9", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
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.49.9"
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]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONSchema
4
- VERSION = "0.49.9"
4
+ VERSION = "0.50.0"
5
5
  end
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 | :any_of | :true | :false | :raw
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 | AnyOfView
24
- | ConstView | EnumView | RawView
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 UnmodeledOperand < CanonicalizationError
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 is_subset_of: (CanonicalSchema other) -> bool?
49
- def negate: () -> CanonicalSchema?
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 satisfiable?: () -> bool
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: () -> (:unconstrained | :all_distinct | :some_repeated)
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` demand of an array; an absent minimum spells the default of one.
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 model structurally, kept verbatim.
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, ContainsView as CoreContainsView,
4
- ObjectViolationView as CoreObjectViolationView,
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!(UNMODELED_OPERAND_CLASS, "UnmodeledOperand");
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::UnmodeledOperand => {
85
- Error::new(ruby.get_inner(&UNMODELED_OPERAND_CLASS), message)
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 satisfiable(rb_self: &Self) -> bool {
125
- rb_self.inner.is_satisfiable()
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 is_subset_of(ruby: &Ruby, rb_self: &Self, other: &Self) -> Result<Option<bool>, Error> {
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
- .is_subset_of(&other.inner)
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 negate(ruby: &Ruby, rb_self: &Self) -> Option<Value> {
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 model structurally, kept verbatim.
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` demand of an array. An absent minimum spells the default of one.
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("UnmodeledOperand", base_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("is_subset_of", method!(RbCanonicalSchema::is_subset_of, 1))?;
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("satisfiable?", method!(RbCanonicalSchema::satisfiable, 0))?;
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))?;
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonschema_rs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.49.9
4
+ version: 0.50.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dygalo