jsi 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +11 -6
  5. data/jsi.gemspec +30 -0
  6. data/lib/jsi/base/node.rb +183 -0
  7. data/lib/jsi/base.rb +135 -161
  8. data/lib/jsi/jsi_coder.rb +3 -3
  9. data/lib/jsi/metaschema.rb +0 -1
  10. data/lib/jsi/metaschema_node/bootstrap_schema.rb +9 -8
  11. data/lib/jsi/metaschema_node.rb +48 -51
  12. data/lib/jsi/ptr.rb +28 -17
  13. data/lib/jsi/schema/application/child_application/contains.rb +11 -2
  14. data/lib/jsi/schema/application/child_application/items.rb +3 -3
  15. data/lib/jsi/schema/application/child_application/properties.rb +3 -3
  16. data/lib/jsi/schema/application/child_application.rb +1 -3
  17. data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
  18. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
  19. data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
  20. data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
  21. data/lib/jsi/schema/application/inplace_application.rb +1 -6
  22. data/lib/jsi/schema/ref.rb +3 -2
  23. data/lib/jsi/schema/schema_ancestor_node.rb +11 -17
  24. data/lib/jsi/schema/validation/array.rb +3 -3
  25. data/lib/jsi/schema/validation/const.rb +1 -1
  26. data/lib/jsi/schema/validation/contains.rb +1 -1
  27. data/lib/jsi/schema/validation/dependencies.rb +1 -1
  28. data/lib/jsi/schema/validation/draft04/minmax.rb +6 -6
  29. data/lib/jsi/schema/validation/enum.rb +1 -1
  30. data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
  31. data/lib/jsi/schema/validation/items.rb +4 -4
  32. data/lib/jsi/schema/validation/not.rb +1 -1
  33. data/lib/jsi/schema/validation/numeric.rb +5 -5
  34. data/lib/jsi/schema/validation/object.rb +2 -2
  35. data/lib/jsi/schema/validation/pattern.rb +1 -1
  36. data/lib/jsi/schema/validation/properties.rb +3 -3
  37. data/lib/jsi/schema/validation/property_names.rb +1 -1
  38. data/lib/jsi/schema/validation/ref.rb +1 -1
  39. data/lib/jsi/schema/validation/required.rb +1 -1
  40. data/lib/jsi/schema/validation/someof.rb +3 -3
  41. data/lib/jsi/schema/validation/string.rb +2 -2
  42. data/lib/jsi/schema/validation/type.rb +1 -1
  43. data/lib/jsi/schema/validation.rb +1 -1
  44. data/lib/jsi/schema.rb +91 -85
  45. data/lib/jsi/schema_classes.rb +70 -45
  46. data/lib/jsi/schema_registry.rb +15 -5
  47. data/lib/jsi/schema_set.rb +42 -2
  48. data/lib/jsi/simple_wrap.rb +23 -4
  49. data/lib/jsi/util/{attr_struct.rb → private/attr_struct.rb} +40 -19
  50. data/lib/jsi/util/private.rb +204 -0
  51. data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +56 -82
  52. data/lib/jsi/util.rb +68 -148
  53. data/lib/jsi/version.rb +1 -1
  54. data/lib/jsi.rb +1 -17
  55. data/lib/schemas/json-schema.org/draft-04/schema.rb +3 -1
  56. data/lib/schemas/json-schema.org/draft-06/schema.rb +3 -1
  57. data/lib/schemas/json-schema.org/draft-07/schema.rb +3 -1
  58. metadata +11 -9
  59. data/lib/jsi/pathed_node.rb +0 -116
data/lib/jsi/base.rb CHANGED
@@ -14,24 +14,17 @@ module JSI
14
14
  #
15
15
  # the JSI::Base class itself is not intended to be instantiated.
16
16
  class Base
17
- include PathedNode
17
+ autoload :ArrayNode, 'jsi/base/node'
18
+ autoload :HashNode, 'jsi/base/node'
19
+
18
20
  include Schema::SchemaAncestorNode
19
21
  include Util::Memoize
20
22
 
21
- # not every JSI::Base is necessarily an Enumerable, but it's better to include Enumerable on
22
- # the class than to conditionally extend the instance.
23
- include Enumerable
24
-
25
- # an exception raised when #[] is invoked on an instance which is not an array or hash
23
+ # an exception raised when {Base#[]} is invoked on an instance which is not an array or hash
26
24
  class CannotSubscriptError < StandardError
27
25
  end
28
26
 
29
27
  class << self
30
- # @private @deprecated
31
- def new_jsi(instance, **kw, &b)
32
- new(instance, **kw, &b)
33
- end
34
-
35
28
  # @private
36
29
  # is the constant JSI::SchemaClasses::<self.schema_classes_const_name> defined?
37
30
  # (if so, we will prefer to use something more human-readable than that ugly mess.)
@@ -84,8 +77,9 @@ module JSI
84
77
  def schema_classes_const_name
85
78
  if respond_to?(:jsi_class_schemas)
86
79
  schema_names = jsi_class_schemas.map do |schema|
87
- if schema.jsi_schema_module.name
88
- schema.jsi_schema_module.name
80
+ named_ancestor_schema, tokens = schema.jsi_schema_module.send(:named_ancestor_schema_tokens)
81
+ if named_ancestor_schema
82
+ [named_ancestor_schema.jsi_schema_module.name, *tokens].join('_')
89
83
  elsif schema.schema_uri
90
84
  schema.schema_uri.to_s
91
85
  else
@@ -119,78 +113,44 @@ module JSI
119
113
  end
120
114
  end
121
115
 
122
- # NOINSTANCE is a magic value passed to #initialize when instantiating a JSI
123
- # from a document and pointer.
116
+ # initializes a JSI whose instance is in the given document at the given pointer.
124
117
  #
125
- # @private
126
- NOINSTANCE = Object.new
127
- [:inspect, :to_s].each(&(-> (s, m) { NOINSTANCE.define_singleton_method(m) { s } }.curry.("#{JSI::Base}::NOINSTANCE")))
128
- NOINSTANCE.freeze
129
-
130
- # initializes this JSI from the given instance - instance is most commonly
131
- # a parsed JSON document consisting of Hash, Array, or sometimes a basic
132
- # type, but this is in no way enforced and a JSI may wrap any object.
118
+ # this is a private api - users should look elsewhere to instantiate JSIs, in particular:
119
+ #
120
+ # - {JSI.new_schema} and {Schema::DescribesSchema#new_schema} to instantiate schemas
121
+ # - {Schema#new_jsi} to instantiate schema instances
133
122
  #
134
- # @param instance [Object] the JSON Schema instance to be represented as a JSI
135
- # @param jsi_document [Object] for internal use. the instance may be specified as a
136
- # node in the `jsi_document` param, pointed to by `jsi_ptr`. the param `instance`
137
- # MUST be `NOINSTANCE` to use the jsi_document + jsi_ptr form. `jsi_document` MUST
138
- # NOT be passed if `instance` is anything other than `NOINSTANCE`.
139
- # @param jsi_ptr [JSI::Ptr] for internal use. a pointer specifying
140
- # the path of this instance in the `jsi_document` param. `jsi_ptr` must be passed
141
- # iff `jsi_document` is passed, i.e. when `instance` is `NOINSTANCE`
142
- # @param jsi_root_node [JSI::Base] for internal use, specifies the JSI at the root of the document
123
+ # @api private
124
+ # @param jsi_document [Object] the document containing the instance
125
+ # @param jsi_ptr [JSI::Ptr] a pointer pointing to the JSI's instance in the document
126
+ # @param jsi_root_node [JSI::Base] the JSI of the root of the document containing this JSI
143
127
  # @param jsi_schema_base_uri [Addressable::URI] see {SchemaSet#new_jsi} param uri
144
- # @param jsi_schema_resource_ancestors [Array<JSI::Base>]
145
- def initialize(instance,
146
- jsi_document: nil,
147
- jsi_ptr: nil,
128
+ # @param jsi_schema_resource_ancestors [Array<JSI::Base<JSI::Schema>>]
129
+ def initialize(jsi_document,
130
+ jsi_ptr: Ptr[],
148
131
  jsi_root_node: nil,
149
132
  jsi_schema_base_uri: nil,
150
- jsi_schema_resource_ancestors: []
133
+ jsi_schema_resource_ancestors: Util::EMPTY_ARY
151
134
  )
152
- unless respond_to?(:jsi_schemas)
153
- raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #jsi_schemas. it is recommended to instantiate JSIs from a schema using JSI::Schema#new_jsi.")
154
- end
155
-
156
- if instance.is_a?(JSI::Schema)
157
- raise(TypeError, "assigning a schema to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}")
158
- elsif instance.is_a?(JSI::Base)
159
- raise(TypeError, "assigning another JSI::Base instance to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}")
160
- end
135
+ raise(Bug, "no #jsi_schemas") unless respond_to?(:jsi_schemas)
161
136
 
162
137
  jsi_initialize_memos
163
138
 
164
- if instance == NOINSTANCE
165
- self.jsi_document = jsi_document
166
- self.jsi_ptr = jsi_ptr
167
- if @jsi_ptr.root?
168
- raise(Bug, "jsi_root_node cannot be specified for root JSI") if jsi_root_node
169
- @jsi_root_node = self
170
- else
171
- if !jsi_root_node.is_a?(JSI::Base)
172
- raise(TypeError, "jsi_root_node must be a JSI::Base; got: #{jsi_root_node.inspect}")
173
- end
174
- if !jsi_root_node.jsi_ptr.root?
175
- raise(Bug, "jsi_root_node ptr #{jsi_root_node.jsi_ptr.inspect} is not root")
176
- end
177
- @jsi_root_node = jsi_root_node
178
- end
179
- else
180
- raise(Bug, 'incorrect usage') if jsi_document || jsi_ptr || jsi_root_node
181
- @jsi_document = instance
182
- @jsi_ptr = Ptr[]
139
+ self.jsi_document = jsi_document
140
+ self.jsi_ptr = jsi_ptr
141
+ if @jsi_ptr.root?
142
+ raise(Bug, "jsi_root_node specified for root JSI") if jsi_root_node
183
143
  @jsi_root_node = self
144
+ else
145
+ raise(Bug, "jsi_root_node is not JSI::Base") if !jsi_root_node.is_a?(JSI::Base)
146
+ raise(Bug, "jsi_root_node ptr is not root") if !jsi_root_node.jsi_ptr.root?
147
+ @jsi_root_node = jsi_root_node
184
148
  end
185
-
186
149
  self.jsi_schema_base_uri = jsi_schema_base_uri
187
150
  self.jsi_schema_resource_ancestors = jsi_schema_resource_ancestors
188
151
 
189
- if self.jsi_instance.respond_to?(:to_hash)
190
- extend PathedHashNode
191
- end
192
- if self.jsi_instance.respond_to?(:to_ary)
193
- extend PathedArrayNode
152
+ if jsi_instance.is_a?(JSI::Base)
153
+ raise(TypeError, "a JSI::Base instance must not be another JSI::Base. received: #{jsi_instance.pretty_inspect.chomp}")
194
154
  end
195
155
  end
196
156
 
@@ -211,53 +171,53 @@ module JSI
211
171
  # @return [JSI::Base]
212
172
  attr_reader :jsi_root_node
213
173
 
174
+ # the content of this node in our {#jsi_document} at our {#jsi_ptr}. the same as {#jsi_instance}.
175
+ def jsi_node_content
176
+ content = jsi_ptr.evaluate(jsi_document)
177
+ content
178
+ end
179
+
214
180
  # the JSON schema instance this JSI represents - the underlying JSON data used to instantiate this JSI
215
181
  alias_method :jsi_instance, :jsi_node_content
216
182
 
217
- # each is overridden by PathedHashNode or PathedArrayNode when appropriate. the base #each
218
- # is not actually implemented, along with all the methods of Enumerable.
219
- def each(*_)
220
- raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{jsi_instance.pretty_inspect.chomp}"
221
- end
222
-
223
183
  # yields a JSI of each node at or below this one in this JSI's document.
224
184
  #
225
185
  # returns an Enumerator if no block is given.
226
186
  #
227
- # @yield [JSI::Base] each node in the document, starting with self
187
+ # @yield [JSI::Base] each descendent node, starting with self
228
188
  # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
229
- def jsi_each_child_node(&block)
189
+ def jsi_each_descendent_node(&block)
230
190
  return to_enum(__method__) unless block
231
191
 
232
192
  yield self
233
193
  if respond_to?(:to_hash)
234
194
  each_key do |k|
235
- self[k, as_jsi: true].jsi_each_child_node(&block)
195
+ self[k, as_jsi: true].jsi_each_descendent_node(&block)
236
196
  end
237
197
  elsif respond_to?(:to_ary)
238
198
  each_index do |i|
239
- self[i, as_jsi: true].jsi_each_child_node(&block)
199
+ self[i, as_jsi: true].jsi_each_descendent_node(&block)
240
200
  end
241
201
  end
242
202
  nil
243
203
  end
244
204
 
245
- # recursively selects child nodes of this JSI, returning a modified copy of self containing only
246
- # child nodes for which the given block had a true-ish result.
205
+ # recursively selects descendent nodes of this JSI, returning a modified copy of self containing only
206
+ # descendent nodes for which the given block had a true-ish result.
247
207
  #
248
208
  # this method yields a node before recursively descending to its child nodes, so leaf nodes are yielded
249
- # last, after their parents. if a node is not selected, its children are never recursed.
209
+ # last, after their parents. if a node is not selected, its descendents are never recursed.
250
210
  #
251
- # @yield [JSI::Base] each child node below self
211
+ # @yield [JSI::Base] each descendent node below self
252
212
  # @return [JSI::Base] modified copy of self containing only the selected nodes
253
- def jsi_select_children_node_first(&block)
213
+ def jsi_select_descendents_node_first(&block)
254
214
  jsi_modified_copy do |instance|
255
215
  if respond_to?(:to_hash)
256
216
  res = instance.class.new
257
217
  each_key do |k|
258
218
  v = self[k, as_jsi: true]
259
219
  if yield(v)
260
- res[k] = v.jsi_select_children_node_first(&block).jsi_node_content
220
+ res[k] = v.jsi_select_descendents_node_first(&block).jsi_node_content
261
221
  end
262
222
  end
263
223
  res
@@ -266,7 +226,7 @@ module JSI
266
226
  each_index do |i|
267
227
  e = self[i, as_jsi: true]
268
228
  if yield(e)
269
- res << e.jsi_select_children_node_first(&block).jsi_node_content
229
+ res << e.jsi_select_descendents_node_first(&block).jsi_node_content
270
230
  end
271
231
  end
272
232
  res
@@ -276,20 +236,23 @@ module JSI
276
236
  end
277
237
  end
278
238
 
279
- # recursively selects child nodes of this JSI, returning a modified copy of self containing only
280
- # child nodes for which the given block had a true-ish result.
239
+ # @deprecated after v0.6
240
+ alias_method :jsi_select_children_node_first, :jsi_select_descendents_node_first
241
+
242
+ # recursively selects descendent nodes of this JSI, returning a modified copy of self containing only
243
+ # descendent nodes for which the given block had a true-ish result.
281
244
  #
282
245
  # this method recursively descends child nodes before yielding each node, so leaf nodes are yielded
283
246
  # before their parents.
284
247
  #
285
- # @yield [JSI::Base] each child node below self
248
+ # @yield [JSI::Base] each descendent node below self
286
249
  # @return [JSI::Base] modified copy of self containing only the selected nodes
287
- def jsi_select_children_leaf_first(&block)
250
+ def jsi_select_descendents_leaf_first(&block)
288
251
  jsi_modified_copy do |instance|
289
252
  if respond_to?(:to_hash)
290
253
  res = instance.class.new
291
254
  each_key do |k|
292
- v = self[k, as_jsi: true].jsi_select_children_leaf_first(&block)
255
+ v = self[k, as_jsi: true].jsi_select_descendents_leaf_first(&block)
293
256
  if yield(v)
294
257
  res[k] = v.jsi_node_content
295
258
  end
@@ -298,7 +261,7 @@ module JSI
298
261
  elsif respond_to?(:to_ary)
299
262
  res = instance.class.new
300
263
  each_index do |i|
301
- e = self[i, as_jsi: true].jsi_select_children_leaf_first(&block)
264
+ e = self[i, as_jsi: true].jsi_select_descendents_leaf_first(&block)
302
265
  if yield(e)
303
266
  res << e.jsi_node_content
304
267
  end
@@ -310,6 +273,9 @@ module JSI
310
273
  end
311
274
  end
312
275
 
276
+ # @deprecated after v0.6
277
+ alias_method :jsi_select_children_leaf_first, :jsi_select_descendents_leaf_first
278
+
313
279
  # an array of JSI instances above this one in the document.
314
280
  #
315
281
  # @return [Array<JSI::Base>]
@@ -327,7 +293,31 @@ module JSI
327
293
  #
328
294
  # @return [JSI::Base, nil]
329
295
  def jsi_parent_node
330
- jsi_parent_nodes.first
296
+ jsi_ptr.root? ? nil : jsi_root_node.jsi_descendent_node(jsi_ptr.parent)
297
+ end
298
+
299
+ # ancestor JSI instances from this node up to the root. this node itself is always its own first ancestor.
300
+ #
301
+ # @return [Array<JSI::Base>]
302
+ def jsi_ancestor_nodes
303
+ ancestors = []
304
+ ancestor = jsi_root_node
305
+ ancestors << ancestor
306
+
307
+ jsi_ptr.tokens.each do |token|
308
+ ancestor = ancestor[token, as_jsi: true]
309
+ ancestors << ancestor
310
+ end
311
+ ancestors.reverse!.freeze
312
+ end
313
+
314
+ # the descendent node at the given pointer
315
+ #
316
+ # @param ptr [JSI::Ptr, #to_ary]
317
+ # @return [JSI::Base]
318
+ def jsi_descendent_node(ptr)
319
+ descendent = Ptr.ary_ptr(ptr).evaluate(self, as_jsi: true)
320
+ descendent
331
321
  end
332
322
 
333
323
  # subscripts to return a child value identified by the given token.
@@ -338,14 +328,14 @@ module JSI
338
328
  #
339
329
  # - :auto (default): by default a JSI will be returned when either:
340
330
  #
341
- # - the result is a complex value (responds to #to_ary or #to_hash) and is described by some schemas
331
+ # - the result is a complex value (responds to #to_ary or #to_hash)
342
332
  # - the result is a schema (including true/false schemas)
343
333
  #
344
334
  # a plain value is returned when no schemas are known to describe the instance, or when the value is a
345
335
  # simple type (anything unresponsive to #to_ary / #to_hash).
346
336
  #
347
- # - true: the result value will always be returned as a JSI. the #jsi_schemas of the result may be empty
348
- # if no schemas describe the instance.
337
+ # - true: the result value will always be returned as a JSI. the {#jsi_schemas} of the result may be
338
+ # empty if no schemas describe the instance.
349
339
  # - false: the result value will always be the plain instance.
350
340
  #
351
341
  # note that nil is returned (regardless of as_jsi) when there is no value to return because the token
@@ -381,14 +371,18 @@ module JSI
381
371
 
382
372
  if token_in_range
383
373
  jsi_subinstance_as_jsi(value, subinstance_schemas, as_jsi) do
384
- jsi_subinstance_memos[token: token, subinstance_schemas: subinstance_schemas]
374
+ jsi_subinstance_memos[
375
+ token: token,
376
+ subinstance_schemas: subinstance_schemas,
377
+ includes: SchemaClasses.includes_for(value),
378
+ ]
385
379
  end
386
380
  else
387
381
  if use_default
388
382
  defaults = Set.new
389
383
  subinstance_schemas.each do |subinstance_schema|
390
- if subinstance_schema.respond_to?(:to_hash) && subinstance_schema.key?('default')
391
- defaults << subinstance_schema['default']
384
+ if subinstance_schema.keyword?('default')
385
+ defaults << subinstance_schema.jsi_node_content['default']
392
386
  end
393
387
  end
394
388
  end
@@ -415,7 +409,7 @@ module JSI
415
409
  # @param value [JSI::Base, Object] the value to be assigned
416
410
  def []=(token, value)
417
411
  unless respond_to?(:to_hash) || respond_to?(:to_ary)
418
- raise(NoMethodError, "cannot assign subscript (using token: #{token.inspect}) to instance: #{jsi_instance.pretty_inspect.chomp}")
412
+ raise(CannotSubscriptError, "cannot assign subscript (using token: #{token.inspect}) to instance: #{jsi_instance.pretty_inspect.chomp}")
419
413
  end
420
414
  if value.is_a?(Base)
421
415
  self[token] = value.jsi_instance
@@ -427,12 +421,12 @@ module JSI
427
421
  # the set of JSI schema modules corresponding to the schemas that describe this JSI
428
422
  # @return [Set<Module>]
429
423
  def jsi_schema_modules
430
- jsi_schemas.map(&:jsi_schema_module).to_set.freeze
424
+ Util.ensure_module_set(jsi_schemas.map(&:jsi_schema_module))
431
425
  end
432
426
 
433
427
  # yields the content of this JSI's instance. the block must result in
434
- # a modified copy of the yielded instance (not destructively modifying it)
435
- # which will be used to instantiate a new JSI with the modified content.
428
+ # a modified copy of the yielded instance (not modified in place, which would alter this JSI
429
+ # as well) which will be used to instantiate and return a new JSI with the modified content.
436
430
  #
437
431
  # the result may have different schemas which describe it than this JSI's schemas,
438
432
  # if conditional applicator schemas apply differently to the modified instance.
@@ -443,17 +437,14 @@ module JSI
443
437
  def jsi_modified_copy(&block)
444
438
  if @jsi_ptr.root?
445
439
  modified_document = @jsi_ptr.modified_document_copy(@jsi_document, &block)
446
- self.class.new(Base::NOINSTANCE,
447
- jsi_document: modified_document,
448
- jsi_ptr: @jsi_ptr,
449
- jsi_schema_base_uri: @jsi_schema_base_uri,
450
- jsi_schema_resource_ancestors: @jsi_schema_resource_ancestors, # this can only be empty but included for consistency
440
+ jsi_schemas.new_jsi(modified_document,
441
+ uri: jsi_schema_base_uri,
451
442
  )
452
443
  else
453
444
  modified_jsi_root_node = @jsi_root_node.jsi_modified_copy do |root|
454
445
  @jsi_ptr.modified_document_copy(root, &block)
455
446
  end
456
- @jsi_ptr.evaluate(modified_jsi_root_node, as_jsi: true)
447
+ modified_jsi_root_node.jsi_descendent_node(@jsi_ptr)
457
448
  end
458
449
  end
459
450
 
@@ -470,19 +461,21 @@ module JSI
470
461
  jsi_schemas.instance_valid?(self)
471
462
  end
472
463
 
473
- # @private
474
- def fully_validate(errors_as_objects: false)
475
- raise(NotImplementedError, "Base#fully_validate removed: see new validation interface Base#jsi_validate")
476
- end
477
-
478
- # @private
479
- def validate
480
- raise(NotImplementedError, "Base#validate renamed: see Base#jsi_valid?")
481
- end
482
-
483
- # @private
484
- def validate!
485
- raise(NotImplementedError, "Base#validate! removed")
464
+ # queries this JSI using the [JMESPath Ruby](https://rubygems.org/gems/jmespath) gem.
465
+ # see [https://jmespath.org/](https://jmespath.org/) to learn the JMESPath query language.
466
+ #
467
+ # the JMESPath gem is not a dependency of JSI, so must be installed / added to your Gemfile to use.
468
+ # e.g. `gem 'jmespath', '~> 1.5'`. note that versions below 1.5 are not compatible with JSI.
469
+ #
470
+ # @param expression [String] a [JMESPath](https://jmespath.org/) expression
471
+ # @param runtime_options passed to [JMESPath.search](https://rubydoc.info/gems/jmespath/JMESPath#search-class_method),
472
+ # though no runtime_options are publicly documented or normally used.
473
+ # @return [Array, Object, nil] query results.
474
+ # see [JMESPath.search](https://rubydoc.info/gems/jmespath/JMESPath#search-class_method)
475
+ def jmespath_search(expression, **runtime_options)
476
+ Util.require_jmespath
477
+
478
+ JMESPath.search(expression, self, **runtime_options)
486
479
  end
487
480
 
488
481
  def dup
@@ -495,6 +488,8 @@ module JSI
495
488
  "\#<#{jsi_object_group_text.join(' ')} #{jsi_instance.inspect}>"
496
489
  end
497
490
 
491
+ alias_method :to_s, :inspect
492
+
498
493
  # pretty-prints a representation of this JSI to the given printer
499
494
  # @return [void]
500
495
  def pretty_print(q)
@@ -513,27 +508,14 @@ module JSI
513
508
  # @private
514
509
  # @return [Array<String>]
515
510
  def jsi_object_group_text
516
- class_name = self.class.name unless self.class.in_schema_classes
517
- class_txt = begin
518
- if class_name
519
- # ignore ID
520
- schema_module_names = jsi_schemas.map { |schema| schema.jsi_schema_module.name }.compact
521
- if schema_module_names.empty?
522
- class_name
523
- else
524
- "#{class_name} (#{schema_module_names.join(', ')})"
525
- end
526
- else
527
- schema_names = jsi_schemas.map { |schema| schema.jsi_schema_module.name_from_ancestor || schema.schema_uri }.compact
528
- if schema_names.empty?
529
- "JSI"
530
- else
531
- "JSI (#{schema_names.join(', ')})"
532
- end
533
- end
511
+ schema_names = jsi_schemas.map { |schema| schema.jsi_schema_module.name_from_ancestor || schema.schema_uri }.compact
512
+ if schema_names.empty?
513
+ class_txt = "JSI"
514
+ else
515
+ class_txt = "JSI (#{schema_names.join(', ')})"
534
516
  end
535
517
 
536
- if (is_a?(PathedArrayNode) || is_a?(PathedHashNode)) && ![Array, Hash].include?(jsi_node_content.class)
518
+ if (is_a?(ArrayNode) || is_a?(HashNode)) && ![Array, Hash].include?(jsi_node_content.class)
537
519
  if jsi_node_content.respond_to?(:jsi_object_group_text)
538
520
  content_txt = jsi_node_content.jsi_object_group_text
539
521
  else
@@ -553,7 +535,7 @@ module JSI
553
535
  # a jsonifiable representation of the instance
554
536
  # @return [Object]
555
537
  def as_json(*opt)
556
- Typelike.as_json(jsi_instance, *opt)
538
+ Util.as_json(jsi_instance, *opt)
557
539
  end
558
540
 
559
541
  # an opaque fingerprint of this JSI for {Util::FingerprintHash}.
@@ -574,22 +556,14 @@ module JSI
574
556
 
575
557
  def jsi_subinstance_schemas_memos
576
558
  jsi_memomap(:subinstance_schemas, key_by: -> (i) { i[:token] }) do |token: , instance: , subinstance: |
577
- SchemaSet.build do |schemas|
578
- jsi_schemas.each do |schema|
579
- schema.each_child_applicator_schema(token, instance) do |child_app_schema|
580
- child_app_schema.each_inplace_applicator_schema(subinstance) do |child_inpl_app_schema|
581
- schemas << child_inpl_app_schema
582
- end
583
- end
584
- end
585
- end
559
+ jsi_schemas.child_applicator_schemas(token, instance).inplace_applicator_schemas(subinstance)
586
560
  end
587
561
  end
588
562
 
589
563
  def jsi_subinstance_memos
590
- jsi_memomap(:subinstance, key_by: -> (i) { i[:token] }) do |token: , subinstance_schemas: |
591
- JSI::SchemaClasses.class_for_schemas(subinstance_schemas).new(Base::NOINSTANCE,
592
- jsi_document: @jsi_document,
564
+ jsi_memomap(:subinstance, key_by: -> (i) { i[:token] }) do |token: , subinstance_schemas: , includes: |
565
+ jsi_class = JSI::SchemaClasses.class_for_schemas(subinstance_schemas, includes: includes)
566
+ jsi_class.new(@jsi_document,
593
567
  jsi_ptr: @jsi_ptr[token],
594
568
  jsi_root_node: @jsi_root_node,
595
569
  jsi_schema_base_uri: jsi_resource_ancestor_uri,
@@ -599,12 +573,12 @@ module JSI
599
573
  end
600
574
 
601
575
  def jsi_subinstance_as_jsi(value, subinstance_schemas, as_jsi)
602
- value_as_jsi = if [true, false].include?(as_jsi)
603
- as_jsi
576
+ if [true, false].include?(as_jsi)
577
+ value_as_jsi = as_jsi
604
578
  elsif as_jsi == :auto
605
- complex_value = subinstance_schemas.any? && (value.respond_to?(:to_hash) || value.respond_to?(:to_ary))
606
- schema_value = subinstance_schemas.any? { |subinstance_schema| subinstance_schema.describes_schema? }
607
- complex_value || schema_value
579
+ complex_value = value.respond_to?(:to_hash) || value.respond_to?(:to_ary)
580
+ schema_value = subinstance_schemas.any?(&:describes_schema?)
581
+ value_as_jsi = complex_value || schema_value
608
582
  else
609
583
  raise(ArgumentError, "as_jsi must be one of: :auto, true, false")
610
584
  end
data/lib/jsi/jsi_coder.rb CHANGED
@@ -48,7 +48,7 @@ module JSI
48
48
  unless data.respond_to?(:to_ary)
49
49
  raise TypeError, "expected array-like column data; got: #{data.class}: #{data.inspect}"
50
50
  end
51
- data.map { |el| load_object(el) }
51
+ data.to_ary.map { |el| load_object(el) }
52
52
  else
53
53
  load_object(data)
54
54
  end
@@ -66,7 +66,7 @@ module JSI
66
66
  unless object.respond_to?(:to_ary)
67
67
  raise(TypeError, "expected array-like attribute; got: #{object.class}: #{object.inspect}")
68
68
  end
69
- object.map do |el|
69
+ object.to_ary.map do |el|
70
70
  dump_object(el)
71
71
  end
72
72
  else
@@ -86,7 +86,7 @@ module JSI
86
86
  # @param object [JSI::Base, Object]
87
87
  # @return [Object]
88
88
  def dump_object(object)
89
- JSI::Typelike.as_json(object)
89
+ JSI::Util.as_json(object)
90
90
  end
91
91
  end
92
92
  end
@@ -2,6 +2,5 @@
2
2
 
3
3
  module JSI
4
4
  module Metaschema
5
- include JSI::Schema::DescribesSchema
6
5
  end
7
6
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- # @private
5
4
  # internal class to bootstrap a metaschema. represents a schema without the complexity of JSI::Base. the
6
5
  # schema is represented but schemas describing the schema are not.
7
6
  #
@@ -9,9 +8,11 @@ module JSI
9
8
  # Schema#subschema and Schema#resource_root_subschema are the intended mechanisms to instantiate subschemas
10
9
  # and resolve references. #[] and #jsi_root_node are not implemented.
11
10
  #
12
- # metaschema instance modules are attached to generated subclasses of BootstrapSchema by
11
+ # schema implementation modules are attached to generated subclasses of BootstrapSchema by
13
12
  # {SchemaClasses.bootstrap_schema_class}. that subclass is instantiated with a document and
14
13
  # pointer, representing a schema.
14
+ #
15
+ # @api private
15
16
  class MetaschemaNode::BootstrapSchema
16
17
  include Util::Memoize
17
18
  include Util::FingerprintHash
@@ -22,7 +23,7 @@ module JSI
22
23
  if self == MetaschemaNode::BootstrapSchema
23
24
  name
24
25
  else
25
- "#{name || MetaschemaNode::BootstrapSchema.name} (#{metaschema_instance_modules.map(&:inspect).join(', ')})"
26
+ "#{name || MetaschemaNode::BootstrapSchema.name} (#{schema_implementation_modules.map(&:inspect).join(', ')})"
26
27
  end
27
28
  end
28
29
 
@@ -36,9 +37,7 @@ module JSI
36
37
  jsi_ptr: Ptr[],
37
38
  jsi_schema_base_uri: nil
38
39
  )
39
- unless respond_to?(:metaschema_instance_modules)
40
- raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #metaschema_instance_modules")
41
- end
40
+ raise(Bug, "no #schema_implementation_modules") unless respond_to?(:schema_implementation_modules)
42
41
 
43
42
  jsi_initialize_memos
44
43
 
@@ -62,6 +61,8 @@ module JSI
62
61
  "\#<#{jsi_object_group_text.join(' ')} #{schema_content.inspect}>"
63
62
  end
64
63
 
64
+ alias_method :to_s, :inspect
65
+
65
66
  # pretty-prints a representation of self to the given printer
66
67
  # @return [void]
67
68
  def pretty_print(q)
@@ -82,7 +83,7 @@ module JSI
82
83
  def jsi_object_group_text
83
84
  [
84
85
  self.class.name || MetaschemaNode::BootstrapSchema.name,
85
- "(#{metaschema_instance_modules.map(&:inspect).join(', ')})",
86
+ "(#{schema_implementation_modules.map(&:inspect).join(', ')})",
86
87
  jsi_ptr.uri,
87
88
  ]
88
89
  end
@@ -93,7 +94,7 @@ module JSI
93
94
  class: self.class,
94
95
  jsi_ptr: @jsi_ptr,
95
96
  jsi_document: @jsi_document,
96
- metaschema_instance_modules: metaschema_instance_modules,
97
+ schema_implementation_modules: schema_implementation_modules,
97
98
  }
98
99
  end
99
100
  end