jsonschema_rs 0.48.1 → 0.48.5
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 +20 -1
- data/Cargo.toml +3 -2
- data/ext/jsonschema/Cargo.lock +56 -11
- data/ext/jsonschema/Cargo.toml +4 -3
- data/lib/jsonschema/version.rb +1 -1
- data/sig/jsonschema.rbs +102 -0
- data/src/canonical.rs +592 -0
- data/src/lib.rs +2 -4
- data/src/ser.rs +18 -3
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60ae1d2ffaa7543f17f5a7953a2ed92520f3887f7e92c8c8ac632f7c56e515ef
|
|
4
|
+
data.tar.gz: 8c3ac5d3f66016a881eed8a6f9f78327e32373e738ab453c39f0449365fce8a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3878552e31d235939c2fc5f4a7665002fa7ee604d4fed06a899ea78679b5ea76183322524b828376e5d891c154426a2fb1167604444eefcb8509afda8b477501
|
|
7
|
+
data.tar.gz: 9e6c6b67a2d3bea2ce93c31c1da0947832708ee55941950860ace90959ad4eb1d66c31e6a66273d17a3bd567f6fec739046c7b18deb6335e326cddfaab5fcdf1
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.48.5] - 2026-07-22
|
|
6
|
+
|
|
7
|
+
### Performance
|
|
8
|
+
|
|
9
|
+
- Avoid map lookups in some `properties` validators.
|
|
10
|
+
- Faster validation of `{"type": "array", "items": {...}}` schemas.
|
|
11
|
+
|
|
12
|
+
## [0.48.2] - 2026-07-21
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `Canonical::JSON.to_string` incorrectly emitting exponent form for small `Float` values (e.g. `1e-7` instead of `0.0000001`).
|
|
17
|
+
|
|
18
|
+
### Performance
|
|
19
|
+
|
|
20
|
+
- Faster validator compilation by pre-sizing internal caches.
|
|
21
|
+
|
|
5
22
|
## [0.48.1] - 2026-07-17
|
|
6
23
|
|
|
7
24
|
### Fixed
|
|
@@ -182,7 +199,9 @@
|
|
|
182
199
|
|
|
183
200
|
- Initial public release
|
|
184
201
|
|
|
185
|
-
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.
|
|
202
|
+
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.5...HEAD
|
|
203
|
+
[0.48.5]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.2...ruby-v0.48.5
|
|
204
|
+
[0.48.2]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.1...ruby-v0.48.2
|
|
186
205
|
[0.48.1]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.0...ruby-v0.48.1
|
|
187
206
|
[0.48.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.47.0...ruby-v0.48.0
|
|
188
207
|
[0.47.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.10...ruby-v0.47.0
|
data/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonschema-rb"
|
|
3
|
-
version = "0.48.
|
|
3
|
+
version = "0.48.5"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Dmitry Dygalo <dmitry@dygalo.dev>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -13,12 +13,13 @@ publish = false
|
|
|
13
13
|
crate-type = ["cdylib"]
|
|
14
14
|
|
|
15
15
|
[dependencies]
|
|
16
|
-
jsonschema = { version = "0.48.
|
|
16
|
+
jsonschema = { version = "0.48.5", default-features = false, features = ["arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
|
|
17
17
|
magnus = { version = "0.8", features = ["rb-sys"] }
|
|
18
18
|
rb-sys = "0.9"
|
|
19
19
|
serde = { workspace = true }
|
|
20
20
|
serde_json = { workspace = true, features = ["arbitrary_precision"] }
|
|
21
21
|
referencing = { version = "*", path = "../jsonschema-referencing" }
|
|
22
|
+
zmij = "1.0.23"
|
|
22
23
|
|
|
23
24
|
[lints]
|
|
24
25
|
workspace = true
|
data/ext/jsonschema/Cargo.lock
CHANGED
|
@@ -387,6 +387,12 @@ dependencies = [
|
|
|
387
387
|
"foldhash",
|
|
388
388
|
]
|
|
389
389
|
|
|
390
|
+
[[package]]
|
|
391
|
+
name = "heck"
|
|
392
|
+
version = "0.5.0"
|
|
393
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
394
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
395
|
+
|
|
390
396
|
[[package]]
|
|
391
397
|
name = "http"
|
|
392
398
|
version = "1.4.0"
|
|
@@ -687,9 +693,9 @@ dependencies = [
|
|
|
687
693
|
|
|
688
694
|
[[package]]
|
|
689
695
|
name = "jsonschema"
|
|
690
|
-
version = "0.48.
|
|
696
|
+
version = "0.48.5"
|
|
691
697
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
692
|
-
checksum = "
|
|
698
|
+
checksum = "f05a6cff806294a7699d48761d4ae2bdebab7fa2eef05e4a0b83320f0ff46901"
|
|
693
699
|
dependencies = [
|
|
694
700
|
"ahash",
|
|
695
701
|
"bytecount",
|
|
@@ -702,6 +708,7 @@ dependencies = [
|
|
|
702
708
|
"itoa",
|
|
703
709
|
"jsonschema-macros",
|
|
704
710
|
"jsonschema-regex",
|
|
711
|
+
"jsonschema-value",
|
|
705
712
|
"num-bigint",
|
|
706
713
|
"num-cmp",
|
|
707
714
|
"num-traits",
|
|
@@ -712,15 +719,16 @@ dependencies = [
|
|
|
712
719
|
"rustls",
|
|
713
720
|
"serde",
|
|
714
721
|
"serde_json",
|
|
722
|
+
"strum",
|
|
715
723
|
"unicode-general-category",
|
|
716
724
|
"uuid-simd",
|
|
717
725
|
]
|
|
718
726
|
|
|
719
727
|
[[package]]
|
|
720
728
|
name = "jsonschema-macros"
|
|
721
|
-
version = "0.48.
|
|
729
|
+
version = "0.48.5"
|
|
722
730
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
723
|
-
checksum = "
|
|
731
|
+
checksum = "40bda6e08b09b66cc0f70c0768118b51e8da7cef847036c88a68471b15101bcb"
|
|
724
732
|
dependencies = [
|
|
725
733
|
"jsonschema-macros-core",
|
|
726
734
|
"proc-macro2",
|
|
@@ -728,9 +736,9 @@ dependencies = [
|
|
|
728
736
|
|
|
729
737
|
[[package]]
|
|
730
738
|
name = "jsonschema-macros-core"
|
|
731
|
-
version = "0.48.
|
|
739
|
+
version = "0.48.5"
|
|
732
740
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
733
|
-
checksum = "
|
|
741
|
+
checksum = "5f884bf73113c53f6b1d23f14c68a09faebf118854ce122a1afe91ce73d050d3"
|
|
734
742
|
dependencies = [
|
|
735
743
|
"fancy-regex",
|
|
736
744
|
"indexmap",
|
|
@@ -747,7 +755,7 @@ dependencies = [
|
|
|
747
755
|
|
|
748
756
|
[[package]]
|
|
749
757
|
name = "jsonschema-rb-ext"
|
|
750
|
-
version = "0.48.
|
|
758
|
+
version = "0.48.5"
|
|
751
759
|
dependencies = [
|
|
752
760
|
"jsonschema",
|
|
753
761
|
"magnus",
|
|
@@ -755,17 +763,33 @@ dependencies = [
|
|
|
755
763
|
"referencing",
|
|
756
764
|
"serde",
|
|
757
765
|
"serde_json",
|
|
766
|
+
"zmij",
|
|
758
767
|
]
|
|
759
768
|
|
|
760
769
|
[[package]]
|
|
761
770
|
name = "jsonschema-regex"
|
|
762
|
-
version = "0.48.
|
|
771
|
+
version = "0.48.5"
|
|
763
772
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
764
|
-
checksum = "
|
|
773
|
+
checksum = "84708dd3f6c5327478a96f37378adb27deed6a626cf84d19de780d9e8ae84b17"
|
|
765
774
|
dependencies = [
|
|
766
775
|
"regex-syntax",
|
|
767
776
|
]
|
|
768
777
|
|
|
778
|
+
[[package]]
|
|
779
|
+
name = "jsonschema-value"
|
|
780
|
+
version = "0.48.5"
|
|
781
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
782
|
+
checksum = "fd8c04ab4ce60e42c4e302d3bd9d184652bb56f212495d2eea23573bcbf72556"
|
|
783
|
+
dependencies = [
|
|
784
|
+
"ahash",
|
|
785
|
+
"bytecount",
|
|
786
|
+
"fraction",
|
|
787
|
+
"num-bigint",
|
|
788
|
+
"num-cmp",
|
|
789
|
+
"num-traits",
|
|
790
|
+
"serde_json",
|
|
791
|
+
]
|
|
792
|
+
|
|
769
793
|
[[package]]
|
|
770
794
|
name = "lazy_static"
|
|
771
795
|
version = "1.5.0"
|
|
@@ -1106,9 +1130,9 @@ dependencies = [
|
|
|
1106
1130
|
|
|
1107
1131
|
[[package]]
|
|
1108
1132
|
name = "referencing"
|
|
1109
|
-
version = "0.48.
|
|
1133
|
+
version = "0.48.5"
|
|
1110
1134
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1111
|
-
checksum = "
|
|
1135
|
+
checksum = "980e77f5cfffc592c0e7dfb6d20770a0b96a6ab94d403de7454ed9799505e4ba"
|
|
1112
1136
|
dependencies = [
|
|
1113
1137
|
"ahash",
|
|
1114
1138
|
"fluent-uri",
|
|
@@ -1424,6 +1448,27 @@ version = "1.2.1"
|
|
|
1424
1448
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1425
1449
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|
1426
1450
|
|
|
1451
|
+
[[package]]
|
|
1452
|
+
name = "strum"
|
|
1453
|
+
version = "0.28.0"
|
|
1454
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1455
|
+
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
|
1456
|
+
dependencies = [
|
|
1457
|
+
"strum_macros",
|
|
1458
|
+
]
|
|
1459
|
+
|
|
1460
|
+
[[package]]
|
|
1461
|
+
name = "strum_macros"
|
|
1462
|
+
version = "0.28.0"
|
|
1463
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1464
|
+
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
|
1465
|
+
dependencies = [
|
|
1466
|
+
"heck",
|
|
1467
|
+
"proc-macro2",
|
|
1468
|
+
"quote",
|
|
1469
|
+
"syn",
|
|
1470
|
+
]
|
|
1471
|
+
|
|
1427
1472
|
[[package]]
|
|
1428
1473
|
name = "subtle"
|
|
1429
1474
|
version = "2.6.1"
|
data/ext/jsonschema/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonschema-rb-ext"
|
|
3
|
-
version = "0.48.
|
|
3
|
+
version = "0.48.5"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
publish = false
|
|
6
6
|
|
|
@@ -10,11 +10,12 @@ name = "jsonschema_rb"
|
|
|
10
10
|
path = "../../src/lib.rs"
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
-
jsonschema = { version = "0.48.
|
|
13
|
+
jsonschema = { version = "0.48.5", default-features = false, features = ["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.48.
|
|
16
|
+
referencing = "0.48.5"
|
|
17
17
|
serde = { version = "1", features = ["derive"] }
|
|
18
18
|
serde_json = { version = "1", features = ["arbitrary_precision"] }
|
|
19
|
+
zmij = "1"
|
|
19
20
|
|
|
20
21
|
[workspace]
|
data/lib/jsonschema/version.rb
CHANGED
data/sig/jsonschema.rbs
CHANGED
|
@@ -14,8 +14,110 @@ module JSONSchema
|
|
|
14
14
|
# Main use case: deduplicate equivalent JSON Schemas by using a stable string form.
|
|
15
15
|
def self.to_string: (untyped object) -> String
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
# Structural discriminant of a canonical schema, one per view class.
|
|
19
|
+
type kind = :multi_type | :typed_group | :string | :integer | :const | :enum
|
|
20
|
+
| :any_of | :true | :false | :raw
|
|
21
|
+
|
|
22
|
+
type view = TrueView | FalseView | MultiTypeView | TypedGroupView | StringView
|
|
23
|
+
| IntegerView | AnyOfView | ConstView | EnumView | RawView
|
|
24
|
+
|
|
25
|
+
class CanonicalizationError < StandardError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class InvalidSchemaType < CanonicalizationError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# A schema reduced to canonical form: schemas accepting the same values share one form.
|
|
32
|
+
class CanonicalSchema
|
|
33
|
+
def to_json_schema: () -> untyped
|
|
34
|
+
def draft: () -> JSONSchema::draft
|
|
35
|
+
def kind: () -> kind
|
|
36
|
+
def view: () -> view
|
|
37
|
+
def definitions: () -> Hash[String, CanonicalSchema]
|
|
38
|
+
def satisfiable?: () -> bool
|
|
39
|
+
def inspect: () -> String
|
|
40
|
+
def ==: (untyped other) -> bool
|
|
41
|
+
def eql?: (untyped other) -> bool
|
|
42
|
+
def hash: () -> Integer
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Matches any value.
|
|
46
|
+
class TrueView
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Matches no value.
|
|
50
|
+
class FalseView
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A value matches iff its JSON type is in the set.
|
|
54
|
+
class MultiTypeView
|
|
55
|
+
def types: () -> Array[Symbol]
|
|
56
|
+
def inspect: () -> String
|
|
57
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# A value matches iff its JSON type is `type_name` and it satisfies `body`.
|
|
61
|
+
class TypedGroupView
|
|
62
|
+
def type_name: () -> Symbol
|
|
63
|
+
def body: () -> CanonicalSchema
|
|
64
|
+
def inspect: () -> String
|
|
65
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# A string value within a length window, matching every pattern.
|
|
69
|
+
class StringView
|
|
70
|
+
def min_length: () -> Integer?
|
|
71
|
+
def max_length: () -> Integer?
|
|
72
|
+
def patterns: () -> Array[String]
|
|
73
|
+
def inspect: () -> String
|
|
74
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# An integer value within a closed interval.
|
|
78
|
+
class IntegerView
|
|
79
|
+
def minimum: () -> Integer?
|
|
80
|
+
def maximum: () -> Integer?
|
|
81
|
+
def inspect: () -> String
|
|
82
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# A value matches iff at least one branch matches.
|
|
86
|
+
class AnyOfView
|
|
87
|
+
def branches: () -> Array[CanonicalSchema]
|
|
88
|
+
def inspect: () -> String
|
|
89
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Exactly one admitted value.
|
|
93
|
+
class ConstView
|
|
94
|
+
def value: () -> untyped
|
|
95
|
+
def inspect: () -> String
|
|
96
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A sorted, deduplicated finite set of admitted values.
|
|
100
|
+
class EnumView
|
|
101
|
+
def values: () -> Array[untyped]
|
|
102
|
+
def inspect: () -> String
|
|
103
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A schema the canonical form does not model structurally, kept verbatim.
|
|
107
|
+
class RawView
|
|
108
|
+
def schema: () -> untyped
|
|
109
|
+
def inspect: () -> String
|
|
110
|
+
def deconstruct_keys: (untyped keys) -> Hash[Symbol, untyped]
|
|
111
|
+
end
|
|
17
112
|
end
|
|
18
113
|
|
|
114
|
+
# Reduce a schema to its canonical form.
|
|
115
|
+
def self.canonicalize: (
|
|
116
|
+
untyped schema,
|
|
117
|
+
?draft: draft?,
|
|
118
|
+
?validate_formats: bool?
|
|
119
|
+
) -> Canonical::CanonicalSchema
|
|
120
|
+
|
|
19
121
|
# Create a validator with auto-detected draft version.
|
|
20
122
|
#
|
|
21
123
|
# @param schema The JSON Schema (Hash or JSON string)
|
data/src/canonical.rs
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
use jsonschema::{
|
|
2
|
+
canonical::{CanonicalSchema, CanonicalView, CanonicalizationError},
|
|
3
|
+
JsonType,
|
|
4
|
+
};
|
|
5
|
+
use magnus::{
|
|
6
|
+
function, method,
|
|
7
|
+
prelude::*,
|
|
8
|
+
scan_args::{get_kwargs, scan_args},
|
|
9
|
+
typed_data,
|
|
10
|
+
value::Lazy,
|
|
11
|
+
DataTypeFunctions, Error, ExceptionClass, RClass, RHash, RModule, Ruby, TryConvert, Value,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
use crate::{
|
|
15
|
+
options::parse_draft_symbol,
|
|
16
|
+
ser::{to_schema_value, value_to_ruby},
|
|
17
|
+
static_id::define_rb_intern,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
define_rb_intern!(static CANONICAL_KW_DRAFT: "draft");
|
|
21
|
+
define_rb_intern!(static CANONICAL_KW_VALIDATE_FORMATS: "validate_formats");
|
|
22
|
+
|
|
23
|
+
macro_rules! canonical_error_class {
|
|
24
|
+
($static_name:ident, $class_name:literal) => {
|
|
25
|
+
static $static_name: Lazy<ExceptionClass> = Lazy::new(|ruby| {
|
|
26
|
+
let json_schema: RModule = ruby
|
|
27
|
+
.class_object()
|
|
28
|
+
.const_get("JSONSchema")
|
|
29
|
+
.expect("JSONSchema");
|
|
30
|
+
let canonical: RModule = json_schema.const_get("Canonical").expect("Canonical");
|
|
31
|
+
let error_class: RClass = canonical.const_get($class_name).expect($class_name);
|
|
32
|
+
let exception_class =
|
|
33
|
+
ExceptionClass::from_value(error_class.as_value()).expect("ExceptionClass");
|
|
34
|
+
// The cached handle is invisible to Ruby's GC; registration pins it across compaction.
|
|
35
|
+
magnus::gc::register_mark_object(exception_class);
|
|
36
|
+
exception_class
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
canonical_error_class!(CANONICALIZATION_ERROR_CLASS, "CanonicalizationError");
|
|
42
|
+
canonical_error_class!(INVALID_SCHEMA_TYPE_CLASS, "InvalidSchemaType");
|
|
43
|
+
|
|
44
|
+
fn canonicalization_error(ruby: &Ruby, error: CanonicalizationError) -> Error {
|
|
45
|
+
if let CanonicalizationError::ValidationError(validation_error) = error {
|
|
46
|
+
return crate::raise_validation_error(ruby, validation_error, None, None);
|
|
47
|
+
}
|
|
48
|
+
let message = error.to_string();
|
|
49
|
+
match error {
|
|
50
|
+
CanonicalizationError::InvalidSchemaType(_) => {
|
|
51
|
+
Error::new(ruby.get_inner(&INVALID_SCHEMA_TYPE_CLASS), message)
|
|
52
|
+
}
|
|
53
|
+
// `ValidationError` returns above; future variants fall back to the base canonical error.
|
|
54
|
+
_ => Error::new(ruby.get_inner(&CANONICALIZATION_ERROR_CLASS), message),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[derive(magnus::TypedData, PartialEq, Eq, Hash)]
|
|
59
|
+
#[magnus(
|
|
60
|
+
class = "JSONSchema::Canonical::CanonicalSchema",
|
|
61
|
+
free_immediately,
|
|
62
|
+
size
|
|
63
|
+
)]
|
|
64
|
+
pub struct RbCanonicalSchema {
|
|
65
|
+
inner: CanonicalSchema,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
impl DataTypeFunctions for RbCanonicalSchema {}
|
|
69
|
+
|
|
70
|
+
impl RbCanonicalSchema {
|
|
71
|
+
fn to_json_schema(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
72
|
+
value_to_ruby(ruby, &rb_self.inner.to_json_schema())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn draft(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
76
|
+
let name = match rb_self.inner.draft() {
|
|
77
|
+
jsonschema::Draft::Draft4 => "draft4",
|
|
78
|
+
jsonschema::Draft::Draft6 => "draft6",
|
|
79
|
+
jsonschema::Draft::Draft7 => "draft7",
|
|
80
|
+
jsonschema::Draft::Draft201909 => "draft201909",
|
|
81
|
+
_ => "draft202012",
|
|
82
|
+
};
|
|
83
|
+
ruby.sym_new(name).as_value()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn kind(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
87
|
+
ruby.sym_new(rb_self.inner.kind().as_str()).as_value()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn satisfiable(rb_self: &Self) -> bool {
|
|
91
|
+
rb_self.inner.is_satisfiable()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
|
|
95
|
+
// Bounded: `inspect` runs implicitly (IRB, error messages) and a full
|
|
96
|
+
// `to_json_schema` re-emits the whole document.
|
|
97
|
+
format!(
|
|
98
|
+
"#<JSONSchema::Canonical::CanonicalSchema kind={} draft={}>",
|
|
99
|
+
Self::kind(ruby, rb_self).inspect(),
|
|
100
|
+
Self::draft(ruby, rb_self).inspect()
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn eq(rb_self: &Self, other: Value) -> bool {
|
|
105
|
+
let Ok(other_ref) = <&RbCanonicalSchema>::try_convert(other) else {
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
rb_self.inner == other_ref.inner
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn view(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
112
|
+
match rb_self.inner.view() {
|
|
113
|
+
CanonicalView::MultiType(set) => ruby
|
|
114
|
+
.obj_wrap(MultiTypeView {
|
|
115
|
+
types: set.iter().map(JsonType::as_str).collect(),
|
|
116
|
+
})
|
|
117
|
+
.as_value(),
|
|
118
|
+
CanonicalView::TypedGroup(group) => ruby
|
|
119
|
+
.obj_wrap(TypedGroupView {
|
|
120
|
+
type_name: group.ty.as_str(),
|
|
121
|
+
body: group.body,
|
|
122
|
+
})
|
|
123
|
+
.as_value(),
|
|
124
|
+
CanonicalView::Const(value) => ruby.obj_wrap(ConstView { value }).as_value(),
|
|
125
|
+
CanonicalView::Enum(values) => ruby.obj_wrap(EnumView { values }).as_value(),
|
|
126
|
+
CanonicalView::True => ruby.obj_wrap(TrueView).as_value(),
|
|
127
|
+
CanonicalView::False => ruby.obj_wrap(FalseView).as_value(),
|
|
128
|
+
CanonicalView::String(view) => ruby
|
|
129
|
+
.obj_wrap(StringView {
|
|
130
|
+
min_length: view.min_length,
|
|
131
|
+
max_length: view.max_length,
|
|
132
|
+
patterns: view.patterns,
|
|
133
|
+
})
|
|
134
|
+
.as_value(),
|
|
135
|
+
CanonicalView::Integer(view) => ruby
|
|
136
|
+
.obj_wrap(IntegerView {
|
|
137
|
+
minimum: view.minimum,
|
|
138
|
+
maximum: view.maximum,
|
|
139
|
+
})
|
|
140
|
+
.as_value(),
|
|
141
|
+
CanonicalView::AnyOf(branches) => ruby.obj_wrap(AnyOfView { branches }).as_value(),
|
|
142
|
+
CanonicalView::Raw(schema) => ruby.obj_wrap(RawView { schema }).as_value(),
|
|
143
|
+
// TODO(canonical): new `CanonicalView` variants need view classes here.
|
|
144
|
+
other => unreachable!("unsupported canonical view: {other:?}"),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn definitions(ruby: &Ruby, rb_self: &Self) -> Result<RHash, Error> {
|
|
149
|
+
let hash = ruby.hash_new();
|
|
150
|
+
for (uri, target) in rb_self.inner.definitions() {
|
|
151
|
+
let wrapped = ruby.obj_wrap(RbCanonicalSchema { inner: target });
|
|
152
|
+
hash.aset(uri, wrapped)?;
|
|
153
|
+
}
|
|
154
|
+
Ok(hash)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// A schema the canonical form does not model structurally, kept verbatim.
|
|
159
|
+
#[derive(magnus::TypedData)]
|
|
160
|
+
#[magnus(class = "JSONSchema::Canonical::RawView", free_immediately)]
|
|
161
|
+
pub struct RawView {
|
|
162
|
+
schema: serde_json::Value,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
impl DataTypeFunctions for RawView {}
|
|
166
|
+
|
|
167
|
+
impl RawView {
|
|
168
|
+
fn schema(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
169
|
+
value_to_ruby(ruby, &rb_self.schema)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
173
|
+
Ok(format!(
|
|
174
|
+
"#<JSONSchema::Canonical::RawView schema={}>",
|
|
175
|
+
Self::schema(ruby, rb_self)?.inspect()
|
|
176
|
+
))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
180
|
+
let hash = ruby.hash_new();
|
|
181
|
+
hash.aset(
|
|
182
|
+
ruby.sym_new("schema"),
|
|
183
|
+
value_to_ruby(ruby, &rb_self.schema)?,
|
|
184
|
+
)?;
|
|
185
|
+
Ok(hash)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Matches any value.
|
|
190
|
+
#[derive(magnus::TypedData)]
|
|
191
|
+
#[magnus(class = "JSONSchema::Canonical::TrueView", free_immediately)]
|
|
192
|
+
pub struct TrueView;
|
|
193
|
+
|
|
194
|
+
impl TrueView {
|
|
195
|
+
fn inspect(_rb_self: &Self) -> String {
|
|
196
|
+
"#<JSONSchema::Canonical::TrueView>".to_string()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
impl DataTypeFunctions for TrueView {}
|
|
201
|
+
|
|
202
|
+
/// Matches no value.
|
|
203
|
+
#[derive(magnus::TypedData)]
|
|
204
|
+
#[magnus(class = "JSONSchema::Canonical::FalseView", free_immediately)]
|
|
205
|
+
pub struct FalseView;
|
|
206
|
+
|
|
207
|
+
impl FalseView {
|
|
208
|
+
fn inspect(_rb_self: &Self) -> String {
|
|
209
|
+
"#<JSONSchema::Canonical::FalseView>".to_string()
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
impl DataTypeFunctions for FalseView {}
|
|
214
|
+
|
|
215
|
+
/// A value matches iff its JSON type is in `types`.
|
|
216
|
+
#[derive(magnus::TypedData)]
|
|
217
|
+
#[magnus(class = "JSONSchema::Canonical::MultiTypeView", free_immediately)]
|
|
218
|
+
pub struct MultiTypeView {
|
|
219
|
+
types: Vec<&'static str>,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
impl DataTypeFunctions for MultiTypeView {}
|
|
223
|
+
|
|
224
|
+
impl MultiTypeView {
|
|
225
|
+
fn types(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
226
|
+
let array = ruby.ary_new_capa(rb_self.types.len());
|
|
227
|
+
for type_name in &rb_self.types {
|
|
228
|
+
array.push(ruby.sym_new(*type_name).as_value())?;
|
|
229
|
+
}
|
|
230
|
+
Ok(array.as_value())
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
234
|
+
Ok(format!(
|
|
235
|
+
"#<JSONSchema::Canonical::MultiTypeView types={}>",
|
|
236
|
+
Self::types(ruby, rb_self)?.inspect()
|
|
237
|
+
))
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
241
|
+
let hash = ruby.hash_new();
|
|
242
|
+
hash.aset(ruby.sym_new("types"), Self::types(ruby, rb_self)?)?;
|
|
243
|
+
Ok(hash)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// A value matches iff its JSON type is `type_name` *and* it satisfies `body`.
|
|
248
|
+
#[derive(magnus::TypedData)]
|
|
249
|
+
#[magnus(class = "JSONSchema::Canonical::TypedGroupView", free_immediately)]
|
|
250
|
+
pub struct TypedGroupView {
|
|
251
|
+
type_name: &'static str,
|
|
252
|
+
body: CanonicalSchema,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
impl DataTypeFunctions for TypedGroupView {}
|
|
256
|
+
|
|
257
|
+
impl TypedGroupView {
|
|
258
|
+
fn type_name(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
259
|
+
ruby.sym_new(rb_self.type_name).as_value()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fn body(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
263
|
+
ruby.obj_wrap(RbCanonicalSchema {
|
|
264
|
+
inner: rb_self.body.clone(),
|
|
265
|
+
})
|
|
266
|
+
.as_value()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
|
|
270
|
+
format!(
|
|
271
|
+
"#<JSONSchema::Canonical::TypedGroupView type_name={}>",
|
|
272
|
+
Self::type_name(ruby, rb_self).inspect()
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
277
|
+
let hash = ruby.hash_new();
|
|
278
|
+
hash.aset(ruby.sym_new("type_name"), Self::type_name(ruby, rb_self))?;
|
|
279
|
+
hash.aset(ruby.sym_new("body"), Self::body(ruby, rb_self))?;
|
|
280
|
+
Ok(hash)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// An absent bound is `nil`; a present one keeps its exact value.
|
|
285
|
+
fn bound_to_ruby(ruby: &Ruby, bound: Option<&serde_json::Number>) -> Result<Value, Error> {
|
|
286
|
+
match bound {
|
|
287
|
+
Some(number) => value_to_ruby(ruby, &serde_json::Value::Number(number.clone())),
|
|
288
|
+
None => Ok(ruby.qnil().as_value()),
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// A string value within a length window, matching every pattern.
|
|
293
|
+
#[derive(magnus::TypedData)]
|
|
294
|
+
#[magnus(class = "JSONSchema::Canonical::StringView", free_immediately)]
|
|
295
|
+
pub struct StringView {
|
|
296
|
+
min_length: Option<serde_json::Number>,
|
|
297
|
+
max_length: Option<serde_json::Number>,
|
|
298
|
+
patterns: Vec<String>,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
impl DataTypeFunctions for StringView {}
|
|
302
|
+
|
|
303
|
+
impl StringView {
|
|
304
|
+
fn min_length(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
305
|
+
bound_to_ruby(ruby, rb_self.min_length.as_ref())
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn max_length(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
309
|
+
bound_to_ruby(ruby, rb_self.max_length.as_ref())
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
fn patterns(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
313
|
+
let array = ruby.ary_new_capa(rb_self.patterns.len());
|
|
314
|
+
for pattern in &rb_self.patterns {
|
|
315
|
+
array.push(ruby.str_new(pattern).as_value())?;
|
|
316
|
+
}
|
|
317
|
+
Ok(array.as_value())
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
321
|
+
Ok(format!(
|
|
322
|
+
"#<JSONSchema::Canonical::StringView min_length={} max_length={} patterns={}>",
|
|
323
|
+
Self::min_length(ruby, rb_self)?.inspect(),
|
|
324
|
+
Self::max_length(ruby, rb_self)?.inspect(),
|
|
325
|
+
Self::patterns(ruby, rb_self)?.inspect()
|
|
326
|
+
))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
330
|
+
let hash = ruby.hash_new();
|
|
331
|
+
hash.aset(ruby.sym_new("min_length"), Self::min_length(ruby, rb_self)?)?;
|
|
332
|
+
hash.aset(ruby.sym_new("max_length"), Self::max_length(ruby, rb_self)?)?;
|
|
333
|
+
hash.aset(ruby.sym_new("patterns"), Self::patterns(ruby, rb_self)?)?;
|
|
334
|
+
Ok(hash)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// An integer value within a closed interval.
|
|
339
|
+
#[derive(magnus::TypedData)]
|
|
340
|
+
#[magnus(class = "JSONSchema::Canonical::IntegerView", free_immediately)]
|
|
341
|
+
pub struct IntegerView {
|
|
342
|
+
minimum: Option<serde_json::Number>,
|
|
343
|
+
maximum: Option<serde_json::Number>,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
impl DataTypeFunctions for IntegerView {}
|
|
347
|
+
|
|
348
|
+
impl IntegerView {
|
|
349
|
+
fn minimum(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
350
|
+
bound_to_ruby(ruby, rb_self.minimum.as_ref())
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
fn maximum(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
354
|
+
bound_to_ruby(ruby, rb_self.maximum.as_ref())
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
358
|
+
Ok(format!(
|
|
359
|
+
"#<JSONSchema::Canonical::IntegerView minimum={} maximum={}>",
|
|
360
|
+
Self::minimum(ruby, rb_self)?.inspect(),
|
|
361
|
+
Self::maximum(ruby, rb_self)?.inspect()
|
|
362
|
+
))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
366
|
+
let hash = ruby.hash_new();
|
|
367
|
+
hash.aset(ruby.sym_new("minimum"), Self::minimum(ruby, rb_self)?)?;
|
|
368
|
+
hash.aset(ruby.sym_new("maximum"), Self::maximum(ruby, rb_self)?)?;
|
|
369
|
+
Ok(hash)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/// A value matches iff at least one branch matches.
|
|
374
|
+
#[derive(magnus::TypedData)]
|
|
375
|
+
#[magnus(class = "JSONSchema::Canonical::AnyOfView", free_immediately)]
|
|
376
|
+
pub struct AnyOfView {
|
|
377
|
+
branches: Vec<CanonicalSchema>,
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
impl DataTypeFunctions for AnyOfView {}
|
|
381
|
+
|
|
382
|
+
impl AnyOfView {
|
|
383
|
+
fn branches(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
384
|
+
let array = ruby.ary_new_capa(rb_self.branches.len());
|
|
385
|
+
for branch in &rb_self.branches {
|
|
386
|
+
array.push(
|
|
387
|
+
ruby.obj_wrap(RbCanonicalSchema {
|
|
388
|
+
inner: branch.clone(),
|
|
389
|
+
})
|
|
390
|
+
.as_value(),
|
|
391
|
+
)?;
|
|
392
|
+
}
|
|
393
|
+
Ok(array.as_value())
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
397
|
+
let kinds = ruby.ary_new_capa(rb_self.branches.len());
|
|
398
|
+
for branch in &rb_self.branches {
|
|
399
|
+
kinds.push(ruby.sym_new(branch.kind().as_str()).as_value())?;
|
|
400
|
+
}
|
|
401
|
+
Ok(format!(
|
|
402
|
+
"#<JSONSchema::Canonical::AnyOfView branches={}>",
|
|
403
|
+
kinds.as_value().inspect()
|
|
404
|
+
))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
408
|
+
let hash = ruby.hash_new();
|
|
409
|
+
hash.aset(ruby.sym_new("branches"), Self::branches(ruby, rb_self)?)?;
|
|
410
|
+
Ok(hash)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/// Exactly one admitted value.
|
|
415
|
+
#[derive(magnus::TypedData)]
|
|
416
|
+
#[magnus(class = "JSONSchema::Canonical::ConstView", free_immediately)]
|
|
417
|
+
pub struct ConstView {
|
|
418
|
+
value: serde_json::Value,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
impl DataTypeFunctions for ConstView {}
|
|
422
|
+
|
|
423
|
+
impl ConstView {
|
|
424
|
+
fn value(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
425
|
+
value_to_ruby(ruby, &rb_self.value)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
429
|
+
Ok(format!(
|
|
430
|
+
"#<JSONSchema::Canonical::ConstView value={}>",
|
|
431
|
+
Self::value(ruby, rb_self)?.inspect()
|
|
432
|
+
))
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
436
|
+
let hash = ruby.hash_new();
|
|
437
|
+
hash.aset(ruby.sym_new("value"), Self::value(ruby, rb_self)?)?;
|
|
438
|
+
Ok(hash)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/// A sorted, deduplicated finite set of admitted values.
|
|
443
|
+
#[derive(magnus::TypedData)]
|
|
444
|
+
#[magnus(class = "JSONSchema::Canonical::EnumView", free_immediately)]
|
|
445
|
+
pub struct EnumView {
|
|
446
|
+
values: Vec<serde_json::Value>,
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
impl DataTypeFunctions for EnumView {}
|
|
450
|
+
|
|
451
|
+
impl EnumView {
|
|
452
|
+
fn values(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
453
|
+
let array = ruby.ary_new_capa(rb_self.values.len());
|
|
454
|
+
for value in &rb_self.values {
|
|
455
|
+
array.push(value_to_ruby(ruby, value)?)?;
|
|
456
|
+
}
|
|
457
|
+
Ok(array.as_value())
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
fn inspect(ruby: &Ruby, rb_self: &Self) -> Result<String, Error> {
|
|
461
|
+
Ok(format!(
|
|
462
|
+
"#<JSONSchema::Canonical::EnumView values={}>",
|
|
463
|
+
Self::values(ruby, rb_self)?.inspect()
|
|
464
|
+
))
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
468
|
+
let hash = ruby.hash_new();
|
|
469
|
+
hash.aset(ruby.sym_new("values"), Self::values(ruby, rb_self)?)?;
|
|
470
|
+
Ok(hash)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
475
|
+
let parsed = scan_args::<(Value,), (), (), (), _, ()>(args)?;
|
|
476
|
+
let (schema_arg,) = parsed.required;
|
|
477
|
+
let keywords: RHash = parsed.keywords;
|
|
478
|
+
let base_kwargs: magnus::scan_args::KwArgs<(), (Option<Value>, Option<bool>), ()> = get_kwargs(
|
|
479
|
+
keywords,
|
|
480
|
+
&[],
|
|
481
|
+
&[*CANONICAL_KW_DRAFT, *CANONICAL_KW_VALIDATE_FORMATS],
|
|
482
|
+
)?;
|
|
483
|
+
let (draft_val, validate_formats) = base_kwargs.optional;
|
|
484
|
+
|
|
485
|
+
let schema_value = to_schema_value(ruby, schema_arg)?;
|
|
486
|
+
let mut options = jsonschema::canonical::options();
|
|
487
|
+
if let Some(draft) = draft_val {
|
|
488
|
+
options = options.with_draft(parse_draft_symbol(ruby, draft)?);
|
|
489
|
+
}
|
|
490
|
+
if let Some(validate_formats) = validate_formats {
|
|
491
|
+
options = options.should_validate_formats(validate_formats);
|
|
492
|
+
}
|
|
493
|
+
options
|
|
494
|
+
.canonicalize(&schema_value)
|
|
495
|
+
.map(|inner| ruby.obj_wrap(RbCanonicalSchema { inner }).as_value())
|
|
496
|
+
.map_err(|error| canonicalization_error(ruby, error))
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
|
|
500
|
+
let canonical_module = module.define_module("Canonical")?;
|
|
501
|
+
|
|
502
|
+
let base_error =
|
|
503
|
+
canonical_module.define_error("CanonicalizationError", ruby.exception_standard_error())?;
|
|
504
|
+
canonical_module.define_error("InvalidSchemaType", base_error)?;
|
|
505
|
+
|
|
506
|
+
let canonical_schema = canonical_module.define_class("CanonicalSchema", ruby.class_object())?;
|
|
507
|
+
canonical_schema.define_method(
|
|
508
|
+
"to_json_schema",
|
|
509
|
+
method!(RbCanonicalSchema::to_json_schema, 0),
|
|
510
|
+
)?;
|
|
511
|
+
canonical_schema.define_method("draft", method!(RbCanonicalSchema::draft, 0))?;
|
|
512
|
+
canonical_schema.define_method("kind", method!(RbCanonicalSchema::kind, 0))?;
|
|
513
|
+
canonical_schema.define_method("inspect", method!(RbCanonicalSchema::inspect, 0))?;
|
|
514
|
+
canonical_schema.define_method("==", method!(RbCanonicalSchema::eq, 1))?;
|
|
515
|
+
canonical_schema.define_method(
|
|
516
|
+
"eql?",
|
|
517
|
+
method!(<RbCanonicalSchema as typed_data::IsEql>::is_eql, 1),
|
|
518
|
+
)?;
|
|
519
|
+
canonical_schema.define_method(
|
|
520
|
+
"hash",
|
|
521
|
+
method!(<RbCanonicalSchema as typed_data::Hash>::hash, 0),
|
|
522
|
+
)?;
|
|
523
|
+
canonical_schema.define_method("view", method!(RbCanonicalSchema::view, 0))?;
|
|
524
|
+
canonical_schema.define_method("definitions", method!(RbCanonicalSchema::definitions, 0))?;
|
|
525
|
+
canonical_schema.define_method("satisfiable?", method!(RbCanonicalSchema::satisfiable, 0))?;
|
|
526
|
+
|
|
527
|
+
let true_view = canonical_module.define_class("TrueView", ruby.class_object())?;
|
|
528
|
+
true_view.define_method("inspect", method!(TrueView::inspect, 0))?;
|
|
529
|
+
let false_view = canonical_module.define_class("FalseView", ruby.class_object())?;
|
|
530
|
+
false_view.define_method("inspect", method!(FalseView::inspect, 0))?;
|
|
531
|
+
|
|
532
|
+
let multi_type_view = canonical_module.define_class("MultiTypeView", ruby.class_object())?;
|
|
533
|
+
multi_type_view.define_method("types", method!(MultiTypeView::types, 0))?;
|
|
534
|
+
multi_type_view.define_method("inspect", method!(MultiTypeView::inspect, 0))?;
|
|
535
|
+
multi_type_view.define_method(
|
|
536
|
+
"deconstruct_keys",
|
|
537
|
+
method!(MultiTypeView::deconstruct_keys, 1),
|
|
538
|
+
)?;
|
|
539
|
+
|
|
540
|
+
let typed_group_view = canonical_module.define_class("TypedGroupView", ruby.class_object())?;
|
|
541
|
+
typed_group_view.define_method("type_name", method!(TypedGroupView::type_name, 0))?;
|
|
542
|
+
typed_group_view.define_method("body", method!(TypedGroupView::body, 0))?;
|
|
543
|
+
typed_group_view.define_method("inspect", method!(TypedGroupView::inspect, 0))?;
|
|
544
|
+
typed_group_view.define_method(
|
|
545
|
+
"deconstruct_keys",
|
|
546
|
+
method!(TypedGroupView::deconstruct_keys, 1),
|
|
547
|
+
)?;
|
|
548
|
+
|
|
549
|
+
let const_view = canonical_module.define_class("ConstView", ruby.class_object())?;
|
|
550
|
+
const_view.define_method("value", method!(ConstView::value, 0))?;
|
|
551
|
+
const_view.define_method("inspect", method!(ConstView::inspect, 0))?;
|
|
552
|
+
const_view.define_method("deconstruct_keys", method!(ConstView::deconstruct_keys, 1))?;
|
|
553
|
+
|
|
554
|
+
let enum_view = canonical_module.define_class("EnumView", ruby.class_object())?;
|
|
555
|
+
enum_view.define_method("values", method!(EnumView::values, 0))?;
|
|
556
|
+
enum_view.define_method("inspect", method!(EnumView::inspect, 0))?;
|
|
557
|
+
enum_view.define_method("deconstruct_keys", method!(EnumView::deconstruct_keys, 1))?;
|
|
558
|
+
|
|
559
|
+
let string_view = canonical_module.define_class("StringView", ruby.class_object())?;
|
|
560
|
+
string_view.define_method("min_length", method!(StringView::min_length, 0))?;
|
|
561
|
+
string_view.define_method("max_length", method!(StringView::max_length, 0))?;
|
|
562
|
+
string_view.define_method("patterns", method!(StringView::patterns, 0))?;
|
|
563
|
+
string_view.define_method("inspect", method!(StringView::inspect, 0))?;
|
|
564
|
+
string_view.define_method("deconstruct_keys", method!(StringView::deconstruct_keys, 1))?;
|
|
565
|
+
|
|
566
|
+
let integer_view = canonical_module.define_class("IntegerView", ruby.class_object())?;
|
|
567
|
+
integer_view.define_method("minimum", method!(IntegerView::minimum, 0))?;
|
|
568
|
+
integer_view.define_method("maximum", method!(IntegerView::maximum, 0))?;
|
|
569
|
+
integer_view.define_method("inspect", method!(IntegerView::inspect, 0))?;
|
|
570
|
+
integer_view.define_method(
|
|
571
|
+
"deconstruct_keys",
|
|
572
|
+
method!(IntegerView::deconstruct_keys, 1),
|
|
573
|
+
)?;
|
|
574
|
+
|
|
575
|
+
let any_of_view = canonical_module.define_class("AnyOfView", ruby.class_object())?;
|
|
576
|
+
any_of_view.define_method("branches", method!(AnyOfView::branches, 0))?;
|
|
577
|
+
any_of_view.define_method("inspect", method!(AnyOfView::inspect, 0))?;
|
|
578
|
+
any_of_view.define_method("deconstruct_keys", method!(AnyOfView::deconstruct_keys, 1))?;
|
|
579
|
+
|
|
580
|
+
let raw_view = canonical_module.define_class("RawView", ruby.class_object())?;
|
|
581
|
+
raw_view.define_method("schema", method!(RawView::schema, 0))?;
|
|
582
|
+
raw_view.define_method("inspect", method!(RawView::inspect, 0))?;
|
|
583
|
+
raw_view.define_method("deconstruct_keys", method!(RawView::deconstruct_keys, 1))?;
|
|
584
|
+
|
|
585
|
+
let json_module = canonical_module.define_module("JSON")?;
|
|
586
|
+
json_module
|
|
587
|
+
.define_singleton_method("to_string", function!(crate::canonical_json_to_string, 1))?;
|
|
588
|
+
|
|
589
|
+
module.define_singleton_method("canonicalize", function!(canonicalize, -1))?;
|
|
590
|
+
|
|
591
|
+
Ok(())
|
|
592
|
+
}
|
data/src/lib.rs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#![allow(clippy::unused_self)]
|
|
6
6
|
#![allow(clippy::struct_field_names)]
|
|
7
7
|
|
|
8
|
+
mod canonical;
|
|
8
9
|
mod error_kind;
|
|
9
10
|
mod evaluation;
|
|
10
11
|
mod options;
|
|
@@ -1513,10 +1514,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
1513
1514
|
module.define_singleton_method("each_error", function!(each_error, -1))?;
|
|
1514
1515
|
module.define_singleton_method("evaluate", function!(evaluate, -1))?;
|
|
1515
1516
|
|
|
1516
|
-
|
|
1517
|
-
let canonical_json_module = canonical_module.define_module("JSON")?;
|
|
1518
|
-
canonical_json_module
|
|
1519
|
-
.define_singleton_method("to_string", function!(canonical_json_to_string, 1))?;
|
|
1517
|
+
canonical::init_canonical(ruby, &module)?;
|
|
1520
1518
|
|
|
1521
1519
|
// Validator class
|
|
1522
1520
|
let validator_class = module.define_class("Validator", ruby.class_object())?;
|
data/src/ser.rs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//! Serialization between Ruby values and `serde_json::Value`.
|
|
2
|
+
use jsonschema::canonical::json::{canonical_number, BorrowedNumber};
|
|
2
3
|
use magnus::{
|
|
3
4
|
error::ErrorType,
|
|
4
5
|
exception::ExceptionClass,
|
|
@@ -435,7 +436,12 @@ impl Serialize for CanonicalRubyValue<'_> {
|
|
|
435
436
|
number.serialize(serializer)
|
|
436
437
|
}
|
|
437
438
|
ruby_value_type::RUBY_T_FLOAT => {
|
|
438
|
-
let
|
|
439
|
+
let float = f64::try_convert(self.value)
|
|
440
|
+
.map_err(|error| ruby_error_to_canonical_serde::<S>(self.ruby, &error))?;
|
|
441
|
+
if float.fract() != 0.0 && float.is_finite() {
|
|
442
|
+
return serialize_fractional_float(float, serializer);
|
|
443
|
+
}
|
|
444
|
+
let number = convert_float_for_canonical(self.ruby, float)
|
|
439
445
|
.map_err(|error| ruby_error_to_canonical_serde::<S>(self.ruby, &error))?;
|
|
440
446
|
number.serialize(serializer)
|
|
441
447
|
}
|
|
@@ -547,9 +553,18 @@ impl Serialize for CanonicalRubyValue<'_> {
|
|
|
547
553
|
}
|
|
548
554
|
}
|
|
549
555
|
|
|
556
|
+
fn serialize_fractional_float<S>(float: f64, serializer: S) -> Result<S::Ok, S::Error>
|
|
557
|
+
where
|
|
558
|
+
S: Serializer,
|
|
559
|
+
{
|
|
560
|
+
let mut buffer = zmij::Buffer::new();
|
|
561
|
+
let text = buffer.format_finite(float);
|
|
562
|
+
let canonical = canonical_number(text);
|
|
563
|
+
BorrowedNumber(canonical.as_deref().unwrap_or(text)).serialize(serializer)
|
|
564
|
+
}
|
|
565
|
+
|
|
550
566
|
#[inline]
|
|
551
|
-
fn convert_float_for_canonical(ruby: &Ruby,
|
|
552
|
-
let float = f64::try_convert(value)?;
|
|
567
|
+
fn convert_float_for_canonical(ruby: &Ruby, float: f64) -> Result<JsonValue, Error> {
|
|
553
568
|
if !float.is_finite() {
|
|
554
569
|
return Ok(JsonValue::Null);
|
|
555
570
|
}
|
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.48.
|
|
4
|
+
version: 0.48.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dygalo
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- lib/jsonschema/version.rb
|
|
64
64
|
- lib/jsonschema_rs.rb
|
|
65
65
|
- sig/jsonschema.rbs
|
|
66
|
+
- src/canonical.rs
|
|
66
67
|
- src/error_kind.rs
|
|
67
68
|
- src/evaluation.rs
|
|
68
69
|
- src/lib.rs
|