jsonschema_rs 0.56.0 → 0.57.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: e5e2046dda10b3b0e6109b3f7a55667f9d6cb86a38b0f7ddbb80e7d9bc67b25d
4
- data.tar.gz: f3eed3727e42c284573978507158cdd966691a0d55add5a12102bbf206440db3
3
+ metadata.gz: 11217286a118f4602ff911fcb2b4bd68810935fd7bf08fcffdd6ad906804683f
4
+ data.tar.gz: cdbf9193f44e167d360fd34032089368d86dc6971f87fe7118da6549b46efd36
5
5
  SHA512:
6
- metadata.gz: '0632192b1860a29b067638bd11e1c70d71e58d39ff5a100514ae72b183c9f46784a466ef59ee80ab5f151e700df4eb8b3a10e3b2a2b7b062b301094b653c07f9'
7
- data.tar.gz: b9155ae7dae3ce0b2559733bc53c938f0129a696dc43d7a7913cfc850967583d9144a0c6f63940b44ec2c45b2d3b7b14ec110263f0daed0027a581174630474c
6
+ metadata.gz: '0875f5b26b56167b3e71a6278620d72db85ea80d5c8b14fa44197c5cfc36b12ec85703c809ed07bd5bb847dcebfc018a18cecb1d5727c05809ff50af3903a85d'
7
+ data.tar.gz: 537158f7caeba6c57e103b6630869cd78b929c2e3ffee0e12c7b267797b89449fb97446d7d163040e65a9e39c9d1916e61e298441ccb01ea90061d447a923033
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.57.0] - 2026-09-22
6
+
7
+ ### Added
8
+
9
+ - `Canonical.find_unsatisfiable`, naming every subschema of a document that admits no value, and the keywords that leave it empty.
10
+
11
+ ### Fixed
12
+
13
+ - The instance path of a schema build error, which was empty instead of naming the keyword location that failed to compile.
14
+
5
15
  ## [0.56.0] - 2026-09-10
6
16
 
7
17
  ### Added
@@ -567,7 +577,8 @@
567
577
 
568
578
  - Initial public release
569
579
 
570
- [Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.56.0...HEAD
580
+ [Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.57.0...HEAD
581
+ [0.57.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.56.0...ruby-v0.57.0
571
582
  [0.56.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.55.1...ruby-v0.56.0
572
583
  [0.55.1]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.55.0...ruby-v0.55.1
573
584
  [0.55.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.54.0...ruby-v0.55.0
data/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonschema-rb"
3
- version = "0.56.0"
3
+ version = "0.57.0"
4
4
  edition = "2021"
5
5
  authors = ["Dmitry Dygalo <dmitry@dygalo.dev>"]
6
6
  license = "MIT"
@@ -14,7 +14,7 @@ crate-type = ["cdylib"]
14
14
 
15
15
  [dependencies]
16
16
  strum = "0.28.0"
17
- jsonschema = { version = "0.56.0", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros", "idna"] }
17
+ jsonschema = { version = "0.57.0", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros", "idna"] }
18
18
  magnus = { version = "0.8", features = ["rb-sys"] }
19
19
  rb-sys = "0.9"
20
20
  serde = { workspace = true }
data/README.md CHANGED
@@ -371,6 +371,33 @@ Compare a request schema new-against-old and a response schema old-against-new:
371
371
 
372
372
  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`.
373
373
 
374
+ ### Finding the dead subschemas of a document
375
+
376
+ `JSONSchema::Canonical.find_unsatisfiable` walks a whole document and answers which of its subschemas admit no value, and why - the keywords at fault and where they sit:
377
+
378
+ ```ruby
379
+ reasons = JSONSchema::Canonical.find_unsatisfiable(
380
+ {
381
+ "properties" => {
382
+ "tag" => { "type" => "string", "minLength" => 5, "maxLength" => 2 },
383
+ "name" => { "type" => "string" }
384
+ }
385
+ }
386
+ )
387
+
388
+ case reasons["/properties/tag"]
389
+ in JSONSchema::Canonical::ConflictReason[causes:]
390
+ causes.map { |cause| [cause.pointer, cause.keywords] }
391
+ # => [["/properties/tag", ["type"]], ["/properties/tag", ["minLength", "maxLength"]]]
392
+ end
393
+
394
+ # A live subschema is not reported
395
+ reasons.key?("/properties/name")
396
+ # => false
397
+ ```
398
+
399
+ A reason is a `LiteralReason` (written as `false`), an `EmptyReason` (one part admits nothing by itself) or a `ConflictReason` (each part admits values, together they admit none). A pointer left out is not proven satisfiable: a document canonicalization cannot model reports nothing, as `satisfiability` answers `:unknown`.
400
+
374
401
  ## Schema Bundling and Dereferencing
375
402
 
376
403
  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.
@@ -693,9 +693,9 @@ dependencies = [
693
693
 
694
694
  [[package]]
695
695
  name = "jsonschema"
696
- version = "0.56.0"
696
+ version = "0.57.0"
697
697
  source = "registry+https://github.com/rust-lang/crates.io-index"
698
- checksum = "b6a806f80c1f5560431009ce5ec29b59d38f950e0cd7db5f1b8925e6d8104e21"
698
+ checksum = "71160ed5f6dbe36a2d6be79f4ca03ee09a99d2866f340faed49458913449aefa"
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.56.0"
729
+ version = "0.57.0"
730
730
  source = "registry+https://github.com/rust-lang/crates.io-index"
731
- checksum = "97ac41a7411c029f7f9971881ccf12c8190411a0ab5caf53c07b07eabbea253e"
731
+ checksum = "9b5421caa3ea97392a353a9bc57b33ac844a6a128f4c3d11ac6c12cc462072ac"
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.56.0"
739
+ version = "0.57.0"
740
740
  source = "registry+https://github.com/rust-lang/crates.io-index"
741
- checksum = "b29ab02b2d2d1b26f9c7709c7985ced2d163cb570dedd38bd8467ef31e5dbc3c"
741
+ checksum = "7c1e51f90029687e9fe880db5ff56b1a99e66eef2464a862f7a8408aed851168"
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.56.0"
758
+ version = "0.57.0"
759
759
  dependencies = [
760
760
  "jsonschema",
761
761
  "magnus",
@@ -769,18 +769,18 @@ dependencies = [
769
769
 
770
770
  [[package]]
771
771
  name = "jsonschema-regex"
772
- version = "0.56.0"
772
+ version = "0.57.0"
773
773
  source = "registry+https://github.com/rust-lang/crates.io-index"
774
- checksum = "cb862addfa7782108933abcf842fe25334523a9df5b89ea4d9620e3dd7b42181"
774
+ checksum = "48d2120d8466ffcdc1b3be4b88ff0e9191bf5b6b09b1b1af1eef9aff8f465ea3"
775
775
  dependencies = [
776
776
  "regex-syntax",
777
777
  ]
778
778
 
779
779
  [[package]]
780
780
  name = "jsonschema-value"
781
- version = "0.56.0"
781
+ version = "0.57.0"
782
782
  source = "registry+https://github.com/rust-lang/crates.io-index"
783
- checksum = "a05cd404c5ff6e2731dbbf7e290a27417750acfb59e329560a1d0af384c93fb0"
783
+ checksum = "3fbfa40a42415369d940b3848f3ce08099f1f1ac04d73e8580f4d652617c4f44"
784
784
  dependencies = [
785
785
  "ahash",
786
786
  "bytecount",
@@ -1135,9 +1135,9 @@ dependencies = [
1135
1135
 
1136
1136
  [[package]]
1137
1137
  name = "referencing"
1138
- version = "0.56.0"
1138
+ version = "0.57.0"
1139
1139
  source = "registry+https://github.com/rust-lang/crates.io-index"
1140
- checksum = "a3b4a92fac7e28c27de3ad26df2ecb9e652f227b258e845af034052e5b22c96c"
1140
+ checksum = "b06f6798be4fed305e74df8b1fb90cf59c6b8cc17aa3febeb1830c0c6b627b3e"
1141
1141
  dependencies = [
1142
1142
  "ahash",
1143
1143
  "fluent-uri",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonschema-rb-ext"
3
- version = "0.56.0"
3
+ version = "0.57.0"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
@@ -10,10 +10,10 @@ name = "jsonschema_rb"
10
10
  path = "../../src/lib.rs"
11
11
 
12
12
  [dependencies]
13
- jsonschema = { version = "0.56.0", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros", "idna"] }
13
+ jsonschema = { version = "0.57.0", default-features = false, features = ["magnus", "arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros", "idna"] }
14
14
  magnus = { version = "0.8", features = ["rb-sys"] }
15
15
  rb-sys = "0.9"
16
- referencing = "0.56.0"
16
+ referencing = "0.57.0"
17
17
  serde = { version = "1", features = ["derive"] }
18
18
  serde_json = { version = "1", features = ["arbitrary_precision"] }
19
19
  strum = "0.28.0"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONSchema
4
- VERSION = "0.56.0"
4
+ VERSION = "0.57.0"
5
5
  end
data/sig/jsonschema.rbs CHANGED
@@ -306,6 +306,55 @@ module JSONSchema
306
306
  def inspect: () -> String
307
307
  def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
308
308
  end
309
+
310
+ # One part of a schema object, as a reason names it.
311
+ class Cause
312
+ # JSON Pointer of the schema object holding `keywords`, or of the subschema itself.
313
+ def pointer: () -> String
314
+ # Keywords of one family present at `pointer`; empty for a whole subschema.
315
+ def keywords: () -> Array[String]
316
+ def ==: (untyped other) -> bool
317
+ def inspect: () -> String
318
+ def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
319
+ end
320
+
321
+ # The subschema is written as `false`.
322
+ class LiteralReason
323
+ def inspect: () -> String
324
+ def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
325
+ end
326
+
327
+ # One part every value must satisfy admits nothing by itself. A subschema part has a reason
328
+ # of its own under its pointer.
329
+ class EmptyReason
330
+ def cause: () -> Cause
331
+ def inspect: () -> String
332
+ def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
333
+ end
334
+
335
+ # Each part admits values; no value satisfies all of them together.
336
+ class ConflictReason
337
+ def causes: () -> Array[Cause]
338
+ def inspect: () -> String
339
+ def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
340
+ end
341
+
342
+ # Why a subschema admits no value.
343
+ type unsatisfiable_reason = LiteralReason | EmptyReason | ConflictReason
344
+
345
+ # The subschemas that admit no value, by JSON Pointer, each with why.
346
+ #
347
+ # A pointer left out is not proven satisfiable: a document the canonical form does not model
348
+ # reports nothing, as `satisfiability` answers `:unknown`.
349
+ def self.find_unsatisfiable: (
350
+ untyped schema,
351
+ ?draft: JSONSchema::draft?,
352
+ ?validate_formats: bool?,
353
+ ?pattern_options: (RegexOptions | FancyRegexOptions)?,
354
+ ?retriever: (^(String) -> untyped)?,
355
+ ?registry: Registry?,
356
+ ?base_uri: String?
357
+ ) -> Hash[String, unsatisfiable_reason]
309
358
  end
310
359
 
311
360
  # Reduce a schema to its canonical form.
data/src/canonical.rs CHANGED
@@ -1,8 +1,9 @@
1
1
  use jsonschema::{
2
2
  canonical::{
3
- CanonicalKind, CanonicalSchema, CanonicalView, CanonicalizationError, Containment,
4
- ContainsView as CoreContainsView, Distinctness,
3
+ CanonicalKind, CanonicalSchema, CanonicalView, CanonicalizationError, Cause as CoreCause,
4
+ Containment, ContainsView as CoreContainsView, Distinctness,
5
5
  ObjectViolationView as CoreObjectViolationView, RawReason, Satisfiability,
6
+ UnsatisfiableReason as CoreUnsatisfiableReason,
6
7
  },
7
8
  JsonType,
8
9
  };
@@ -1374,10 +1375,154 @@ impl EnumView {
1374
1375
  }
1375
1376
  }
1376
1377
 
1377
- fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
1378
- let parsed = scan_args::<(Value,), (), (), (), _, ()>(args)?;
1379
- let (schema_arg,) = parsed.required;
1380
- let keywords: RHash = parsed.keywords;
1378
+ /// One part of a schema object, as a reason names it.
1379
+ #[derive(magnus::TypedData)]
1380
+ #[magnus(class = "JSONSchema::Canonical::Cause", free_immediately)]
1381
+ pub struct Cause {
1382
+ pointer: String,
1383
+ keywords: Vec<String>,
1384
+ }
1385
+
1386
+ impl DataTypeFunctions for Cause {}
1387
+
1388
+ impl Cause {
1389
+ fn pointer(ruby: &Ruby, rb_self: &Self) -> Value {
1390
+ ruby.str_new(&rb_self.pointer).as_value()
1391
+ }
1392
+
1393
+ fn keywords(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
1394
+ let array = ruby.ary_new_capa(rb_self.keywords.len());
1395
+ for keyword in &rb_self.keywords {
1396
+ array.push(ruby.str_new(keyword))?;
1397
+ }
1398
+ Ok(array.as_value())
1399
+ }
1400
+
1401
+ fn eq(rb_self: &Self, other: Value) -> bool {
1402
+ let Ok(other_ref) = <&Cause>::try_convert(other) else {
1403
+ return false;
1404
+ };
1405
+ rb_self.pointer == other_ref.pointer && rb_self.keywords == other_ref.keywords
1406
+ }
1407
+
1408
+ fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
1409
+ Ok(format!(
1410
+ "#<JSONSchema::Canonical::Cause pointer={} keywords={}>",
1411
+ Self::pointer(ruby, rb_self).inspect(),
1412
+ Self::keywords(ruby, rb_self)?.inspect()
1413
+ ))
1414
+ }
1415
+
1416
+ fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
1417
+ let hash = ruby.hash_new();
1418
+ hash.aset(ruby.sym_new("pointer"), Self::pointer(ruby, rb_self))?;
1419
+ hash.aset(ruby.sym_new("keywords"), Self::keywords(ruby, rb_self)?)?;
1420
+ Ok(hash)
1421
+ }
1422
+ }
1423
+
1424
+ /// The subschema is written as `false`.
1425
+ #[derive(magnus::TypedData)]
1426
+ #[magnus(class = "JSONSchema::Canonical::LiteralReason", free_immediately)]
1427
+ pub struct LiteralReason;
1428
+
1429
+ impl DataTypeFunctions for LiteralReason {}
1430
+
1431
+ impl LiteralReason {
1432
+ fn inspect(_rb_self: &Self) -> &'static str {
1433
+ "#<JSONSchema::Canonical::LiteralReason>"
1434
+ }
1435
+
1436
+ fn deconstruct_keys(ruby: &Ruby, _rb_self: &Self, _keys: Value) -> RHash {
1437
+ ruby.hash_new()
1438
+ }
1439
+ }
1440
+
1441
+ /// One part every value must satisfy admits nothing by itself. A subschema part has a reason of
1442
+ /// its own under its pointer.
1443
+ #[derive(magnus::TypedData)]
1444
+ #[magnus(class = "JSONSchema::Canonical::EmptyReason", free_immediately)]
1445
+ pub struct EmptyReason {
1446
+ cause: CoreCause,
1447
+ }
1448
+
1449
+ impl DataTypeFunctions for EmptyReason {}
1450
+
1451
+ impl EmptyReason {
1452
+ fn cause(ruby: &Ruby, rb_self: &Self) -> Value {
1453
+ cause_to_ruby(ruby, rb_self.cause.clone())
1454
+ }
1455
+
1456
+ fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
1457
+ format!(
1458
+ "#<JSONSchema::Canonical::EmptyReason cause={}>",
1459
+ Self::cause(ruby, rb_self).inspect()
1460
+ )
1461
+ }
1462
+
1463
+ fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
1464
+ let hash = ruby.hash_new();
1465
+ hash.aset(ruby.sym_new("cause"), Self::cause(ruby, rb_self))?;
1466
+ Ok(hash)
1467
+ }
1468
+ }
1469
+
1470
+ /// Each part admits values; no value satisfies all of them together.
1471
+ #[derive(magnus::TypedData)]
1472
+ #[magnus(class = "JSONSchema::Canonical::ConflictReason", free_immediately)]
1473
+ pub struct ConflictReason {
1474
+ causes: Vec<CoreCause>,
1475
+ }
1476
+
1477
+ impl DataTypeFunctions for ConflictReason {}
1478
+
1479
+ impl ConflictReason {
1480
+ fn causes(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
1481
+ let array = ruby.ary_new_capa(rb_self.causes.len());
1482
+ for cause in &rb_self.causes {
1483
+ array.push(cause_to_ruby(ruby, cause.clone()))?;
1484
+ }
1485
+ Ok(array.as_value())
1486
+ }
1487
+
1488
+ fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
1489
+ Ok(format!(
1490
+ "#<JSONSchema::Canonical::ConflictReason causes={}>",
1491
+ Self::causes(ruby, rb_self)?.inspect()
1492
+ ))
1493
+ }
1494
+
1495
+ fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
1496
+ let hash = ruby.hash_new();
1497
+ hash.aset(ruby.sym_new("causes"), Self::causes(ruby, rb_self)?)?;
1498
+ Ok(hash)
1499
+ }
1500
+ }
1501
+
1502
+ fn cause_to_ruby(ruby: &Ruby, cause: CoreCause) -> Value {
1503
+ ruby.obj_wrap(Cause {
1504
+ pointer: cause.pointer,
1505
+ keywords: cause.keywords,
1506
+ })
1507
+ .as_value()
1508
+ }
1509
+
1510
+ fn reason_to_ruby(ruby: &Ruby, reason: CoreUnsatisfiableReason) -> Value {
1511
+ match reason {
1512
+ CoreUnsatisfiableReason::Literal => ruby.obj_wrap(LiteralReason).as_value(),
1513
+ CoreUnsatisfiableReason::Empty(cause) => ruby.obj_wrap(EmptyReason { cause }).as_value(),
1514
+ CoreUnsatisfiableReason::Conflict(causes) => {
1515
+ ruby.obj_wrap(ConflictReason { causes }).as_value()
1516
+ }
1517
+ }
1518
+ }
1519
+
1520
+ /// Read the keyword arguments every canonicalization entry point takes, and run `call` with them.
1521
+ fn with_canonical_options<R>(
1522
+ ruby: &Ruby,
1523
+ keywords: RHash,
1524
+ call: impl FnOnce(jsonschema::canonical::CanonicalizeOptions<'_>) -> Result<R, Error>,
1525
+ ) -> Result<R, Error> {
1381
1526
  let base_kwargs: CanonicalKwArgs = get_kwargs(
1382
1527
  keywords,
1383
1528
  &[],
@@ -1393,7 +1538,6 @@ fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
1393
1538
  let (draft_val, validate_formats, pattern_options, retriever_val, registry_val, base_uri) =
1394
1539
  base_kwargs.optional;
1395
1540
 
1396
- let schema_value = to_schema_value(ruby, schema_arg)?;
1397
1541
  let mut options = jsonschema::canonical::options();
1398
1542
  if let Some(draft) = draft_val {
1399
1543
  options = options.with_draft(parse_draft_symbol(ruby, draft)?);
@@ -1414,31 +1558,61 @@ fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
1414
1558
  has_retriever = true;
1415
1559
  }
1416
1560
  }
1417
- if let Some(val) = registry_val {
1418
- if !val.is_nil() {
1419
- let registry: &Registry = TryConvert::try_convert(val).map_err(|_| {
1420
- Error::new(
1421
- ruby.exception_type_error(),
1422
- "registry must be a JSONSchema::Registry instance",
1423
- )
1424
- })?;
1425
- if !has_retriever {
1426
- if let Some(value) = registry.retriever_value(ruby) {
1427
- if let Some(retriever) = make_retriever(ruby, value)? {
1428
- options = options.with_retriever(retriever);
1429
- }
1430
- }
1431
- }
1432
- options = options.with_registry(registry.inner.as_ref());
1433
- }
1434
- }
1435
1561
  if let Some(base_uri) = base_uri {
1436
1562
  options = options.with_base_uri(base_uri);
1437
1563
  }
1438
- options
1439
- .canonicalize(&schema_value)
1440
- .map(|inner| ruby.obj_wrap(RbCanonicalSchema { inner }).as_value())
1441
- .map_err(|error| canonicalization_error(ruby, error))
1564
+ let Some(val) = registry_val else {
1565
+ return call(options);
1566
+ };
1567
+ if val.is_nil() {
1568
+ return call(options);
1569
+ }
1570
+ let registry: &Registry = TryConvert::try_convert(val).map_err(|_| {
1571
+ Error::new(
1572
+ ruby.exception_type_error(),
1573
+ "registry must be a JSONSchema::Registry instance",
1574
+ )
1575
+ })?;
1576
+ if !has_retriever {
1577
+ if let Some(value) = registry.retriever_value(ruby) {
1578
+ if let Some(retriever) = make_retriever(ruby, value)? {
1579
+ options = options.with_retriever(retriever);
1580
+ }
1581
+ }
1582
+ }
1583
+ call(options.with_registry(registry.inner.as_ref()))
1584
+ }
1585
+
1586
+ /// The subschemas of `schema` that admit no value, by JSON Pointer, each with why.
1587
+ fn find_unsatisfiable(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
1588
+ let parsed = scan_args::<(Value,), (), (), (), _, ()>(args)?;
1589
+ let (schema_arg,) = parsed.required;
1590
+ let schema_value = to_schema_value(ruby, schema_arg)?;
1591
+ with_canonical_options(ruby, parsed.keywords, |options| {
1592
+ let prepared = options
1593
+ .prepare(&schema_value)
1594
+ .map_err(|error| canonicalization_error(ruby, error))?;
1595
+ let reasons = prepared
1596
+ .unsatisfiable()
1597
+ .map_err(|error| canonicalization_error(ruby, error))?;
1598
+ let hash = ruby.hash_new();
1599
+ for (pointer, reason) in reasons {
1600
+ hash.aset(ruby.str_new(&pointer), reason_to_ruby(ruby, reason))?;
1601
+ }
1602
+ Ok(hash.as_value())
1603
+ })
1604
+ }
1605
+
1606
+ fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
1607
+ let parsed = scan_args::<(Value,), (), (), (), _, ()>(args)?;
1608
+ let (schema_arg,) = parsed.required;
1609
+ let schema_value = to_schema_value(ruby, schema_arg)?;
1610
+ with_canonical_options(ruby, parsed.keywords, |options| {
1611
+ options
1612
+ .canonicalize(&schema_value)
1613
+ .map(|inner| ruby.obj_wrap(RbCanonicalSchema { inner }).as_value())
1614
+ .map_err(|error| canonicalization_error(ruby, error))
1615
+ })
1442
1616
  }
1443
1617
 
1444
1618
  fn define_labels<Label: Copy + Into<&'static str>>(
@@ -1716,6 +1890,39 @@ pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error>
1716
1890
  raw_view.define_method("inspect", method!(RawView::inspect, 0))?;
1717
1891
  raw_view.define_method("deconstruct_keys", method!(RawView::deconstruct_keys, 1))?;
1718
1892
 
1893
+ let cause = canonical_module.define_class("Cause", ruby.class_object())?;
1894
+ cause.define_method("pointer", method!(Cause::pointer, 0))?;
1895
+ cause.define_method("keywords", method!(Cause::keywords, 0))?;
1896
+ cause.define_method("==", method!(Cause::eq, 1))?;
1897
+ cause.define_method("inspect", method!(Cause::inspect, 0))?;
1898
+ cause.define_method("deconstruct_keys", method!(Cause::deconstruct_keys, 1))?;
1899
+
1900
+ let literal_reason = canonical_module.define_class("LiteralReason", ruby.class_object())?;
1901
+ literal_reason.define_method("inspect", method!(LiteralReason::inspect, 0))?;
1902
+ literal_reason.define_method(
1903
+ "deconstruct_keys",
1904
+ method!(LiteralReason::deconstruct_keys, 1),
1905
+ )?;
1906
+
1907
+ let empty_reason = canonical_module.define_class("EmptyReason", ruby.class_object())?;
1908
+ empty_reason.define_method("cause", method!(EmptyReason::cause, 0))?;
1909
+ empty_reason.define_method("inspect", method!(EmptyReason::inspect, 0))?;
1910
+ empty_reason.define_method(
1911
+ "deconstruct_keys",
1912
+ method!(EmptyReason::deconstruct_keys, 1),
1913
+ )?;
1914
+
1915
+ let conflict_reason = canonical_module.define_class("ConflictReason", ruby.class_object())?;
1916
+ conflict_reason.define_method("causes", method!(ConflictReason::causes, 0))?;
1917
+ conflict_reason.define_method("inspect", method!(ConflictReason::inspect, 0))?;
1918
+ conflict_reason.define_method(
1919
+ "deconstruct_keys",
1920
+ method!(ConflictReason::deconstruct_keys, 1),
1921
+ )?;
1922
+
1923
+ canonical_module
1924
+ .define_singleton_method("find_unsatisfiable", function!(find_unsatisfiable, -1))?;
1925
+
1719
1926
  let json_module = canonical_module.define_module("JSON")?;
1720
1927
  json_module
1721
1928
  .define_singleton_method("to_string", function!(crate::canonical_json_to_string, 1))?;
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.56.0
4
+ version: 0.57.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dygalo