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 +4 -4
- data/CHANGELOG.md +12 -1
- data/Cargo.toml +2 -2
- data/README.md +27 -0
- data/ext/jsonschema/Cargo.lock +13 -13
- data/ext/jsonschema/Cargo.toml +3 -3
- data/lib/jsonschema/version.rb +1 -1
- data/sig/jsonschema.rbs +49 -0
- data/src/canonical.rs +236 -29
- 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: 11217286a118f4602ff911fcb2b4bd68810935fd7bf08fcffdd6ad906804683f
|
|
4
|
+
data.tar.gz: cdbf9193f44e167d360fd34032089368d86dc6971f87fe7118da6549b46efd36
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz: '
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
data/ext/jsonschema/Cargo.lock
CHANGED
|
@@ -693,9 +693,9 @@ dependencies = [
|
|
|
693
693
|
|
|
694
694
|
[[package]]
|
|
695
695
|
name = "jsonschema"
|
|
696
|
-
version = "0.
|
|
696
|
+
version = "0.57.0"
|
|
697
697
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
698
|
-
checksum = "
|
|
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.
|
|
729
|
+
version = "0.57.0"
|
|
730
730
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
731
|
-
checksum = "
|
|
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.
|
|
739
|
+
version = "0.57.0"
|
|
740
740
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
741
|
-
checksum = "
|
|
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.
|
|
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.
|
|
772
|
+
version = "0.57.0"
|
|
773
773
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
774
|
-
checksum = "
|
|
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.
|
|
781
|
+
version = "0.57.0"
|
|
782
782
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
783
|
-
checksum = "
|
|
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.
|
|
1138
|
+
version = "0.57.0"
|
|
1139
1139
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1140
|
-
checksum = "
|
|
1140
|
+
checksum = "b06f6798be4fed305e74df8b1fb90cf59c6b8cc17aa3febeb1830c0c6b627b3e"
|
|
1141
1141
|
dependencies = [
|
|
1142
1142
|
"ahash",
|
|
1143
1143
|
"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.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.
|
|
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.
|
|
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"
|
data/lib/jsonschema/version.rb
CHANGED
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,
|
|
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
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
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
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
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))?;
|