jsi 0.6.0 → 0.7.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.
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