jsonschema_rs 0.48.0 → 0.48.2
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/README.md +4 -0
- data/ext/jsonschema/Cargo.lock +40 -11
- data/ext/jsonschema/Cargo.toml +4 -3
- data/lib/jsonschema/version.rb +1 -1
- data/src/canonical.rs +413 -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: 7091a24d3c4cae1381d2dcc89844c0c9e2752a04e418a94ded9b3e93656fe9f8
|
|
4
|
+
data.tar.gz: c2367c71d93a857f2520dd345e26222608714868e2941a332eb30d78816f75a4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7dea5681e119df671ac136f7fb9a780e3ac12354487ed763aef6cb7c0f9c68357287a66a17745a2778d78d9aaa5d33faa41de4bbb19f9d7f7b17bbb2285da4a
|
|
7
|
+
data.tar.gz: 9c8544d8e5379e5d7013fbed284ef7e932894f23900973a5901a6cb6a40022fbb170bd30ecee04555cf2d512fe3a7a5778e9bff5e809556e9c0a2845a21f70da
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.48.2] - 2026-07-21
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `Canonical::JSON.to_string` incorrectly emitting exponent form for small `Float` values (e.g. `1e-7` instead of `0.0000001`).
|
|
10
|
+
|
|
11
|
+
### Performance
|
|
12
|
+
|
|
13
|
+
- Faster validator compilation by pre-sizing internal caches.
|
|
14
|
+
|
|
15
|
+
## [0.48.1] - 2026-07-17
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Missing `required` errors in `evaluate` output for schemas with `properties` and a two-entry `required` array. [#1220](https://github.com/Stranger6667/jsonschema/issues/1220)
|
|
20
|
+
- `contentEncoding` errors for invalid UTF-8 after decoding incorrectly had empty `instance_path` and `schema_path`.
|
|
21
|
+
|
|
5
22
|
## [0.48.0] - 2026-07-16
|
|
6
23
|
|
|
7
24
|
### Fixed
|
|
@@ -175,7 +192,9 @@
|
|
|
175
192
|
|
|
176
193
|
- Initial public release
|
|
177
194
|
|
|
178
|
-
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.
|
|
195
|
+
[Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.2...HEAD
|
|
196
|
+
[0.48.2]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.1...ruby-v0.48.2
|
|
197
|
+
[0.48.1]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.0...ruby-v0.48.1
|
|
179
198
|
[0.48.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.47.0...ruby-v0.48.0
|
|
180
199
|
[0.47.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.10...ruby-v0.47.0
|
|
181
200
|
[0.46.10]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.9...ruby-v0.46.10
|
data/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonschema-rb"
|
|
3
|
-
version = "0.48.
|
|
3
|
+
version = "0.48.2"
|
|
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.2", 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/README.md
CHANGED
|
@@ -64,6 +64,10 @@ The following drafts are supported:
|
|
|
64
64
|
|
|
65
65
|
You can check the current status on the [Bowtie Report](https://bowtie.report/#/implementations/rust-jsonschema).
|
|
66
66
|
|
|
67
|
+
## Playground
|
|
68
|
+
|
|
69
|
+
If you'd like to try `jsonschema`, you can check the WebAssembly-powered [playground](https://jsonschema.dygalo.dev/) to see the results instantly.
|
|
70
|
+
|
|
67
71
|
## Installation
|
|
68
72
|
|
|
69
73
|
Add to your Gemfile:
|
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.2"
|
|
691
697
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
692
|
-
checksum = "
|
|
698
|
+
checksum = "39d785c0683d1027839f1ac6a43b6d07c63d58ecd363d8146dda4531d53465a1"
|
|
693
699
|
dependencies = [
|
|
694
700
|
"ahash",
|
|
695
701
|
"bytecount",
|
|
@@ -712,15 +718,16 @@ dependencies = [
|
|
|
712
718
|
"rustls",
|
|
713
719
|
"serde",
|
|
714
720
|
"serde_json",
|
|
721
|
+
"strum",
|
|
715
722
|
"unicode-general-category",
|
|
716
723
|
"uuid-simd",
|
|
717
724
|
]
|
|
718
725
|
|
|
719
726
|
[[package]]
|
|
720
727
|
name = "jsonschema-macros"
|
|
721
|
-
version = "0.48.
|
|
728
|
+
version = "0.48.2"
|
|
722
729
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
723
|
-
checksum = "
|
|
730
|
+
checksum = "a1c58940755c1f87fc5843b63626867ed8384bc0b6fabf0de2a718ccafb42570"
|
|
724
731
|
dependencies = [
|
|
725
732
|
"jsonschema-macros-core",
|
|
726
733
|
"proc-macro2",
|
|
@@ -728,9 +735,9 @@ dependencies = [
|
|
|
728
735
|
|
|
729
736
|
[[package]]
|
|
730
737
|
name = "jsonschema-macros-core"
|
|
731
|
-
version = "0.48.
|
|
738
|
+
version = "0.48.2"
|
|
732
739
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
733
|
-
checksum = "
|
|
740
|
+
checksum = "110e53690c5d5524b69e557bd286b1c6c9743bfcf13c18fd5e3f21a9708737dc"
|
|
734
741
|
dependencies = [
|
|
735
742
|
"fancy-regex",
|
|
736
743
|
"indexmap",
|
|
@@ -747,7 +754,7 @@ dependencies = [
|
|
|
747
754
|
|
|
748
755
|
[[package]]
|
|
749
756
|
name = "jsonschema-rb-ext"
|
|
750
|
-
version = "0.48.
|
|
757
|
+
version = "0.48.2"
|
|
751
758
|
dependencies = [
|
|
752
759
|
"jsonschema",
|
|
753
760
|
"magnus",
|
|
@@ -755,13 +762,14 @@ dependencies = [
|
|
|
755
762
|
"referencing",
|
|
756
763
|
"serde",
|
|
757
764
|
"serde_json",
|
|
765
|
+
"zmij",
|
|
758
766
|
]
|
|
759
767
|
|
|
760
768
|
[[package]]
|
|
761
769
|
name = "jsonschema-regex"
|
|
762
|
-
version = "0.48.
|
|
770
|
+
version = "0.48.2"
|
|
763
771
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
764
|
-
checksum = "
|
|
772
|
+
checksum = "917b3be16b2662538aafb55201790561e1f1ad1a0454baf42f33bd9de343778b"
|
|
765
773
|
dependencies = [
|
|
766
774
|
"regex-syntax",
|
|
767
775
|
]
|
|
@@ -1106,9 +1114,9 @@ dependencies = [
|
|
|
1106
1114
|
|
|
1107
1115
|
[[package]]
|
|
1108
1116
|
name = "referencing"
|
|
1109
|
-
version = "0.48.
|
|
1117
|
+
version = "0.48.2"
|
|
1110
1118
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1111
|
-
checksum = "
|
|
1119
|
+
checksum = "2d2b21a85c07fcf1cb95ff7ab8a687fcd37fa70db59975b5a032abdaadfdf42e"
|
|
1112
1120
|
dependencies = [
|
|
1113
1121
|
"ahash",
|
|
1114
1122
|
"fluent-uri",
|
|
@@ -1424,6 +1432,27 @@ version = "1.2.1"
|
|
|
1424
1432
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1425
1433
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|
1426
1434
|
|
|
1435
|
+
[[package]]
|
|
1436
|
+
name = "strum"
|
|
1437
|
+
version = "0.28.0"
|
|
1438
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1439
|
+
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
|
1440
|
+
dependencies = [
|
|
1441
|
+
"strum_macros",
|
|
1442
|
+
]
|
|
1443
|
+
|
|
1444
|
+
[[package]]
|
|
1445
|
+
name = "strum_macros"
|
|
1446
|
+
version = "0.28.0"
|
|
1447
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1448
|
+
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
|
1449
|
+
dependencies = [
|
|
1450
|
+
"heck",
|
|
1451
|
+
"proc-macro2",
|
|
1452
|
+
"quote",
|
|
1453
|
+
"syn",
|
|
1454
|
+
]
|
|
1455
|
+
|
|
1427
1456
|
[[package]]
|
|
1428
1457
|
name = "subtle"
|
|
1429
1458
|
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.2"
|
|
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.2", 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.2"
|
|
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/src/canonical.rs
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
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(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
|
+
rb_self.inner.kind().as_str(),
|
|
100
|
+
rb_self.inner.draft()
|
|
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::Raw(schema) => ruby.obj_wrap(RawView { schema }).as_value(),
|
|
129
|
+
// TODO(canonical): new `CanonicalView` variants need view classes here.
|
|
130
|
+
other => unreachable!("unsupported canonical view: {other:?}"),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn definitions(ruby: &Ruby, rb_self: &Self) -> Result<RHash, Error> {
|
|
135
|
+
let hash = ruby.hash_new();
|
|
136
|
+
for (uri, target) in rb_self.inner.definitions() {
|
|
137
|
+
let wrapped = ruby.obj_wrap(RbCanonicalSchema { inner: target });
|
|
138
|
+
hash.aset(uri, wrapped)?;
|
|
139
|
+
}
|
|
140
|
+
Ok(hash)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// A schema the canonical form does not model structurally, kept verbatim.
|
|
145
|
+
#[derive(magnus::TypedData)]
|
|
146
|
+
#[magnus(class = "JSONSchema::Canonical::RawView", free_immediately)]
|
|
147
|
+
pub struct RawView {
|
|
148
|
+
schema: serde_json::Value,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl DataTypeFunctions for RawView {}
|
|
152
|
+
|
|
153
|
+
impl RawView {
|
|
154
|
+
fn schema(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
155
|
+
value_to_ruby(ruby, &rb_self.schema)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn inspect(rb_self: &Self) -> String {
|
|
159
|
+
format!(
|
|
160
|
+
"#<JSONSchema::Canonical::RawView schema={}>",
|
|
161
|
+
rb_self.schema
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
166
|
+
let hash = ruby.hash_new();
|
|
167
|
+
hash.aset(
|
|
168
|
+
ruby.sym_new("schema"),
|
|
169
|
+
value_to_ruby(ruby, &rb_self.schema)?,
|
|
170
|
+
)?;
|
|
171
|
+
Ok(hash)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Matches any value.
|
|
176
|
+
#[derive(magnus::TypedData)]
|
|
177
|
+
#[magnus(class = "JSONSchema::Canonical::TrueView", free_immediately)]
|
|
178
|
+
pub struct TrueView;
|
|
179
|
+
|
|
180
|
+
impl DataTypeFunctions for TrueView {}
|
|
181
|
+
|
|
182
|
+
/// Matches no value.
|
|
183
|
+
#[derive(magnus::TypedData)]
|
|
184
|
+
#[magnus(class = "JSONSchema::Canonical::FalseView", free_immediately)]
|
|
185
|
+
pub struct FalseView;
|
|
186
|
+
|
|
187
|
+
impl DataTypeFunctions for FalseView {}
|
|
188
|
+
|
|
189
|
+
/// A value matches iff its JSON type is in `types`.
|
|
190
|
+
#[derive(magnus::TypedData)]
|
|
191
|
+
#[magnus(class = "JSONSchema::Canonical::MultiTypeView", free_immediately)]
|
|
192
|
+
pub struct MultiTypeView {
|
|
193
|
+
types: Vec<&'static str>,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
impl DataTypeFunctions for MultiTypeView {}
|
|
197
|
+
|
|
198
|
+
impl MultiTypeView {
|
|
199
|
+
fn types(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
200
|
+
let array = ruby.ary_new_capa(rb_self.types.len());
|
|
201
|
+
for type_name in &rb_self.types {
|
|
202
|
+
array.push(ruby.sym_new(*type_name).as_value())?;
|
|
203
|
+
}
|
|
204
|
+
Ok(array.as_value())
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fn inspect(rb_self: &Self) -> String {
|
|
208
|
+
format!(
|
|
209
|
+
"#<JSONSchema::Canonical::MultiTypeView types={:?}>",
|
|
210
|
+
rb_self.types
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
215
|
+
let hash = ruby.hash_new();
|
|
216
|
+
hash.aset(ruby.sym_new("types"), Self::types(ruby, rb_self)?)?;
|
|
217
|
+
Ok(hash)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// A value matches iff its JSON type is `type_name` *and* it satisfies `body`.
|
|
222
|
+
#[derive(magnus::TypedData)]
|
|
223
|
+
#[magnus(class = "JSONSchema::Canonical::TypedGroupView", free_immediately)]
|
|
224
|
+
pub struct TypedGroupView {
|
|
225
|
+
type_name: &'static str,
|
|
226
|
+
body: CanonicalSchema,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
impl DataTypeFunctions for TypedGroupView {}
|
|
230
|
+
|
|
231
|
+
impl TypedGroupView {
|
|
232
|
+
fn type_name(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
233
|
+
ruby.sym_new(rb_self.type_name).as_value()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fn body(ruby: &Ruby, rb_self: &Self) -> Value {
|
|
237
|
+
ruby.obj_wrap(RbCanonicalSchema {
|
|
238
|
+
inner: rb_self.body.clone(),
|
|
239
|
+
})
|
|
240
|
+
.as_value()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
fn inspect(rb_self: &Self) -> String {
|
|
244
|
+
format!(
|
|
245
|
+
"#<JSONSchema::Canonical::TypedGroupView type_name={:?}>",
|
|
246
|
+
rb_self.type_name
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
251
|
+
let hash = ruby.hash_new();
|
|
252
|
+
hash.aset(ruby.sym_new("type_name"), Self::type_name(ruby, rb_self))?;
|
|
253
|
+
hash.aset(ruby.sym_new("body"), Self::body(ruby, rb_self))?;
|
|
254
|
+
Ok(hash)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Exactly one admitted value.
|
|
259
|
+
#[derive(magnus::TypedData)]
|
|
260
|
+
#[magnus(class = "JSONSchema::Canonical::ConstView", free_immediately)]
|
|
261
|
+
pub struct ConstView {
|
|
262
|
+
value: serde_json::Value,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
impl DataTypeFunctions for ConstView {}
|
|
266
|
+
|
|
267
|
+
impl ConstView {
|
|
268
|
+
fn value(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
269
|
+
value_to_ruby(ruby, &rb_self.value)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fn inspect(rb_self: &Self) -> String {
|
|
273
|
+
format!(
|
|
274
|
+
"#<JSONSchema::Canonical::ConstView value={}>",
|
|
275
|
+
rb_self.value
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
280
|
+
let hash = ruby.hash_new();
|
|
281
|
+
hash.aset(ruby.sym_new("value"), Self::value(ruby, rb_self)?)?;
|
|
282
|
+
Ok(hash)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// A sorted, deduplicated finite set of admitted values.
|
|
287
|
+
#[derive(magnus::TypedData)]
|
|
288
|
+
#[magnus(class = "JSONSchema::Canonical::EnumView", free_immediately)]
|
|
289
|
+
pub struct EnumView {
|
|
290
|
+
values: Vec<serde_json::Value>,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
impl DataTypeFunctions for EnumView {}
|
|
294
|
+
|
|
295
|
+
impl EnumView {
|
|
296
|
+
fn values(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
297
|
+
let array = ruby.ary_new_capa(rb_self.values.len());
|
|
298
|
+
for value in &rb_self.values {
|
|
299
|
+
array.push(value_to_ruby(ruby, value)?)?;
|
|
300
|
+
}
|
|
301
|
+
Ok(array.as_value())
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
fn inspect(rb_self: &Self) -> String {
|
|
305
|
+
format!(
|
|
306
|
+
"#<JSONSchema::Canonical::EnumView values={:?}>",
|
|
307
|
+
rb_self.values
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
fn deconstruct_keys(ruby: &Ruby, rb_self: &Self, _keys: Value) -> Result<RHash, Error> {
|
|
312
|
+
let hash = ruby.hash_new();
|
|
313
|
+
hash.aset(ruby.sym_new("values"), Self::values(ruby, rb_self)?)?;
|
|
314
|
+
Ok(hash)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fn canonicalize(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
319
|
+
let parsed = scan_args::<(Value,), (), (), (), _, ()>(args)?;
|
|
320
|
+
let (schema_arg,) = parsed.required;
|
|
321
|
+
let keywords: RHash = parsed.keywords;
|
|
322
|
+
let base_kwargs: magnus::scan_args::KwArgs<(), (Option<Value>, Option<bool>), ()> = get_kwargs(
|
|
323
|
+
keywords,
|
|
324
|
+
&[],
|
|
325
|
+
&[*CANONICAL_KW_DRAFT, *CANONICAL_KW_VALIDATE_FORMATS],
|
|
326
|
+
)?;
|
|
327
|
+
let (draft_val, validate_formats) = base_kwargs.optional;
|
|
328
|
+
|
|
329
|
+
let schema_value = to_schema_value(ruby, schema_arg)?;
|
|
330
|
+
let mut options = jsonschema::canonical::options();
|
|
331
|
+
if let Some(draft) = draft_val {
|
|
332
|
+
options = options.with_draft(parse_draft_symbol(ruby, draft)?);
|
|
333
|
+
}
|
|
334
|
+
if let Some(validate_formats) = validate_formats {
|
|
335
|
+
options = options.should_validate_formats(validate_formats);
|
|
336
|
+
}
|
|
337
|
+
options
|
|
338
|
+
.canonicalize(&schema_value)
|
|
339
|
+
.map(|inner| ruby.obj_wrap(RbCanonicalSchema { inner }).as_value())
|
|
340
|
+
.map_err(|error| canonicalization_error(ruby, error))
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
pub(crate) fn init_canonical(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
|
|
344
|
+
let canonical_module = module.define_module("Canonical")?;
|
|
345
|
+
|
|
346
|
+
let base_error =
|
|
347
|
+
canonical_module.define_error("CanonicalizationError", ruby.exception_standard_error())?;
|
|
348
|
+
canonical_module.define_error("InvalidSchemaType", base_error)?;
|
|
349
|
+
|
|
350
|
+
let canonical_schema = canonical_module.define_class("CanonicalSchema", ruby.class_object())?;
|
|
351
|
+
canonical_schema.define_method(
|
|
352
|
+
"to_json_schema",
|
|
353
|
+
method!(RbCanonicalSchema::to_json_schema, 0),
|
|
354
|
+
)?;
|
|
355
|
+
canonical_schema.define_method("draft", method!(RbCanonicalSchema::draft, 0))?;
|
|
356
|
+
canonical_schema.define_method("kind", method!(RbCanonicalSchema::kind, 0))?;
|
|
357
|
+
canonical_schema.define_method("inspect", method!(RbCanonicalSchema::inspect, 0))?;
|
|
358
|
+
canonical_schema.define_method("==", method!(RbCanonicalSchema::eq, 1))?;
|
|
359
|
+
canonical_schema.define_method(
|
|
360
|
+
"eql?",
|
|
361
|
+
method!(<RbCanonicalSchema as typed_data::IsEql>::is_eql, 1),
|
|
362
|
+
)?;
|
|
363
|
+
canonical_schema.define_method(
|
|
364
|
+
"hash",
|
|
365
|
+
method!(<RbCanonicalSchema as typed_data::Hash>::hash, 0),
|
|
366
|
+
)?;
|
|
367
|
+
canonical_schema.define_method("view", method!(RbCanonicalSchema::view, 0))?;
|
|
368
|
+
canonical_schema.define_method("definitions", method!(RbCanonicalSchema::definitions, 0))?;
|
|
369
|
+
canonical_schema.define_method("satisfiable?", method!(RbCanonicalSchema::satisfiable, 0))?;
|
|
370
|
+
|
|
371
|
+
canonical_module.define_class("TrueView", ruby.class_object())?;
|
|
372
|
+
canonical_module.define_class("FalseView", ruby.class_object())?;
|
|
373
|
+
|
|
374
|
+
let multi_type_view = canonical_module.define_class("MultiTypeView", ruby.class_object())?;
|
|
375
|
+
multi_type_view.define_method("types", method!(MultiTypeView::types, 0))?;
|
|
376
|
+
multi_type_view.define_method("inspect", method!(MultiTypeView::inspect, 0))?;
|
|
377
|
+
multi_type_view.define_method(
|
|
378
|
+
"deconstruct_keys",
|
|
379
|
+
method!(MultiTypeView::deconstruct_keys, 1),
|
|
380
|
+
)?;
|
|
381
|
+
|
|
382
|
+
let typed_group_view = canonical_module.define_class("TypedGroupView", ruby.class_object())?;
|
|
383
|
+
typed_group_view.define_method("type_name", method!(TypedGroupView::type_name, 0))?;
|
|
384
|
+
typed_group_view.define_method("body", method!(TypedGroupView::body, 0))?;
|
|
385
|
+
typed_group_view.define_method("inspect", method!(TypedGroupView::inspect, 0))?;
|
|
386
|
+
typed_group_view.define_method(
|
|
387
|
+
"deconstruct_keys",
|
|
388
|
+
method!(TypedGroupView::deconstruct_keys, 1),
|
|
389
|
+
)?;
|
|
390
|
+
|
|
391
|
+
let const_view = canonical_module.define_class("ConstView", ruby.class_object())?;
|
|
392
|
+
const_view.define_method("value", method!(ConstView::value, 0))?;
|
|
393
|
+
const_view.define_method("inspect", method!(ConstView::inspect, 0))?;
|
|
394
|
+
const_view.define_method("deconstruct_keys", method!(ConstView::deconstruct_keys, 1))?;
|
|
395
|
+
|
|
396
|
+
let enum_view = canonical_module.define_class("EnumView", ruby.class_object())?;
|
|
397
|
+
enum_view.define_method("values", method!(EnumView::values, 0))?;
|
|
398
|
+
enum_view.define_method("inspect", method!(EnumView::inspect, 0))?;
|
|
399
|
+
enum_view.define_method("deconstruct_keys", method!(EnumView::deconstruct_keys, 1))?;
|
|
400
|
+
|
|
401
|
+
let raw_view = canonical_module.define_class("RawView", ruby.class_object())?;
|
|
402
|
+
raw_view.define_method("schema", method!(RawView::schema, 0))?;
|
|
403
|
+
raw_view.define_method("inspect", method!(RawView::inspect, 0))?;
|
|
404
|
+
raw_view.define_method("deconstruct_keys", method!(RawView::deconstruct_keys, 1))?;
|
|
405
|
+
|
|
406
|
+
let json_module = canonical_module.define_module("JSON")?;
|
|
407
|
+
json_module
|
|
408
|
+
.define_singleton_method("to_string", function!(crate::canonical_json_to_string, 1))?;
|
|
409
|
+
|
|
410
|
+
module.define_singleton_method("canonicalize", function!(canonicalize, -1))?;
|
|
411
|
+
|
|
412
|
+
Ok(())
|
|
413
|
+
}
|
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.2
|
|
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
|