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.
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::{options::parse_draft_symbol, retriever::make_retriever, ser::to_value};
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.clone(),
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::options();
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
- .build(pairs)
115
- .map_err(|e| Error::new(ruby.exception_arg_error(), e.to_string()))?;
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.45.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