jsonschema_rs 0.48.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dff84fb88165fc24b6035c6b100e89c83c4b6f7194fc945c3ec73e93f5119178
4
- data.tar.gz: feac688548b70b5cc57f455c5f78c76fbe3107f9b38f03220ab98ce110018668
3
+ metadata.gz: 7091a24d3c4cae1381d2dcc89844c0c9e2752a04e418a94ded9b3e93656fe9f8
4
+ data.tar.gz: c2367c71d93a857f2520dd345e26222608714868e2941a332eb30d78816f75a4
5
5
  SHA512:
6
- metadata.gz: 64315bbd608bd3a80a74fa392e15c9eb57a6887dbe913d68a194f2d58c889c35c8e798b7abfb3ef619775ba9e34378de6ae3425ff4221b630f0788b8cebf40d5
7
- data.tar.gz: a86e31921a0498b479e841ea25cb4bc5083dd54f9a055513c8fda434c865216992561bd92ec7a6a5543671c919227a5039f976a6f882e1cb7576dc741dedde34
6
+ metadata.gz: a7dea5681e119df671ac136f7fb9a780e3ac12354487ed763aef6cb7c0f9c68357287a66a17745a2778d78d9aaa5d33faa41de4bbb19f9d7f7b17bbb2285da4a
7
+ data.tar.gz: 9c8544d8e5379e5d7013fbed284ef7e932894f23900973a5901a6cb6a40022fbb170bd30ecee04555cf2d512fe3a7a5778e9bff5e809556e9c0a2845a21f70da
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
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
+
5
15
  ## [0.48.1] - 2026-07-17
6
16
 
7
17
  ### Fixed
@@ -182,7 +192,8 @@
182
192
 
183
193
  - Initial public release
184
194
 
185
- [Unreleased]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.1...HEAD
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
186
197
  [0.48.1]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.0...ruby-v0.48.1
187
198
  [0.48.0]: https://github.com/Stranger6667/jsonschema/compare/ruby-v0.47.0...ruby-v0.48.0
188
199
  [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.1"
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.1", default-features = false, features = ["arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
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
@@ -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.1"
696
+ version = "0.48.2"
691
697
  source = "registry+https://github.com/rust-lang/crates.io-index"
692
- checksum = "9baf521a926fd1e6f94af6922be9686b61c8a89a5fb7e14d6a1e21b79738d4cc"
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.1"
728
+ version = "0.48.2"
722
729
  source = "registry+https://github.com/rust-lang/crates.io-index"
723
- checksum = "11efb171797c352f167fd4f3754b3a5e969d814222e457e63136cffca111b014"
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.1"
738
+ version = "0.48.2"
732
739
  source = "registry+https://github.com/rust-lang/crates.io-index"
733
- checksum = "a3ce6144063dbeca5c841049bc0958d2eaf7d0a20d600be0907c29a2d1a6267a"
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.1"
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.1"
770
+ version = "0.48.2"
763
771
  source = "registry+https://github.com/rust-lang/crates.io-index"
764
- checksum = "d668f4775619127513d9a4f190704d20a66d20ccf0efc258f8ed42548e5fd7cd"
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.1"
1117
+ version = "0.48.2"
1110
1118
  source = "registry+https://github.com/rust-lang/crates.io-index"
1111
- checksum = "df843f41f0cb459649f64a8b6b10579cf454f7a7420dc702767c81a26f23d780"
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"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonschema-rb-ext"
3
- version = "0.48.1"
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.1", default-features = false, features = ["arbitrary-precision", "resolve-http", "resolve-file", "tls-ring", "macros"] }
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.1"
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]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONSchema
4
- VERSION = "0.48.1"
4
+ VERSION = "0.48.2"
5
5
  end
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
- let canonical_module = module.define_module("Canonical")?;
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 number = convert_float_for_canonical(self.ruby, self.value)
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, value: Value) -> Result<JsonValue, Error> {
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.1
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