jsonschema_rs 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -1
- data/Cargo.toml +2 -2
- data/ext/jsonschema/Cargo.lock +141 -195
- data/ext/jsonschema/Cargo.toml +3 -3
- data/lib/jsonschema/version.rb +1 -1
- data/sig/jsonschema.rbs +50 -0
- data/src/lib.rs +138 -13
- data/src/options.rs +7 -4
- data/src/registry.rs +184 -25
- data/src/validator_map.rs +92 -0
- metadata +2 -1
data/src/registry.rs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
use std::sync::Arc;
|
|
2
|
+
|
|
1
3
|
use magnus::{
|
|
2
4
|
function,
|
|
3
5
|
gc::{register_address, unregister_address},
|
|
@@ -8,7 +10,11 @@ use magnus::{
|
|
|
8
10
|
DataTypeFunctions, Error, RArray, RModule, Ruby, TryConvert, Value,
|
|
9
11
|
};
|
|
10
12
|
|
|
11
|
-
use crate::{
|
|
13
|
+
use crate::{
|
|
14
|
+
options::parse_draft_symbol,
|
|
15
|
+
retriever::make_retriever,
|
|
16
|
+
ser::{to_value, value_to_ruby},
|
|
17
|
+
};
|
|
12
18
|
|
|
13
19
|
struct RetrieverBuildRootGuard {
|
|
14
20
|
// Keep roots in a heap allocation so addresses passed to Ruby GC are stable
|
|
@@ -40,7 +46,7 @@ impl Drop for RetrieverBuildRootGuard {
|
|
|
40
46
|
#[derive(magnus::TypedData)]
|
|
41
47
|
#[magnus(class = "JSONSchema::Registry", free_immediately, size, mark)]
|
|
42
48
|
pub struct Registry {
|
|
43
|
-
pub inner: jsonschema::Registry
|
|
49
|
+
pub inner: Arc<jsonschema::Registry<'static>>,
|
|
44
50
|
retriever_root: Option<Opaque<Value>>,
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -56,12 +62,63 @@ impl TryConvert for Registry {
|
|
|
56
62
|
fn try_convert(val: Value) -> Result<Self, Error> {
|
|
57
63
|
let typed: &Registry = TryConvert::try_convert(val)?;
|
|
58
64
|
Ok(Registry {
|
|
59
|
-
inner: typed.inner
|
|
65
|
+
inner: Arc::clone(&typed.inner),
|
|
60
66
|
retriever_root: typed.retriever_root,
|
|
61
67
|
})
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
struct ResolverData {
|
|
72
|
+
registry: Arc<jsonschema::Registry<'static>>,
|
|
73
|
+
retriever_root: Option<Opaque<Value>>,
|
|
74
|
+
base_uri: jsonschema::Uri<String>,
|
|
75
|
+
dynamic_scope: Vec<jsonschema::Uri<String>>,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[derive(magnus::TypedData)]
|
|
79
|
+
#[magnus(class = "JSONSchema::Resolver", free_immediately, size, mark)]
|
|
80
|
+
pub struct Resolver(ResolverData);
|
|
81
|
+
|
|
82
|
+
impl DataTypeFunctions for Resolver {
|
|
83
|
+
fn mark(&self, marker: &magnus::gc::Marker) {
|
|
84
|
+
if let Some(root) = self.0.retriever_root {
|
|
85
|
+
marker.mark(root);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[derive(magnus::TypedData)]
|
|
91
|
+
#[magnus(class = "JSONSchema::Resolved", free_immediately, size, mark)]
|
|
92
|
+
pub struct Resolved {
|
|
93
|
+
contents: Opaque<Value>,
|
|
94
|
+
resolver: ResolverData,
|
|
95
|
+
draft: u8,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl DataTypeFunctions for Resolved {
|
|
99
|
+
fn mark(&self, marker: &magnus::gc::Marker) {
|
|
100
|
+
marker.mark(self.contents);
|
|
101
|
+
if let Some(root) = self.resolver.retriever_root {
|
|
102
|
+
marker.mark(root);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn parse_uri(ruby: &Ruby, uri: &str) -> Result<jsonschema::Uri<String>, Error> {
|
|
108
|
+
jsonschema::uri::from_str(uri)
|
|
109
|
+
.map_err(|e| Error::new(ruby.exception_arg_error(), format!("{e}")))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn draft_to_u8(draft: jsonschema::Draft) -> u8 {
|
|
113
|
+
match draft {
|
|
114
|
+
jsonschema::Draft::Draft4 => 4,
|
|
115
|
+
jsonschema::Draft::Draft6 => 6,
|
|
116
|
+
jsonschema::Draft::Draft7 => 7,
|
|
117
|
+
jsonschema::Draft::Draft201909 => 19,
|
|
118
|
+
_ => 20,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
65
122
|
impl Registry {
|
|
66
123
|
fn new_impl(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
|
|
67
124
|
let parsed_args = scan_args::<(RArray,), (), (), (), _, ()>(args)?;
|
|
@@ -72,7 +129,7 @@ impl Registry {
|
|
|
72
129
|
let draft_val = kw.optional.0.flatten();
|
|
73
130
|
let retriever_val = kw.optional.1;
|
|
74
131
|
|
|
75
|
-
let mut builder = jsonschema::Registry::
|
|
132
|
+
let mut builder = jsonschema::Registry::new();
|
|
76
133
|
let mut retriever_root = None;
|
|
77
134
|
let mut retriever_build_root = None;
|
|
78
135
|
|
|
@@ -89,33 +146,30 @@ impl Registry {
|
|
|
89
146
|
}
|
|
90
147
|
}
|
|
91
148
|
|
|
92
|
-
let pairs: Vec<(String, jsonschema::Resource)> = resources
|
|
93
|
-
.into_iter()
|
|
94
|
-
.map(|item| {
|
|
95
|
-
let pair: RArray = TryConvert::try_convert(item)?;
|
|
96
|
-
if pair.len() != 2 {
|
|
97
|
-
return Err(Error::new(
|
|
98
|
-
ruby.exception_arg_error(),
|
|
99
|
-
"Each resource must be a [uri, schema] pair",
|
|
100
|
-
));
|
|
101
|
-
}
|
|
102
|
-
let uri: String = pair.entry(0)?;
|
|
103
|
-
let schema_val: Value = pair.entry(1)?;
|
|
104
|
-
let schema = to_value(ruby, schema_val)?;
|
|
105
|
-
let resource = jsonschema::Resource::from_contents(schema);
|
|
106
|
-
Ok((uri, resource))
|
|
107
|
-
})
|
|
108
|
-
.collect::<Result<Vec<_>, Error>>()?;
|
|
109
|
-
|
|
110
149
|
// Keep the retriever proc GC-rooted for the entire build, because `build`
|
|
111
150
|
// may call into retriever callbacks while traversing referenced resources.
|
|
112
151
|
let _retriever_build_guard = RetrieverBuildRootGuard::new(retriever_build_root);
|
|
152
|
+
for item in resources {
|
|
153
|
+
let pair: RArray = TryConvert::try_convert(item)?;
|
|
154
|
+
if pair.len() != 2 {
|
|
155
|
+
return Err(Error::new(
|
|
156
|
+
ruby.exception_arg_error(),
|
|
157
|
+
"Each resource must be a [uri, schema] pair",
|
|
158
|
+
));
|
|
159
|
+
}
|
|
160
|
+
let uri: String = pair.entry(0)?;
|
|
161
|
+
let schema_val: Value = pair.entry(1)?;
|
|
162
|
+
let schema = to_value(ruby, schema_val)?;
|
|
163
|
+
builder = builder
|
|
164
|
+
.add(uri, schema)
|
|
165
|
+
.map_err(|e| Error::new(ruby.exception_arg_error(), format!("{e}")))?;
|
|
166
|
+
}
|
|
113
167
|
let registry = builder
|
|
114
|
-
.
|
|
115
|
-
.map_err(|e| Error::new(ruby.exception_arg_error(), e
|
|
168
|
+
.prepare()
|
|
169
|
+
.map_err(|e| Error::new(ruby.exception_arg_error(), format!("{e}")))?;
|
|
116
170
|
|
|
117
171
|
Ok(Registry {
|
|
118
|
-
inner: registry,
|
|
172
|
+
inner: Arc::new(registry),
|
|
119
173
|
retriever_root,
|
|
120
174
|
})
|
|
121
175
|
}
|
|
@@ -127,12 +181,117 @@ impl Registry {
|
|
|
127
181
|
pub(crate) fn retriever_value(&self, ruby: &Ruby) -> Option<Value> {
|
|
128
182
|
self.retriever_root.map(|root| ruby.get_inner(root))
|
|
129
183
|
}
|
|
184
|
+
|
|
185
|
+
#[allow(clippy::needless_pass_by_value)]
|
|
186
|
+
fn resolver(ruby: &Ruby, rb_self: &Registry, base_uri: String) -> Result<Resolver, Error> {
|
|
187
|
+
Ok(Resolver(ResolverData {
|
|
188
|
+
registry: Arc::clone(&rb_self.inner),
|
|
189
|
+
retriever_root: rb_self.retriever_root,
|
|
190
|
+
base_uri: parse_uri(ruby, &base_uri)?,
|
|
191
|
+
dynamic_scope: Vec::new(),
|
|
192
|
+
}))
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
impl Resolver {
|
|
197
|
+
fn base_uri(&self) -> String {
|
|
198
|
+
self.0.base_uri.as_str().to_string()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fn dynamic_scope(ruby: &Ruby, rb_self: &Resolver) -> Result<Value, Error> {
|
|
202
|
+
let arr = ruby.ary_new_capa(rb_self.0.dynamic_scope.len());
|
|
203
|
+
for scope in &rb_self.0.dynamic_scope {
|
|
204
|
+
arr.push(ruby.into_value(scope.as_str()))?;
|
|
205
|
+
}
|
|
206
|
+
Ok(arr.as_value())
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#[allow(clippy::needless_pass_by_value)]
|
|
210
|
+
fn lookup(ruby: &Ruby, rb_self: &Resolver, reference: String) -> Result<Resolved, Error> {
|
|
211
|
+
let resolver = if rb_self.0.dynamic_scope.is_empty() {
|
|
212
|
+
rb_self
|
|
213
|
+
.0
|
|
214
|
+
.registry
|
|
215
|
+
.as_ref()
|
|
216
|
+
.resolver(rb_self.0.base_uri.clone())
|
|
217
|
+
} else {
|
|
218
|
+
let oldest_uri = rb_self
|
|
219
|
+
.0
|
|
220
|
+
.dynamic_scope
|
|
221
|
+
.last()
|
|
222
|
+
.expect("dynamic_scope is not empty")
|
|
223
|
+
.clone();
|
|
224
|
+
let mut resolver = rb_self.0.registry.as_ref().resolver(oldest_uri);
|
|
225
|
+
for next_uri in rb_self
|
|
226
|
+
.0
|
|
227
|
+
.dynamic_scope
|
|
228
|
+
.iter()
|
|
229
|
+
.rev()
|
|
230
|
+
.skip(1)
|
|
231
|
+
.chain(std::iter::once(&rb_self.0.base_uri))
|
|
232
|
+
{
|
|
233
|
+
let next_resolved = resolver
|
|
234
|
+
.lookup(next_uri.as_str())
|
|
235
|
+
.map_err(|e| crate::referencing_error(ruby, e.to_string()))?;
|
|
236
|
+
resolver = next_resolved.into_inner().1;
|
|
237
|
+
}
|
|
238
|
+
resolver
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
let resolved = resolver
|
|
242
|
+
.lookup(&reference)
|
|
243
|
+
.map_err(|e| crate::referencing_error(ruby, e.to_string()))?;
|
|
244
|
+
let (contents, resolver, draft) = resolved.into_inner();
|
|
245
|
+
|
|
246
|
+
let contents_val = value_to_ruby(ruby, contents)?;
|
|
247
|
+
|
|
248
|
+
Ok(Resolved {
|
|
249
|
+
contents: Opaque::from(contents_val),
|
|
250
|
+
resolver: ResolverData {
|
|
251
|
+
registry: Arc::clone(&rb_self.0.registry),
|
|
252
|
+
retriever_root: rb_self.0.retriever_root,
|
|
253
|
+
base_uri: resolver.base_uri().as_ref().clone(),
|
|
254
|
+
dynamic_scope: resolver.dynamic_scope().iter().cloned().collect(),
|
|
255
|
+
},
|
|
256
|
+
draft: draft_to_u8(draft),
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
impl Resolved {
|
|
262
|
+
fn contents(ruby: &Ruby, rb_self: &Resolved) -> Value {
|
|
263
|
+
ruby.get_inner(rb_self.contents)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fn resolver(_ruby: &Ruby, rb_self: &Resolved) -> Resolver {
|
|
267
|
+
Resolver(ResolverData {
|
|
268
|
+
registry: Arc::clone(&rb_self.resolver.registry),
|
|
269
|
+
retriever_root: rb_self.resolver.retriever_root,
|
|
270
|
+
base_uri: rb_self.resolver.base_uri.clone(),
|
|
271
|
+
dynamic_scope: rb_self.resolver.dynamic_scope.clone(),
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
fn draft(&self) -> u8 {
|
|
276
|
+
self.draft
|
|
277
|
+
}
|
|
130
278
|
}
|
|
131
279
|
|
|
132
280
|
pub fn define_class(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
|
|
133
281
|
let class = module.define_class("Registry", ruby.class_object())?;
|
|
134
282
|
class.define_singleton_method("new", function!(Registry::new_impl, -1))?;
|
|
135
283
|
class.define_method("inspect", method!(Registry::inspect, 0))?;
|
|
284
|
+
class.define_method("resolver", method!(Registry::resolver, 1))?;
|
|
285
|
+
|
|
286
|
+
let resolver_class = module.define_class("Resolver", ruby.class_object())?;
|
|
287
|
+
resolver_class.define_method("base_uri", method!(Resolver::base_uri, 0))?;
|
|
288
|
+
resolver_class.define_method("dynamic_scope", method!(Resolver::dynamic_scope, 0))?;
|
|
289
|
+
resolver_class.define_method("lookup", method!(Resolver::lookup, 1))?;
|
|
290
|
+
|
|
291
|
+
let resolved_class = module.define_class("Resolved", ruby.class_object())?;
|
|
292
|
+
resolved_class.define_method("contents", method!(Resolved::contents, 0))?;
|
|
293
|
+
resolved_class.define_method("resolver", method!(Resolved::resolver, 0))?;
|
|
294
|
+
resolved_class.define_method("draft", method!(Resolved::draft, 0))?;
|
|
136
295
|
|
|
137
296
|
Ok(())
|
|
138
297
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//! ValidatorMap binding for Ruby.
|
|
2
|
+
use magnus::{gc::Marker, method, prelude::*, DataTypeFunctions, Error, RModule, Ruby, TypedData};
|
|
3
|
+
|
|
4
|
+
use crate::{
|
|
5
|
+
options::{CallbackRoots, CompilationRootsRef},
|
|
6
|
+
Validator,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/// Ruby wrapper around `jsonschema::ValidatorMap`.
|
|
10
|
+
#[derive(TypedData)]
|
|
11
|
+
#[magnus(class = "JSONSchema::ValidatorMap", free_immediately, size, mark)]
|
|
12
|
+
pub struct ValidatorMap {
|
|
13
|
+
pub(crate) inner: jsonschema::ValidatorMap,
|
|
14
|
+
pub(crate) has_ruby_callbacks: bool,
|
|
15
|
+
pub(crate) callback_roots: CallbackRoots,
|
|
16
|
+
pub(crate) compilation_roots: CompilationRootsRef,
|
|
17
|
+
pub(crate) mask: Option<String>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl DataTypeFunctions for ValidatorMap {
|
|
21
|
+
fn mark(&self, marker: &Marker) {
|
|
22
|
+
// Avoid panicking in Ruby GC mark paths; preserving existing roots is safer than aborting.
|
|
23
|
+
let roots = match self.callback_roots.lock() {
|
|
24
|
+
Ok(roots) => roots,
|
|
25
|
+
Err(poisoned) => poisoned.into_inner(),
|
|
26
|
+
};
|
|
27
|
+
for root in roots.iter().copied() {
|
|
28
|
+
marker.mark(root);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl ValidatorMap {
|
|
34
|
+
/// `map["#/$defs/User"]` → Validator | nil
|
|
35
|
+
#[allow(clippy::needless_pass_by_value)]
|
|
36
|
+
fn get(&self, pointer: String) -> Option<Validator> {
|
|
37
|
+
self.inner.get(&pointer).map(|v| {
|
|
38
|
+
Validator::from_jsonschema_with_roots(
|
|
39
|
+
v.clone(),
|
|
40
|
+
self.mask.clone(),
|
|
41
|
+
self.has_ruby_callbacks,
|
|
42
|
+
self.callback_roots.clone(),
|
|
43
|
+
self.compilation_roots.clone(),
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// `map.fetch("#/$defs/User")` → Validator (raises KeyError if missing)
|
|
49
|
+
#[allow(clippy::needless_pass_by_value)]
|
|
50
|
+
fn fetch(ruby: &Ruby, rb_self: &Self, pointer: String) -> Result<Validator, Error> {
|
|
51
|
+
match rb_self.inner.get(&pointer) {
|
|
52
|
+
Some(v) => Ok(Validator::from_jsonschema_with_roots(
|
|
53
|
+
v.clone(),
|
|
54
|
+
rb_self.mask.clone(),
|
|
55
|
+
rb_self.has_ruby_callbacks,
|
|
56
|
+
rb_self.callback_roots.clone(),
|
|
57
|
+
rb_self.compilation_roots.clone(),
|
|
58
|
+
)),
|
|
59
|
+
None => Err(Error::new(
|
|
60
|
+
ruby.exception_key_error(),
|
|
61
|
+
format!("key not found: {pointer}"),
|
|
62
|
+
)),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// `map.key?("#/$defs/User")` → bool
|
|
67
|
+
#[allow(clippy::needless_pass_by_value)]
|
|
68
|
+
fn key_p(&self, pointer: String) -> bool {
|
|
69
|
+
self.inner.contains_key(&pointer)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// `map.keys` → Array[String]
|
|
73
|
+
fn keys(&self) -> Vec<String> {
|
|
74
|
+
self.inner.keys().map(str::to_owned).collect()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// `map.length` → Integer
|
|
78
|
+
fn length(&self) -> usize {
|
|
79
|
+
self.inner.len()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn define_class(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
|
|
84
|
+
let class = module.define_class("ValidatorMap", ruby.class_object())?;
|
|
85
|
+
class.define_method("[]", method!(ValidatorMap::get, 1))?;
|
|
86
|
+
class.define_method("fetch", method!(ValidatorMap::fetch, 1))?;
|
|
87
|
+
class.define_method("key?", method!(ValidatorMap::key_p, 1))?;
|
|
88
|
+
class.define_method("keys", method!(ValidatorMap::keys, 0))?;
|
|
89
|
+
class.define_method("length", method!(ValidatorMap::length, 0))?;
|
|
90
|
+
class.define_alias("size", "length")?;
|
|
91
|
+
Ok(())
|
|
92
|
+
}
|
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.
|
|
4
|
+
version: 0.46.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dygalo
|
|
@@ -71,6 +71,7 @@ files:
|
|
|
71
71
|
- src/retriever.rs
|
|
72
72
|
- src/ser.rs
|
|
73
73
|
- src/static_id.rs
|
|
74
|
+
- src/validator_map.rs
|
|
74
75
|
homepage: https://github.com/Stranger6667/jsonschema/tree/master/crates/jsonschema-rb
|
|
75
76
|
licenses:
|
|
76
77
|
- MIT
|