jsonschema_rs 0.55.1 → 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 +28 -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 +64 -0
- data/src/canonical.rs +266 -33
- 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,31 @@
|
|
|
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
|
+
|
|
15
|
+
## [0.56.0] - 2026-09-10
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `JSONSchema::Canonical::RawReason`, what stopped a run from modeling a document, and `RawView#reason` and `RawView#pointer`, carrying it beside the subschema the run stopped on.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Canonicalization of a `oneOf` whose branches share a region too wide to expand, which spent the document's whole intersection allowance on that choice and kept the document `Raw`.
|
|
24
|
+
|
|
25
|
+
### Performance
|
|
26
|
+
|
|
27
|
+
- Preparing or building a schema that names no base URI parses the default one once for the process instead of once per schema.
|
|
28
|
+
- Canonicalization reads the facets no checker covers once per node instead of once per containment query.
|
|
29
|
+
|
|
5
30
|
## [0.55.1] - 2026-09-08
|
|
6
31
|
|
|
7
32
|
### Changed
|
|
@@ -552,7 +577,9 @@
|
|
|
552
577
|
|
|
553
578
|
- Initial public release
|
|
554
579
|
|
|
555
|
-
[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
|
|
582
|
+
[0.56.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.55.1...ruby-v0.56.0
|
|
556
583
|
[0.55.1]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.55.0...ruby-v0.55.1
|
|
557
584
|
[0.55.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.54.0...ruby-v0.55.0
|
|
558
585
|
[0.54.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.53.0...ruby-v0.54.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
|
@@ -23,6 +23,10 @@ module JSONSchema
|
|
|
23
23
|
# What an array requires of its elements: all distinct, some repeated, or neither.
|
|
24
24
|
type distinctness = :unconstrained | :all_distinct | :some_repeated
|
|
25
25
|
|
|
26
|
+
# What stopped a run from modeling a document.
|
|
27
|
+
type raw_reason = :unknown_dialect | :unmodeled | :outgrew_intersections
|
|
28
|
+
| :outgrew_cases | :inexact_intersection
|
|
29
|
+
|
|
26
30
|
# Whether one schema admits every value another does. Only `:yes` proves containment: test
|
|
27
31
|
# for `:yes`, and treat `:unknown` like `:no`.
|
|
28
32
|
type containment = :yes | :no | :unknown
|
|
@@ -57,6 +61,15 @@ module JSONSchema
|
|
|
57
61
|
ALL: Array[distinctness]
|
|
58
62
|
end
|
|
59
63
|
|
|
64
|
+
module RawReason
|
|
65
|
+
UNKNOWN_DIALECT: :unknown_dialect
|
|
66
|
+
UNMODELED: :unmodeled
|
|
67
|
+
OUTGREW_INTERSECTIONS: :outgrew_intersections
|
|
68
|
+
OUTGREW_CASES: :outgrew_cases
|
|
69
|
+
INEXACT_INTERSECTION: :inexact_intersection
|
|
70
|
+
ALL: Array[raw_reason]
|
|
71
|
+
end
|
|
72
|
+
|
|
60
73
|
module Kind
|
|
61
74
|
MULTI_TYPE: :multi_type
|
|
62
75
|
TYPED_GROUP: :typed_group
|
|
@@ -288,9 +301,60 @@ module JSONSchema
|
|
|
288
301
|
# A schema the canonical form does not support structurally, kept verbatim.
|
|
289
302
|
class RawView
|
|
290
303
|
def schema: () -> untyped
|
|
304
|
+
def reason: () -> raw_reason
|
|
305
|
+
def pointer: () -> String?
|
|
291
306
|
def inspect: () -> String
|
|
292
307
|
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
293
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]
|
|
294
358
|
end
|
|
295
359
|
|
|
296
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,
|
|
5
|
-
ObjectViolationView as CoreObjectViolationView, Satisfiability,
|
|
3
|
+
CanonicalKind, CanonicalSchema, CanonicalView, CanonicalizationError, Cause as CoreCause,
|
|
4
|
+
Containment, ContainsView as CoreContainsView, Distinctness,
|
|
5
|
+
ObjectViolationView as CoreObjectViolationView, RawReason, Satisfiability,
|
|
6
|
+
UnsatisfiableReason as CoreUnsatisfiableReason,
|
|
6
7
|
},
|
|
7
8
|
JsonType,
|
|
8
9
|
};
|
|
@@ -226,7 +227,13 @@ impl RbCanonicalSchema {
|
|
|
226
227
|
CanonicalView::AnyOf(branches) => ruby.obj_wrap(AnyOfView { branches }).as_value(),
|
|
227
228
|
CanonicalView::OneOf(branches) => ruby.obj_wrap(OneOfView { branches }).as_value(),
|
|
228
229
|
CanonicalView::Reference(uri) => ruby.obj_wrap(ReferenceView { uri }).as_value(),
|
|
229
|
-
CanonicalView::Raw(
|
|
230
|
+
CanonicalView::Raw(raw) => ruby
|
|
231
|
+
.obj_wrap(RawView {
|
|
232
|
+
schema: raw.schema,
|
|
233
|
+
reason: raw.reason,
|
|
234
|
+
pointer: raw.pointer,
|
|
235
|
+
})
|
|
236
|
+
.as_value(),
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
|
|
@@ -293,6 +300,8 @@ impl RbCanonicalSchema {
|
|
|
293
300
|
#[magnus(class = "JSONSchema::Canonical::RawView", free_immediately)]
|
|
294
301
|
pub struct RawView {
|
|
295
302
|
schema: serde_json::Value,
|
|
303
|
+
reason: RawReason,
|
|
304
|
+
pointer: Option<String>,
|
|
296
305
|
}
|
|
297
306
|
|
|
298
307
|
impl DataTypeFunctions for RawView {}
|
|
@@ -302,10 +311,23 @@ impl RawView {
|
|
|
302
311
|
value_to_ruby(ruby, &rb_self.schema)
|
|
303
312
|
}
|
|
304
313
|
|
|
314
|
+
fn reason(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
315
|
+
ruby.sym_new(rb_self.reason.as_str()).as_value()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fn pointer(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
319
|
+
rb_self.pointer.as_ref().map_or_else(
|
|
320
|
+
|| ruby.qnil().as_value(),
|
|
321
|
+
|pointer| ruby.str_new(pointer).as_value(),
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
305
325
|
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
306
326
|
Ok(format!(
|
|
307
|
-
"#<JSONSchema::Canonical::RawView schema={}>",
|
|
308
|
-
Self::schema(ruby, rb_self)?.inspect()
|
|
327
|
+
"#<JSONSchema::Canonical::RawView schema={} reason={} pointer={}>",
|
|
328
|
+
Self::schema(ruby, rb_self)?.inspect(),
|
|
329
|
+
Self::reason(ruby, rb_self).inspect(),
|
|
330
|
+
Self::pointer(ruby, rb_self).inspect()
|
|
309
331
|
))
|
|
310
332
|
}
|
|
311
333
|
|
|
@@ -315,6 +337,8 @@ impl RawView {
|
|
|
315
337
|
ruby.sym_new("schema"),
|
|
316
338
|
value_to_ruby(ruby, &rb_self.schema)?,
|
|
317
339
|
)?;
|
|
340
|
+
hash.aset(ruby.sym_new("reason"), Self::reason(ruby, rb_self))?;
|
|
341
|
+
hash.aset(ruby.sym_new("pointer"), Self::pointer(ruby, rb_self))?;
|
|
318
342
|
Ok(hash)
|
|
319
343
|
}
|
|
320
344
|
}
|
|
@@ -1351,10 +1375,154 @@ impl EnumView {
|
|
|
1351
1375
|
}
|
|
1352
1376
|
}
|
|
1353
1377
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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> {
|
|
1358
1526
|
let base_kwargs: CanonicalKwArgs = get_kwargs(
|
|
1359
1527
|
keywords,
|
|
1360
1528
|
&[],
|
|
@@ -1370,7 +1538,6 @@ fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
|
1370
1538
|
let (draft_val, validate_formats, pattern_options, retriever_val, registry_val, base_uri) =
|
|
1371
1539
|
base_kwargs.optional;
|
|
1372
1540
|
|
|
1373
|
-
let schema_value = to_schema_value(ruby, schema_arg)?;
|
|
1374
1541
|
let mut options = jsonschema::canonical::options();
|
|
1375
1542
|
if let Some(draft) = draft_val {
|
|
1376
1543
|
options = options.with_draft(parse_draft_symbol(ruby, draft)?);
|
|
@@ -1391,31 +1558,61 @@ fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
|
1391
1558
|
has_retriever = true;
|
|
1392
1559
|
}
|
|
1393
1560
|
}
|
|
1394
|
-
if let Some(val) = registry_val {
|
|
1395
|
-
if !val.is_nil() {
|
|
1396
|
-
let registry: &Registry = TryConvert::try_convert(val).map_err(|_| {
|
|
1397
|
-
Error::new(
|
|
1398
|
-
ruby.exception_type_error(),
|
|
1399
|
-
"registry must be a JSONSchema::Registry instance",
|
|
1400
|
-
)
|
|
1401
|
-
})?;
|
|
1402
|
-
if !has_retriever {
|
|
1403
|
-
if let Some(value) = registry.retriever_value(ruby) {
|
|
1404
|
-
if let Some(retriever) = make_retriever(ruby, value)? {
|
|
1405
|
-
options = options.with_retriever(retriever);
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
options = options.with_registry(registry.inner.as_ref());
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
1561
|
if let Some(base_uri) = base_uri {
|
|
1413
1562
|
options = options.with_base_uri(base_uri);
|
|
1414
1563
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
+
})
|
|
1419
1616
|
}
|
|
1420
1617
|
|
|
1421
1618
|
fn define_labels<Label: Copy + Into<&'static str>>(
|
|
@@ -1467,6 +1664,7 @@ pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error>
|
|
|
1467
1664
|
Distinctness::VARIANTS,
|
|
1468
1665
|
)?;
|
|
1469
1666
|
define_labels(ruby, &canonical_module, "Kind", CanonicalKind::VARIANTS)?;
|
|
1667
|
+
define_labels(ruby, &canonical_module, "RawReason", RawReason::VARIANTS)?;
|
|
1470
1668
|
|
|
1471
1669
|
let canonical_schema = canonical_module.define_class("CanonicalSchema", ruby.class_object())?;
|
|
1472
1670
|
canonical_schema.define_method(
|
|
@@ -1687,9 +1885,44 @@ pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error>
|
|
|
1687
1885
|
|
|
1688
1886
|
let raw_view = canonical_module.define_class("RawView", ruby.class_object())?;
|
|
1689
1887
|
raw_view.define_method("schema", method!(RawView::schema, 0))?;
|
|
1888
|
+
raw_view.define_method("reason", method!(RawView::reason, 0))?;
|
|
1889
|
+
raw_view.define_method("pointer", method!(RawView::pointer, 0))?;
|
|
1690
1890
|
raw_view.define_method("inspect", method!(RawView::inspect, 0))?;
|
|
1691
1891
|
raw_view.define_method("deconstruct_keys", method!(RawView::deconstruct_keys, 1))?;
|
|
1692
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
|
+
|
|
1693
1926
|
let json_module = canonical_module.define_module("JSON")?;
|
|
1694
1927
|
json_module
|
|
1695
1928
|
.define_singleton_method("to_string", function!(crate::canonical_json_to_string, 1))?;
|